Completed
Push — master ( dcfc04...697864 )
by Neomerx
04:30
created

Schema::hasRelationshipMapping()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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
     * @var array|null
49
     */
50
    private $attributesMapping;
51
52
    /**
53
     * @var array|null
54
     */
55
    private $relationshipsMapping;
56
57
    /**
58
     * @param FactoryInterface         $factory
59
     * @param JsonSchemasInterface     $jsonSchemas
60
     * @param ModelSchemaInfoInterface $modelSchemas
61
     */
62 28
    public function __construct(
63
        FactoryInterface $factory,
64
        JsonSchemasInterface $jsonSchemas,
65
        ModelSchemaInfoInterface $modelSchemas
66
    ) {
67 28
        assert(empty(static::TYPE) === false);
68 28
        assert(empty(static::MODEL) === false);
69
70 28
        parent::__construct($factory);
71
72 28
        $this->modelSchemas = $modelSchemas;
73 28
        $this->jsonSchemas  = $jsonSchemas;
74
75 28
        $this->attributesMapping    = null;
76 28
        $this->relationshipsMapping = null;
77
    }
78
79
    /**
80
     * @inheritdoc
81
     */
82 19
    public function getType(): string
83
    {
84 19
        return static::TYPE;
85
    }
86
87
    /**
88
     * @inheritdoc
89
     */
90 14
    public static function getAttributeMapping(string $jsonName): string
91
    {
92 14
        return static::getMappings()[static::SCHEMA_ATTRIBUTES][$jsonName];
93
    }
94
95
    /**
96
     * @inheritdoc
97
     */
98 12
    public static function getRelationshipMapping(string $jsonName): string
99
    {
100 12
        return static::getMappings()[static::SCHEMA_RELATIONSHIPS][$jsonName];
101
    }
102
103
    /**
104
     * @inheritdoc
105
     */
106 21
    public static function hasAttributeMapping(string $jsonName): bool
107
    {
108 21
        $mappings = static::getMappings();
109
110
        return
111 21
            array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true &&
112 21
            array_key_exists($jsonName, $mappings[static::SCHEMA_ATTRIBUTES]) === true;
113
    }
114
115
    /**
116
     * @inheritdoc
117
     */
118 15
    public static function hasRelationshipMapping(string $jsonName): bool
