Completed
Push — develop ( 6d3477...b4cdc6 )
by Neomerx
05:19
created

Schema::getJsonSchemes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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 18
    public function __construct(FactoryInterface $factory, ModelSchemeInfoInterface $modelSchemes)
44
    {
45
        /** @noinspection PhpUndefinedFieldInspection */
46 18
        $this->resourceType = static::TYPE;
47
48 18
        parent::__construct($factory);
49
50 18
        $this->modelSchemes = $modelSchemes;
51
    }
52
53
    /**
54
     * @inheritdoc
55
     */
56 6
    public static function getAttributeMapping(string $jsonName): string
57
    {
58 6
        return static::getMappings()[static::SCHEMA_ATTRIBUTES][$jsonName];
59
    }
60
61
    /**
62
     * @inheritdoc
63
     */
64 8
    public static function getRelationshipMapping(string $jsonName): string
65
    {
66 8
        return static::getMappings()[static::SCHEMA_RELATIONSHIPS][$jsonName];
67
    }
68
69
    /**
70
     * @inheritdoc
71
     */
72 7 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 7
        $mappings = static::getMappings();
75
76
        return
77 7
            array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true &&
78 7
            array_key_exists($jsonName, $mappings[static::SCHEMA_ATTRIBUTES]) === true;
79
    }
80
81
    /**
82
     * @inheritdoc
83
     */
84 5 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 5
        $mappings = static::getMappings();
87
88
        return
89 5
            array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true &&
90 5
            array_key_exists($jsonName, $mappings[static::SCHEMA_RELATIONSHIPS]) === true;
91
    }
92
93
    /**
94
     * @inheritdoc
95
     */
96 16
    public function getAttributes($model)
97
    {
98 16
        $attributes = [];
99 16
        $mappings   = static::getMappings();
100 16
        if (array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true) {
101 16
            $attrMappings = $mappings[static::SCHEMA_ATTRIBUTES];
102 16
            foreach ($attrMappings as $jsonAttrName => $modelAttrName) {
103 16
                $attributes[$jsonAttrName] = isset($model->{$modelAttrName}) === true ? $model->{$modelAttrName} : null;
104
            }
105
        }
106
107 16
        return $attributes;
108
    }
109
110
    /** @noinspection PhpMissingParentCallCommonInspection
111
     * @inheritdoc
112
     *
113
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
114
     */
115 17
    public function getRelationships($model, $isPrimary, array $includeRelationships)
116
    {
117 17
        $modelClass    = get_class($model);
118 17
        $relationships = [];
119 17
        $mappings      = static::getMappings();
120 17
        if (array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true) {
121 17
            $relMappings = $mappings[static::SCHEMA_RELATIONSHIPS];
122 17
            foreach ($relMappings as $jsonRelName => $modelRelName) {
123 17
                $isRelToBeIncluded = array_key_exists($jsonRelName, $includeRelationships) === true;
124
125 17
                $hasRelData = $this->hasRelationship($model, $modelRelName);
126 17
                $relType    = $this->getModelSchemes()->getRelationshipType($modelClass, $modelRelName);
127
128
                // there is a case for `to-1` relationship when we can return identity resource (type + id)
129 17
                if ($relType === RelationshipTypes::BELONGS_TO) {
130 16
                    if ($isRelToBeIncluded === true && $hasRelData === true) {
131 2
                        $relationships[$jsonRelName] = [static::DATA => $model->{$modelRelName}];
132
                    } else {
133 16
                        $relationships[$jsonRelName] =
134 16
                            $this->getRelationshipIdentityRepresentation($model, $jsonRelName, $modelRelName);
135
                    }
136 16
                    continue;
137
                }
138
139
                // if our storage do not have any data for this relationship or relationship would not
140
                // be included we return it as link
141 17
                $isShowAsLink = false;
142 17
                if ($hasRelData === false ||
143 17
                    ($isRelToBeIncluded === false && $relType !== RelationshipTypes::BELONGS_TO)
144
                ) {
145 17
                    $isShowAsLink = true;
146
                }
147
148 17
                if ($isShowAsLink === true) {
149 17
                    $relationships[$jsonRelName] = $this->getRelationshipLinkRepresentation($model, $jsonRelName);
150 17
                    continue;
151
                }
152
153 2
                $relUri  = $this->getRelationshipSelfUrl($model, $jsonRelName);
154 2
                $relationships[$jsonRelName] = $this->getRelationshipDescription($model->{$modelRelName}, $relUri);
155
            }
156
        }
157
158 17
        return $relationships;
159
    }
160
161
    /**
162
     * @return ModelSchemeInfoInterface
163
     */
164 17
    protected function getModelSchemes(): ModelSchemeInfoInterface
165
    {
166 17
        return $this->modelSchemes;
167
    }
168
169
    /**
170
     * @param PaginatedDataInterface $data
171
     * @param string                 $uri
172
     *
173
     * @return array
174
     */
175 2
    protected function getRelationshipDescription(PaginatedDataInterface $data, string $uri): array
176
    {
177 2
        if ($data->hasMoreItems() === false) {
178 2
            return [static::DATA => $data->getData()];
179
        }
180
181 1
        $buildUrl = function ($offset) use ($data, $uri) {
182
            $paramsWithPaging = [
183 1
                PaginationStrategyInterface::PARAM_PAGING_SKIP => $offset,
184 1
                PaginationStrategyInterface::PARAM_PAGING_SIZE => $data->getLimit(),
185
            ];
186 1
            $fullUrl = $uri . '?' . http_build_query($paramsWithPaging);
187
188 1
            return $fullUrl;
189 1
        };
190
191 1
        $links = [];
192
193
        // It looks like relationship can only hold first data rows so we might need `next` link but never `prev`
194
195 1
        if ($data->hasMoreItems() === true) {
196 1
            $offset = $data->getOffset() + $data->getLimit();
197 1
            $links[DocumentInterface::KEYWORD_NEXT] = $this->createLink($buildUrl($offset));
198
        }
199
200
        return [
201 1
            static::DATA  => $data->getData(),
202 1
            static::LINKS => $links,
203
        ];
204
    }
