Completed
Push — develop ( 3d10a6...3822de )
by Neomerx
02:23
created

Schema   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 276
Duplicated Lines 5.8 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 77.88%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 7
dl 16
loc 276
ccs 81
cts 104
cp 0.7788
rs 10
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A getType() 0 4 1
A getAttributeMapping() 0 4 1
A getRelationshipMapping() 0 4 1
A hasAttributeMapping() 8 8 2
A hasRelationshipMapping() 8 8 2
A getAttributes() 0 18 4
B getRelationships() 0 49 7
A isAddSelfLinkInRelationshipWithData() 0 4 1
A getModelSchemas() 0 4 1
A getJsonSchemas() 0 4 1
B createRelationshipRepresentationFromData() 0 63 5
A hasProperty() 0 6 1
A getRelationshipSelfSubUrl() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php namespace Limoncello\Flute\Schema;
2
3
/**
4
 * Copyright 2015-2018 [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\Application\ModelInterface;
20
use Limoncello\Contracts\Data\ModelSchemaInfoInterface;
21
use Limoncello\Contracts\Data\RelationshipTypes;
22
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface;
23
use Limoncello\Flute\Contracts\Schema\JsonSchemasInterface;
24
use Limoncello\Flute\Contracts\Schema\SchemaInterface;
25
use Limoncello\Flute\Contracts\Validation\JsonApiQueryParserInterface;
26
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
27
use Neomerx\JsonApi\Contracts\Schema\DocumentInterface;
28
use Neomerx\JsonApi\Contracts\Schema\LinkInterface;
29
use Neomerx\JsonApi\Schema\BaseSchema;
30
use Neomerx\JsonApi\Schema\Identifier;
31
32
/**
33
 * @package Limoncello\Flute
34
 */
