Completed
Push — develop ( 3822de...d029a9 )
by Neomerx
06:40
created

Schema::getRelationships()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 49
ccs 30
cts 30
cp 1
rs 8.1793
c 0
b 0
f 0
cc 7
nc 2
nop 1
crap 7
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 28
    public function __construct(
53
        FactoryInterface $factory,
54
        JsonSchemasInterface $jsonSchemas,
55
        ModelSchemaInfoInterface $modelSchemas
56
    ) {
57 28
        assert(empty(static::TYPE) === false);
58
59 28
        parent::__construct($factory);
60
61 28
        $this->modelSchemas = $modelSchemas;
62 28
        $this->jsonSchemas  = $jsonSchemas;
63
    }
64
65
    /**
66
     * @inheritdoc
67
     */
68 18
    public function getType(): string
69
    {
70 18
        return static::TYPE;
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76 14
    public static function getAttributeMapping(string $jsonName): string
77
    {
78 14
        return static::getMappings()[static::SCHEMA_ATTRIBUTES][$jsonName];
79
    }
80
81
    /**
82
     * @inheritdoc
83
     */
84 12
    public static function getRelationshipMapping(string $jsonName): string
85
    {
86 12
        return static::getMappings()[static::SCHEMA_RELATIONSHIPS][$jsonName];
87
    }
88
89
    /**
90
     * @inheritdoc
91
     */
92 21 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 21
        $mappings = static::getMappings();
95
96
        return
97 21
            array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true &&
98 21
            array_key_exists($jsonName, $mappings[static::SCHEMA_ATTRIBUTES]) === true;
99
    }
100
101
    /**
102
     * @inheritdoc
103
     */
104 15 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 15
        $mappings = static::getMappings();
107
108
        return
109 15
            array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true &&
110 15
            array_key_exists($jsonName, $mappings[static::SCHEMA_RELATIONSHIPS]) === true;
111
    }
112
113
    /**
114
     * @inheritdoc
115
     */
116 16
    public function getAttributes($model): iterable
117
    {
118 16
        $attributes = [];
119 16
        $mappings   = static::getMappings();
120 16
        if (array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true) {
121 16
            $attrMappings = $mappings[static::SCHEMA_ATTRIBUTES];
122
123
            // `id` is a `special` attribute and cannot be included in JSON API resource
124 16
            unset($attrMappings[static::RESOURCE_ID]);
125
126 16
            foreach ($attrMappings as $jsonAttrName => $modelAttrName) {
127 16
                $attributes[$jsonAttrName] =
128 16
                    $this->hasProperty($model, $modelAttrName) === true ? $model->{$modelAttrName} : null;
129
            }
130
        }
131
132 16
        return $attributes;
133
    }
134
135
    /**
136
     * @inheritdoc
137
     *
138
     * @SuppressWarnings(PHPMD.ElseExpression)
139
     */
140 19
    public function getRelationships($model): iterable
141
    {
142 19
        assert($model instanceof ModelInterface);
143
144 19
        $relationships = [];
145
146 19
        $mappings = static::getMappings();
147 19
        if (array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true) {
148 19
            foreach ($mappings[static::SCHEMA_RELATIONSHIPS] as $jsonRelName => $modelRelName) {
149
                // if model has relationship data then use it
150 19
                if ($this->hasProperty($model, $modelRelName) === true) {
151 4
                    $relationships[$jsonRelName] = $this->createRelationshipRepresentationFromData(
152 4
                        $model,
153 4
                        $modelRelName,
154 4
                        $jsonRelName
155
                    );
156 4
                    continue;
157
                }
158
159
                // if relationship is `belongs-to` and has that ID we can add relationship as identifier
160 18
                $modelClass  = get_class($model);
161 18
                $relType = $this->getModelSchemas()->getRelationshipType($modelClass, $modelRelName);
162 18
                if ($relType === RelationshipTypes::BELONGS_TO) {
163 16
                    $fkName = $this->getModelSchemas()->getForeignKey($modelClass, $modelRelName);
164 16
                    if ($this->hasProperty($model, $fkName) === true) {
165 15
                        $reverseIndex  = $model->{$fkName};
166 15
                        if ($reverseIndex === null) {
167 2
                            $identifier = null;
168
                        } else {
169 14
                            $reverseSchema = $this->getJsonSchemas()
170 14
                                ->getRelationshipSchema(static::class, $jsonRelName);
171 14
                            $reverseType   = $reverseSchema->getType();
172 14
                            $identifier    = new Identifier($reverseIndex, $reverseType, false, null);
173
                        }
174 15
                        $relationships[$jsonRelName] = [
175 15
                            static::RELATIONSHIP_DATA       => $identifier,
176 15
                            static::RELATIONSHIP_LINKS_SELF => $this->isAddSelfLinkInRelationshipWithData($jsonRelName),
177
                        ];
178 15
                        continue;
179
                    }
180
                }
181
182
                // if we are here it's nothing left but show relationship as a link
183 18
                $relationships[$jsonRelName] = [static::RELATIONSHIP_LINKS_SELF => true];
184
            }
185
        }
186
187 19
        return $relationships;
188
    }