205
206
    /**
207
     * @param mixed  $model
208
     * @param string $jsonRelationship
209
     * @param string $modelRelationship
210
     *
211
     * @return array
212
     */
213 16
    protected function getRelationshipIdentityRepresentation(
214
        $model,
215
        string $jsonRelationship,
216
        string $modelRelationship
217
    ): array {
218 16
        $identity = $this->getIdentity($model, $modelRelationship);
219
220
        return [
221 16
            static::DATA  => $identity,
222 16
            static::LINKS => $this->getRelationshipLinks($model, $jsonRelationship),
223
        ];
224
    }
225
226
    /**
227
     * @param mixed  $model
228
     * @param string $jsonRelationship
229
     *
230
     * @return array
231
     */
232 17
    protected function getRelationshipLinkRepresentation($model, string $jsonRelationship): array
233
    {
234
        return [
235 17
            static::LINKS     => $this->getRelationshipLinks($model, $jsonRelationship),
236 17
            static::SHOW_DATA => false,
237
        ];
238
    }
239
240
    /**
241
     * @param mixed  $model
242
     * @param string $jsonRelationship
243
     *
244
     * @return array
245
     */
246 17
    protected function getRelationshipLinks($model, string $jsonRelationship): array
247
    {
248 17
        $links = [];
249 17
        if ($this->showSelfInRelationship($jsonRelationship) === true) {
250 17
            $links[LinkInterface::SELF] = $this->getRelationshipSelfLink($model, $jsonRelationship);
251
        }
252 17
        if ($this->showRelatedInRelationship($jsonRelationship) === true) {
253 5
            $links[LinkInterface::RELATED] = $this->getRelationshipRelatedLink($model, $jsonRelationship);
254
        }
255
256 17
        return $links;
257
    }
258
259
    /**
260
     * @param mixed  $model
261
     * @param string $modelRelName
262
     *
263
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|object.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
264
     */
265 16
    protected function getIdentity($model, string $modelRelName)
266
    {
267 16
        $schema = $this->getModelSchemes();
268
269 16
        $class          = get_class($model);
270 16
        $fkName         = $schema->getForeignKey($class, $modelRelName);
271 16
        $reversePkValue = $model->{$fkName};
272 16
        if ($reversePkValue === null) {
273 3
            return null;
274
        }
275
276 15
        $reverseClass  = $schema->getReverseModelClass($class, $modelRelName);
277 15
        $reversePkName = $schema->getPrimaryKey($reverseClass);
278
279 15
        $model = new $reverseClass;
280 15
        $model->{$reversePkName} = $reversePkValue;
281
282 15
        return $model;
283
    }
284
285
    /**
286
     * Gets excludes from default 'show `self` link in relationships' rule.
287
     *
288
     * @return array Should be in ['jsonRelationship' => true] format.
289
     */
290 7
    protected function getExcludesFromDefaultShowSelfLinkInRelationships(): array
291
    {
292 7
        return [];
293
    }
294
295
    /**
296
     * Gets excludes from default 'show `related` link in relationships' rule.
297
     *
298
     * @return array Should be in ['jsonRelationship' => true] format.
299
     */
300 7
    protected function getExcludesFromDefaultShowRelatedLinkInRelationships(): array
301
    {
302 7
        return [];
303
    }
304
305
    /**
306
     * If `self` link should be shown in relationships by default.
307
     *
308
     * @return bool
309
     */
310 17
    protected function isShowSelfLinkInRelationships(): bool
311
    {
312 17
        return true;
313
    }
314
315
    /**
316
     * If `related` link should be shown in relationships by default.
317
     *
318
     * @return bool
319
     */
320 17
    protected function isShowRelatedLinkInRelationships(): bool
321
    {
322 17
        return true;
323
    }
324
325
    /**
326
     * @param mixed  $model
327
     * @param string $name
328
     *
329
     * @return bool
330
     */
331 17
    private function hasRelationship($model, string $name): bool
332
    {
333 17
        $hasRelationship = property_exists($model, $name);
334
335 17
        return $hasRelationship;
336
    }
337
338
    /**
339
     * @param string $jsonRelationship
340
     *
341
     * @return bool
342
     */
343 17
    private function showSelfInRelationship(string $jsonRelationship): bool
344
    {
345 17
        $default = $this->isShowSelfLinkInRelationships();
346 17
        $result  = isset($this->getExcludesFromDefaultShowSelfLinkInRelationships()[$jsonRelationship]) === true ?
347 17
            !$default : $default;
348
349 17
        return $result;
350
    }
351
352
    /**
353
     * @param string $jsonRelationship
354
     *
355
     * @return bool
356
     */
357 17
    private function showRelatedInRelationship(string $jsonRelationship): bool
358
    {
359 17
        $default = $this->isShowRelatedLinkInRelationships();
360 17
        $result  = isset($this->getExcludesFromDefaultShowRelatedLinkInRelationships()[$jsonRelationship]) === true ?
361 17
            !$default : $default;
362
363 17
        return $result;
364
    }
365
}
366