Completed
Push — master ( b6f561...db9bd8 )
by Neomerx
03:16
created

Schema::getRelationships()   C

Complexity

Conditions 12
Paths 2

Size

Total Lines 74
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 74
ccs 40
cts 40
cp 1
rs 5.3992
c 0
b 0
f 0
cc 12
eloc 41
nc 2
nop 3
crap 12

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php namespace Limoncello\Flute\Schema;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Limoncello\Contracts\Data\ModelSchemeInfoInterface;
20
use Limoncello\Contracts\Data\RelationshipTypes;
21
use Limoncello\Flute\Contracts\Adapters\PaginationStrategyInterface;
22
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface;
23
use Limoncello\Flute\Contracts\Schema\SchemaInterface;
24
use Neomerx\JsonApi\Contracts\Document\DocumentInterface;
25
use Neomerx\JsonApi\Contracts\Document\LinkInterface;
26
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
27
use Neomerx\JsonApi\Schema\SchemaProvider;
28
29
/**
30
 * @package Limoncello\Flute
31
 */
32
abstract class Schema extends SchemaProvider implements SchemaInterface
33
{
34
    /**
35
     * @var ModelSchemeInfoInterface
36
     */
37
    private $modelSchemes;
38
39
    /**
40
     * @param FactoryInterface         $factory
41
     * @param ModelSchemeInfoInterface $modelSchemes
42
     */
43 22
    public function __construct(FactoryInterface $factory, ModelSchemeInfoInterface $modelSchemes)
44
    {
45
        /** @noinspection PhpUndefinedFieldInspection */
46 22
        $this->resourceType = static::TYPE;
47
48 22
        parent::__construct($factory);
49
50 22
        $this->modelSchemes = $modelSchemes;
51
    }
52
53
    /**
54
     * @inheritdoc
55
     */
56 11
    public static function getAttributeMapping(string $jsonName): string
57
    {
58 11
        return static::getMappings()[static::SCHEMA_ATTRIBUTES][$jsonName];
59
    }
60
61
    /**
62
     * @inheritdoc
63
     */
64 13
    public static function getRelationshipMapping(string $jsonName): string
65
    {
66 13
        return static::getMappings()[static::SCHEMA_RELATIONSHIPS][$jsonName];
67
    }
68
69
    /**
70
     * @inheritdoc
71
     */
72 14 View Code Duplication
    public static function hasAttributeMapping(string $jsonName): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
73
    {
74 14
        $mappings = static::getMappings();
75
76
        return
77 14
            array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true &&
78 14
            array_key_exists($jsonName, $mappings[static::SCHEMA_ATTRIBUTES]) === true;
79
    }
80
81
    /**
82
     * @inheritdoc
83
     */
84 11 View Code Duplication
    public static function hasRelationshipMapping(string $jsonName): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
85
    {
86 11
        $mappings = static::getMappings();
87
88
        return
89 11
            array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true &&
90 11
            array_key_exists($jsonName, $mappings[static::SCHEMA_RELATIONSHIPS]) === true;
91
    }
92
93
    /**
94
     * @inheritdoc
95
     */
96 17
    public function getAttributes($model)
97
    {
98 17
        $attributes = [];
99 17
        $mappings   = static::getMappings();
100 17
        if (array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true) {
101 17
            $attrMappings = $mappings[static::SCHEMA_ATTRIBUTES];
102
103
            // `id` is a `special` attribute and cannot be included in JSON API resource
104 17
            unset($attrMappings[static::RESOURCE_ID]);
105
106 17
            foreach ($attrMappings as $jsonAttrName => $modelAttrName) {
107 17
                $attributes[$jsonAttrName] = isset($model->{$modelAttrName}) === true ? $model->{$modelAttrName} : null;
108
            }
109
        }
110
111 17
        return $attributes;
112
    }
113
114
    /** @noinspection PhpMissingParentCallCommonInspection
115
     * @inheritdoc
116
     *
117
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
118
     * @SuppressWarnings(PHPMD.ElseExpression)
119
     */
120 18
    public function getRelationships($model, $isPrimary, array $includeRelationships)
121
    {
122 18
        $modelClass    = get_class($model);
123 18
        $relationships = [];
124 18
        $mappings      = static::getMappings();
125 18
        if (array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true) {
126 18
            $relMappings = $mappings[static::SCHEMA_RELATIONSHIPS];
127 18
            foreach ($relMappings as $jsonRelName => $modelRelName) {
128 18
                $isRelToBeIncluded = array_key_exists($jsonRelName, $includeRelationships) === true;
129
130 18
                $hasRelData = $this->hasRelationship($model, $modelRelName);
131 18
                $relType    = $this->getModelSchemes()->getRelationshipType($modelClass, $modelRelName);
132
133 18
                $isShowAsLink = false;
134
135
                // there is a case for `to-1` relationship when we can return identity resource (type + id)
136 18
                if ($relType === RelationshipTypes::BELONGS_TO) {
137 16
                    if ($isRelToBeIncluded === true && $hasRelData === true) {
138 2
                        $relationships[$jsonRelName] = [static::DATA => $model->{$modelRelName}];
139 2
                        continue;
140
                    } else {
141 16
                        $schema = $this->getModelSchemes();
142
143 16
                        $class  = get_class($model);
144 16
                        $fkName = $schema->getForeignKey($class, $modelRelName);
145
146 16
                        $isShowAsLink = property_exists($model, $fkName) === false;
147
148 16
                        if ($isShowAsLink === false) {
149
                            // show as identity (type, id)
150
151 16
                            $identity       = null;
152 16
                            $reversePkValue = $model->{$fkName};
153 16
                            if ($reversePkValue !== null) {
154 15
                                $reverseClass  = $schema->getReverseModelClass($class, $modelRelName);
155 15
                                $reversePkName = $schema->getPrimaryKey($reverseClass);
156
157 15
                                $identity                   = new $reverseClass;
158 15
                                $identity->{$reversePkName} = $reversePkValue;
159
                            }
160
161 16
                            $relationships[$jsonRelName] = [
162 16
                                static::DATA  => $identity,
163 16
                                static::LINKS => $this->getRelationshipLinks($model, $jsonRelName),
164
                            ];
165 16
                            continue;
166
                        }
167
168
                        // the relationship will be shown as a link
169
                    }
170
                }
171
172
                // if our storage do not have any data for this relationship or relationship would not
173
                // be included we return it as link
174 18
                if ($hasRelData === false ||
175 18
                    ($isRelToBeIncluded === false && $relType !== RelationshipTypes::BELONGS_TO)
176
                ) {
177 18
                    $isShowAsLink = true;
178
                }
179
180 18
                if ($isShowAsLink === true) {
181 18
                    $relationships[$jsonRelName] = $this->getRelationshipLinkRepresentation($model, $jsonRelName);
182 18
                    continue;
183
                }
184
185
                // if we are here this is a `to-Many` relationship and we have to show data and got the data
186
187 2
                $relUri                      = $this->getRelationshipSelfUrl($model, $jsonRelName);
188 2
                $relationships[$jsonRelName] = $this->getRelationshipDescription($model->{$modelRelName}, $relUri);
189
            }
190
        }
191
192 18
        return $relationships;
193
    }
194
195
    /**
196
     * @return ModelSchemeInfoInterface
197
     */
198 18
    protected function getModelSchemes(): ModelSchemeInfoInterface
199
    {
200 18
        return $this->modelSchemes;
201
    }
202
203
    /**
204
     * @param PaginatedDataInterface $data
205
     * @param string                 $uri
206
     *
207
     * @return array
208
     */
209 2
    protected function getRelationshipDescription(PaginatedDataInterface $data, string $uri): array
210
    {
211 2
        if ($data->hasMoreItems() === false) {
212 2
            return [static::DATA => $data->getData()];
213
        }
214
215 1
        $buildUrl = function ($offset) use ($data, $uri) {
216
            $paramsWithPaging = [
217 1
                PaginationStrategyInterface::PARAM_PAGING_SKIP => $offset,
218 1
                PaginationStrategyInterface::PARAM_PAGING_SIZE => $data->getLimit(),
219
            ];
220 1
            $fullUrl          = $uri . '?' . http_build_query($paramsWithPaging);
221
222 1
            return $fullUrl;
223 1
        };
224
225 1
        $links = [];
226
227
        // It looks like relationship can only hold first data rows so we might need `next` link but never `prev`
228
229 1
        if ($data->hasMoreItems() === true) {
230 1
            $offset                                 = $data->getOffset() + $data->getLimit();
231 1
            $links[DocumentInterface::KEYWORD_NEXT] = $this->createLink($buildUrl($offset));
232
        }
233
234
        return [
235 1
            static::DATA  => $data->getData(),
236 1
            static::LINKS => $links,
237
        ];
238
    }
239
240
    /**
241
     * @param mixed  $model
242
     * @param string $jsonRelationship
243
     *
244
     * @return array
245
     */
246 18
    protected function getRelationshipLinkRepresentation($model, string $jsonRelationship): array
247
    {
248
        return [
249 18
            static::LINKS     => $this->getRelationshipLinks($model, $jsonRelationship),
250 18
            static::SHOW_DATA => false,
251
        ];
252
    }
253
254
    /**
255
     * @param mixed  $model
256
     * @param string $jsonRelationship
257
     *
258
     * @return array
259
     */
260 18
    protected function getRelationshipLinks($model, string $jsonRelationship): array
261
    {
262 18
        $links = [];
263 18
        if ($this->showSelfInRelationship($jsonRelationship) === true) {
264 18
            $links[LinkInterface::SELF] = $this->getRelationshipSelfLink($model, $jsonRelationship);
265
        }
266 18
        if ($this->showRelatedInRelationship($jsonRelationship) === true) {
267 6
            $links[LinkInterface::RELATED] = $this->getRelationshipRelatedLink($model, $jsonRelationship);
268
        }
269
270 18
        return $links;
271
    }
272
273
    /**
274
     * Gets excludes from default 'show `self` link in relationships' rule.
275
     *
276
     * @return array Should be in ['jsonRelationship' => true] format.
277
     */
278 8
    protected function getExcludesFromDefaultShowSelfLinkInRelationships(): array
279
    {
280 8
        return [];
281
    }
282
283
    /**
284
     * Gets excludes from default 'show `related` link in relationships' rule.
285
     *
286
     * @return array Should be in ['jsonRelationship' => true] format.
287
     */
288 8
    protected function getExcludesFromDefaultShowRelatedLinkInRelationships(): array
289
    {
290 8
        return [];
291
    }
292
293
    /**
294
     * If `self` link should be shown in relationships by default.
295
     *
296
     * @return bool
297
     */
298 18
    protected function isShowSelfLinkInRelationships(): bool
299
    {
300 18
        return true;
301
    }
302
303
    /**
304
     * If `related` link should be shown in relationships by default.
305
     *
306
     * @return bool
307
     */
308 18
    protected function isShowRelatedLinkInRelationships(): bool
309
    {
310 18
        return true;
311
    }
312
313
    /**
314
     * @param mixed  $model
315
     * @param string $name
316
     *
317
     * @return bool
318
     */
319 18
    private function hasRelationship($model, string $name): bool
320
    {
321 18
        $hasRelationship = property_exists($model, $name);
322
323 18
        return $hasRelationship;
324
    }
325
326
    /**
327
     * @param string $jsonRelationship
328
     *
329
     * @return bool
330
     */
331 18
    private function showSelfInRelationship(string $jsonRelationship): bool
332
    {
333 18
        $default = $this->isShowSelfLinkInRelationships();
334 18
        $result  = isset($this->getExcludesFromDefaultShowSelfLinkInRelationships()[$jsonRelationship]) === true ?
335 18
            !$default : $default;
336
337 18
        return $result;
338
    }
339
340
    /**
341
     * @param string $jsonRelationship
342
     *
343
     * @return bool
344
     */
345 18
    private function showRelatedInRelationship(string $jsonRelationship): bool
346
    {
347 18
        $default = $this->isShowRelatedLinkInRelationships();
348 18
        $result  = isset($this->getExcludesFromDefaultShowRelatedLinkInRelationships()[$jsonRelationship]) === true ?
349 18
            !$default : $default;
350
351 18
        return $result;
352
    }
353
}
354