35
abstract class Schema extends BaseSchema implements SchemaInterface
36
{
37
    /**
38
     * @var ModelSchemaInfoInterface
39
     */
40
    private $modelSchemas;
41
42
    /**
43
     * @var JsonSchemasInterface
44
     */
45
    private $jsonSchemas;
46
47
    /**
48
     * @param FactoryInterface         $factory
49
     * @param JsonSchemasInterface     $jsonSchemas
50
     * @param ModelSchemaInfoInterface $modelSchemas
51
     */
52 9
    public function __construct(
53
        FactoryInterface $factory,
54
        JsonSchemasInterface $jsonSchemas,
55
        ModelSchemaInfoInterface $modelSchemas
56
    ) {
57 9
        assert(empty(static::TYPE) === false);
58
59 9
        parent::__construct($factory);
60
61 9
        $this->modelSchemas = $modelSchemas;
62 9
        $this->jsonSchemas  = $jsonSchemas;
63
    }
64
65
    /**
66
     * @inheritdoc
67
     */
68 1
    public function getType(): string
69
    {
70 1
        return static::TYPE;
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76
    public static function getAttributeMapping(string $jsonName): string
77
    {
78
        return static::getMappings()[static::SCHEMA_ATTRIBUTES][$jsonName];
79
    }
80
81
    /**
82
     * @inheritdoc
83
     */
84 2
    public static function getRelationshipMapping(string $jsonName): string
85
    {
86 2
        return static::getMappings()[static::SCHEMA_RELATIONSHIPS][$jsonName];
87
    }
88
89
    /**
90
     * @inheritdoc
91
     */
92 6 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...
93
    {
94 6
        $mappings = static::getMappings();
95
96
        return
97 6
            array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true &&
98 6
            array_key_exists($jsonName, $mappings[static::SCHEMA_ATTRIBUTES]) === true;
99
    }
100
101
    /**
102
     * @inheritdoc
103
     */
104 4 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...
105
    {
106 4
        $mappings = static::getMappings();
107
108
        return
109 4
            array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true &&
110 4
            array_key_exists($jsonName, $mappings[static::SCHEMA_RELATIONSHIPS]) === true;
111
    }
112
113
    /**
114
     * @inheritdoc
115
     */
116
    public function getAttributes($model): iterable
117
    {
118
        $attributes = [];
119
        $mappings   = static::getMappings();
120
        if (array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true) {
121
            $attrMappings = $mappings[static::SCHEMA_ATTRIBUTES];
122
123
            // `id` is a `special` attribute and cannot be included in JSON API resource
124
            unset($attrMappings[static::RESOURCE_ID]);
125
126
            foreach ($attrMappings as $jsonAttrName => $modelAttrName) {
127
                $attributes[$jsonAttrName] =
128
                    $this->hasProperty($model, $modelAttrName) === true ? $model->{$modelAttrName} : null;
129
            }
130
        }
131
132
        return $attributes;
133
    }
134
135
    /**
136
     * @inheritdoc
137
     *
138
     * @SuppressWarnings(PHPMD.ElseExpression)
139
     */
140 2
    public function getRelationships($model): iterable
141
    {
142 2
        assert($model instanceof ModelInterface);
143
144 2
        $relationships = [];
145
146 2
        $mappings = static::getMappings();
147 2
        if (array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true) {
148 2
            foreach ($mappings[static::SCHEMA_RELATIONSHIPS] as $jsonRelName => $modelRelName) {
149
                // if model has relationship data then use it
150 2
                if ($this->hasProperty($model, $modelRelName) === true) {
151 1
                    $relationships[$jsonRelName] = $this->createRelationshipRepresentationFromData(
152 1
                        $model,
153 1
                        $modelRelName,
154 1
                        $jsonRelName
155
                    );
156 1
                    continue;
157
                }
158
159
                // if relationship is `belongs-to` and has that ID we can add relationship as identifier
160 2
                $modelClass  = get_class($model);
161 2
                $relType = $this->getModelSchemas()->getRelationshipType($modelClass, $modelRelName);
162 2
                if ($relType === RelationshipTypes::BELONGS_TO) {
163 2
                    $fkName = $this->getModelSchemas()->getForeignKey($modelClass, $modelRelName);
164 2
                    if ($this->hasProperty($model, $fkName) === true) {
165 1
                        $reverseIndex  = $model->{$fkName};
166 1
                        if ($reverseIndex === null) {
167 1
                            $identifier = null;
168
                        } else {
169
                            $reverseSchema = $this->getJsonSchemas()
170
                                ->getRelationshipSchema(static::class, $jsonRelName);
171
                            $reverseType   = $reverseSchema->getType();
172
                            $identifier    = new Identifier($reverseIndex, $reverseType, false, null);
173
                        }
174 1
                        $relationships[$jsonRelName] = [
175 1
                            static::RELATIONSHIP_DATA       => $identifier,
176 1
                            static::RELATIONSHIP_LINKS_SELF => $this->isAddSelfLinkInRelationshipWithData($jsonRelName),
177
                        ];
178 1
                        continue;
179
                    }
180
                }
181
182
                // if we are here it's nothing left but show relationship as a link
183 2
                $relationships[$jsonRelName] = [static::RELATIONSHIP_LINKS_SELF => true];
184
            }
185
        }
186
187 2
        return $relationships;
188
    }
189
190
    /**
191
     * @inheritdoc
192
     */
193 2
    public function isAddSelfLinkInRelationshipWithData(string $relationshipName): bool
194
    {
195 2
        return false;
196
    }
197
198
    /**
199
     * @return ModelSchemaInfoInterface
200
     */
201 2
    protected function getModelSchemas(): ModelSchemaInfoInterface
202
    {
203 2
        return $this->modelSchemas;
204
    }
205
206
    /**
207
     * @return JsonSchemasInterface
208
     */
209
    protected function getJsonSchemas(): JsonSchemasInterface
210
    {
211
        return $this->jsonSchemas;
212
    }
213
214
    /**
215
     * @param ModelInterface $model
216
     * @param string         $modelRelName
217
     * @param string         $jsonRelName
218
     *
219
     * @return array
220
     *
221
     * @SuppressWarnings(PHPMD.ElseExpression)
222
     */
223 1
    protected function createRelationshipRepresentationFromData(
224
        ModelInterface $model,
225
        string $modelRelName,
226
        string $jsonRelName
227
    ): array {
228 1
        assert($this->hasProperty($model, $modelRelName) === true);
229 1
        $relationshipData = $model->{$modelRelName};
230 1
        $isPaginatedData  = $relationshipData instanceof PaginatedDataInterface;
231
232 1
        $description = [static::RELATIONSHIP_LINKS_SELF => $this->isAddSelfLinkInRelationshipWithData($jsonRelName)];
233
234 1
        if ($isPaginatedData === false) {
235
            $description[static::RELATIONSHIP_DATA] = $relationshipData;
236
237
            return $description;
238
        }
239
240 1
        assert($relationshipData instanceof PaginatedDataInterface);
241
242 1
        $description[static::RELATIONSHIP_DATA] = $relationshipData->getData();
243
244 1
        if ($relationshipData->hasMoreItems() === false) {
245
            return $description;
246
        }
247
248
        // if we are here then relationship contains paginated data, so we have to add pagination links
249 1
        $offset    = $relationshipData->getOffset();
250 1
        $limit     = $relationshipData->getLimit();
251 1
        $urlPrefix = $this->getRelationshipSelfSubUrl($model, $jsonRelName) . '?';
252
        $buildLink = function (int $offset, int $limit) use ($urlPrefix) : LinkInterface {
253
            $paramsWithPaging = [
254 1
                JsonApiQueryParserInterface::PARAM_PAGING_OFFSET => $offset,
255 1
                JsonApiQueryParserInterface::PARAM_PAGING_LIMIT  => $limit,
256
            ];
257
258 1
            $subUrl = $urlPrefix . http_build_query($paramsWithPaging);
259
260 1
            return $this->getFactory()->createLink(true, $subUrl, false);
261 1
        };
262
263 1
        $nextOffset = $offset + $limit;
264 1
        $nextLimit  = $limit;
265 1
        if ($offset <= 0) {
266
            $description[static::RELATIONSHIP_LINKS] = [
267
                DocumentInterface::KEYWORD_NEXT => $buildLink($nextOffset, $nextLimit),
268
            ];
269
        } else {
270 1
            $prevOffset = $offset - $limit;
271 1
            if ($prevOffset < 0) {
272
                // set offset 0 and decrease limit
273 1
                $prevLimit  = $limit + $prevOffset;
274 1
                $prevOffset = 0;
275
            } else {
276 1
                $prevLimit = $limit;
277
            }
278 1
            $description[static::RELATIONSHIP_LINKS] = [
279 1
                DocumentInterface::KEYWORD_PREV => $buildLink($prevOffset, $prevLimit),
280 1
                DocumentInterface::KEYWORD_NEXT => $buildLink($nextOffset, $nextLimit),
281
            ];
282
        }
283
284 1
        return $description;
285
    }
286
287
    /**
288
     * @param ModelInterface $model
289
     * @param string         $name
290
     *
291
     * @return bool
292
     */
293 2
    private function hasProperty(ModelInterface $model, string $name): bool
294
    {
295 2
        $hasRelationship = property_exists($model, $name);
296
297 2
        return $hasRelationship;
298
    }
299
300
    /**
301
     * @param ModelInterface $model
302
     * @param string         $jsonRelName
303
     *
304
     * @return string
305
     */
306 1
    private function getRelationshipSelfSubUrl(ModelInterface $model, string $jsonRelName): string
307
    {
308 1
        return $this->getSelfSubUrl($model) . '/relationships/' . $jsonRelName;
309
    }
310
}
311