119
    {
120 15
        $mappings = static::getMappings();
121
122
        return
123 15
            array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true &&
124 15
            array_key_exists($jsonName, $mappings[static::SCHEMA_RELATIONSHIPS]) === true;
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130 16
    public function getAttributes($model): iterable
131
    {
132 16
        foreach ($this->getAttributesMapping() as $jsonAttrName => $modelAttrName) {
133 16
            if ($this->hasProperty($model, $modelAttrName) === true) {
134 16
                yield $jsonAttrName => $model->{$modelAttrName};
135
            }
136
        }
137
    }
138
139
    /**
140
     * @inheritdoc
141
     *
142
     * @SuppressWarnings(PHPMD.ElseExpression)
143
     */
144 19
    public function getRelationships($model): iterable
145
    {
146 19
        assert($model instanceof ModelInterface);
147
148 19
        foreach ($this->getRelationshipsMapping() as $jsonRelName => [$modelRelName, $belongsToFkName, $reverseType]) {
149
            // if model has relationship data then use it
150 19
            if ($this->hasProperty($model, $modelRelName) === true) {
151 4
                yield $jsonRelName => $this->createRelationshipRepresentationFromData(
152 4
                    $model,
153 4
                    $modelRelName,
0 ignored issues
show
Bug introduced by
The variable $modelRelName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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
            if ($belongsToFkName !== null && $this->hasProperty($model, $belongsToFkName) === true) {
161 15
                $reverseIndex = $model->{$belongsToFkName};
0 ignored issues
show
Bug introduced by
The variable $belongsToFkName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
162 15
                $identifier   = $reverseIndex === null ?
163 15
                    null : new Identifier($reverseIndex, $reverseType, false, null);
0 ignored issues
show
Bug introduced by
The variable $reverseType does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
164
165
                yield $jsonRelName => [
166 15
                    static::RELATIONSHIP_DATA       => $identifier,
167 15
                    static::RELATIONSHIP_LINKS_SELF => $this->isAddSelfLinkInRelationshipWithData($jsonRelName),
168
                ];
169 15
                continue;
170
            }
171
172
            // if we are here it's nothing left but show relationship as a link
173 18
            yield $jsonRelName => [static::RELATIONSHIP_LINKS_SELF => true];
174
        }
175
    }
176
177
    /**
178
     * @inheritdoc
179
     */
180 17
    public function isAddSelfLinkInRelationshipWithData(string $relationshipName): bool
181
    {
182 17
        return false;
183
    }
184
185
    /**
186
     * @return ModelSchemaInfoInterface
187
     */
188 19
    protected function getModelSchemas(): ModelSchemaInfoInterface
189
    {
190 19
        return $this->modelSchemas;
191
    }
192
193
    /**
194
     * @return JsonSchemasInterface
195
     */
196 17
    protected function getJsonSchemas(): JsonSchemasInterface
197
    {
198 17
        return $this->jsonSchemas;
199
    }
200
201
    /**
202
     * @param ModelInterface $model
203
     * @param string         $modelRelName
204
     * @param string         $jsonRelName
205
     *
206
     * @return array
207
     *
208
     * @SuppressWarnings(PHPMD.ElseExpression)
209
     */
210 4
    protected function createRelationshipRepresentationFromData(
211
        ModelInterface $model,
212
        string $modelRelName,
213
        string $jsonRelName
214
    ): array {
215 4
        assert($this->hasProperty($model, $modelRelName) === true);
216 4
        $relationshipData = $model->{$modelRelName};
217 4
        $isPaginatedData  = $relationshipData instanceof PaginatedDataInterface;
218
219 4
        $description = [static::RELATIONSHIP_LINKS_SELF => $this->isAddSelfLinkInRelationshipWithData($jsonRelName)];
220
221 4
        if ($isPaginatedData === false) {
222 2
            $description[static::RELATIONSHIP_DATA] = $relationshipData;
223
224 2
            return $description;
225
        }
226
227 3
        assert($relationshipData instanceof PaginatedDataInterface);
228
229 3
        $description[static::RELATIONSHIP_DATA] = $relationshipData->getData();
230
231 3
        if ($relationshipData->hasMoreItems() === false) {
232 2
            return $description;
233
        }
234
235
        // if we are here then relationship contains paginated data, so we have to add pagination links
236 2
        $offset    = $relationshipData->getOffset();
237 2
        $limit     = $relationshipData->getLimit();
238 2
        $urlPrefix = $this->getRelationshipSelfSubUrl($model, $jsonRelName) . '?';
239
        $buildLink = function (int $offset, int $limit) use ($urlPrefix) : LinkInterface {
240
            $paramsWithPaging = [
241 2
                JsonApiQueryParserInterface::PARAM_PAGING_OFFSET => $offset,
242 2
                JsonApiQueryParserInterface::PARAM_PAGING_LIMIT  => $limit,
243
            ];
244
245 2
            $subUrl = $urlPrefix . http_build_query($paramsWithPaging);
246
247 2
            return $this->getFactory()->createLink(true, $subUrl, false);
248 2
        };
249
250 2
        $nextOffset = $offset + $limit;
251 2
        $nextLimit  = $limit;
252 2
        if ($offset <= 0) {
253 1
            $description[static::RELATIONSHIP_LINKS] = [
254 1
                DocumentInterface::KEYWORD_NEXT => $buildLink($nextOffset, $nextLimit),
255
            ];
256
        } else {
257 1
            $prevOffset = $offset - $limit;
258 1
            if ($prevOffset < 0) {
259
                // set offset 0 and decrease limit
260 1
                $prevLimit  = $limit + $prevOffset;
261 1
                $prevOffset = 0;
262
            } else {
263 1
                $prevLimit = $limit;
264
            }
265 1
            $description[static::RELATIONSHIP_LINKS] = [
266 1
                DocumentInterface::KEYWORD_PREV => $buildLink($prevOffset, $prevLimit),
267 1
                DocumentInterface::KEYWORD_NEXT => $buildLink($nextOffset, $nextLimit),
268
            ];
269
        }
270
271 2
        return $description;
272
    }
273
274
    /**
275
     * @return array
276
     */
277 16
    private function getAttributesMapping(): array
278
    {
279 16
        if ($this->attributesMapping !== null) {
280 10
            return $this->attributesMapping;
281
        }
282
283 16
        $attributesMapping = static::getMappings()[static::SCHEMA_ATTRIBUTES] ?? [];
284
285
        // `id` is a `special` attribute and cannot be included in JSON API resource
286 16
        unset($attributesMapping[static::RESOURCE_ID]);
287
288 16
        $this->attributesMapping = $attributesMapping;
289
290 16
        return $this->attributesMapping;
291
    }
292
293
    /**
294
     * @return array
295
     */
296 19
    private function getRelationshipsMapping(): array
297
    {
298 19
        if ($this->relationshipsMapping !== null) {
299 12
            return $this->relationshipsMapping;
300
        }
301
302 19
        $relationshipsMapping = [];
303 19
        foreach (static::getMappings()[static::SCHEMA_RELATIONSHIPS] ?? [] as $jsonRelName => $modelRelName) {
304 19
            $belongsToFkName = null;
305 19
            $reverseJsonType = null;
306
307 19
            $relType = $this->getModelSchemas()->getRelationshipType(static::MODEL, $modelRelName);
308 19
            if ($relType === RelationshipTypes::BELONGS_TO) {
309 17
                $belongsToFkName = $this->getModelSchemas()->getForeignKey(static::MODEL, $modelRelName);
310 17
                $reverseSchema   = $this->getJsonSchemas()
311 17
                    ->getRelationshipSchema(static::class, $jsonRelName);
312 17
                $reverseJsonType = $reverseSchema->getType();
313
            }
314
315 19
            $relationshipsMapping[$jsonRelName] = [$modelRelName, $belongsToFkName, $reverseJsonType];
316
        }
317
318 19
        $this->relationshipsMapping = $relationshipsMapping;
319
320 19
        return $this->relationshipsMapping;
321
    }
322
323
    /**
324
     * @param ModelInterface $model
325
     * @param string         $name
326
     *
327
     * @return bool
328
     */
329 19
    private function hasProperty(ModelInterface $model, string $name): bool
330
    {
331 19
        $hasRelationship = property_exists($model, $name);
332
333 19
        return $hasRelationship;
334
    }
335
336
    /**
337
     * @param ModelInterface $model
338
     * @param string         $jsonRelName
339
     *
340
     * @return string
341
     */
342 2
    private function getRelationshipSelfSubUrl(ModelInterface $model, string $jsonRelName): string
343
    {
344 2
        return $this->getSelfSubUrl($model) . '/relationships/' . $jsonRelName;
345
    }
346
}
347