189
190
    /**
191
     * @inheritdoc
192
     */
193 17
    public function isAddSelfLinkInRelationshipWithData(string $relationshipName): bool
194
    {
195 17
        return false;
196
    }
197
198
    /**
199
     * @return ModelSchemaInfoInterface
200
     */
201 18
    protected function getModelSchemas(): ModelSchemaInfoInterface
202
    {
203 18
        return $this->modelSchemas;
204
    }
205
206
    /**
207
     * @return JsonSchemasInterface
208
     */
209 14
    protected function getJsonSchemas(): JsonSchemasInterface
210
    {
211 14
        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 4
    protected function createRelationshipRepresentationFromData(
224
        ModelInterface $model,
225
        string $modelRelName,
226
        string $jsonRelName
227
    ): array {
228 4
        assert($this->hasProperty($model, $modelRelName) === true);
229 4
        $relationshipData = $model->{$modelRelName};
230 4
        $isPaginatedData  = $relationshipData instanceof PaginatedDataInterface;
231
232 4
        $description = [static::RELATIONSHIP_LINKS_SELF => $this->isAddSelfLinkInRelationshipWithData($jsonRelName)];
233
234 4
        if ($isPaginatedData === false) {
235 2
            $description[static::RELATIONSHIP_DATA] = $relationshipData;
236
237 2
            return $description;
238
        }
239
240 3
        assert($relationshipData instanceof PaginatedDataInterface);
241
242 3
        $description[static::RELATIONSHIP_DATA] = $relationshipData->getData();
243
244 3
        if ($relationshipData->hasMoreItems() === false) {
245 2
            return $description;
246
        }
247
248
        // if we are here then relationship contains paginated data, so we have to add pagination links
249 2
        $offset    = $relationshipData->getOffset();
250 2
        $limit     = $relationshipData->getLimit();
251 2
        $urlPrefix = $this->getRelationshipSelfSubUrl($model, $jsonRelName) . '?';
252
        $buildLink = function (int $offset, int $limit) use ($urlPrefix) : LinkInterface {
253
            $paramsWithPaging = [
254 2
                JsonApiQueryParserInterface::PARAM_PAGING_OFFSET => $offset,
255 2
                JsonApiQueryParserInterface::PARAM_PAGING_LIMIT  => $limit,
256
            ];
257
258 2
            $subUrl = $urlPrefix . http_build_query($paramsWithPaging);
259
260 2
            return $this->getFactory()->createLink(true, $subUrl, false);
261 2
        };
262
263 2
        $nextOffset = $offset + $limit;
264 2
        $nextLimit  = $limit;
265 2
        if ($offset <= 0) {
266 1
            $description[static::RELATIONSHIP_LINKS] = [
267 1
                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 2
        return $description;
285
    }
286
287
    /**
288
     * @param ModelInterface $model
289
     * @param string         $name
290
     *
291
     * @return bool
292
     */
293 19
    private function hasProperty(ModelInterface $model, string $name): bool
294
    {
295 19
        $hasRelationship = property_exists($model, $name);
296
297 19
        return $hasRelationship;
298
    }
299
300
    /**
301
     * @param ModelInterface $model
302
     * @param string         $jsonRelName
303
     *
304
     * @return string
305
     */
306 2
    private function getRelationshipSelfSubUrl(ModelInterface $model, string $jsonRelName): string
307
    {
308 2
        return $this->getSelfSubUrl($model) . '/relationships/' . $jsonRelName;
309
    }
310
}
311