Completed
Push — develop ( b4cdc6...ec64f2 )
by Neomerx
04:50
created

Schema::getRelationshipIdentityRepresentation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 9
nc 1
nop 3
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 17
                $isShowAsLink = false;
129
130
                // there is a case for `to-1` relationship when we can return identity resource (type + id)
131 17
                if ($relType === RelationshipTypes::BELONGS_TO) {
132 16
                    if ($isRelToBeIncluded === true && $hasRelData === true) {
133 2
                        $relationships[$jsonRelName] = [static::DATA => $model->{$modelRelName}];
134 2
                        continue;
135
                    } else {
136 16
                        $schema = $this->getModelSchemes();
137
138 16
                        $class  = get_class($model);
139 16
                        $fkName = $schema->getForeignKey($class, $modelRelName);
140
141 16
                        $isShowAsLink = property_exists($model, $fkName) === false;
142
143 16
                        if ($isShowAsLink === false) {
144
145
                            // show as identity (type, id)
146
147 16
                            $identity       = null;
148 16
                            $reversePkValue = $model->{$fkName};
149 16
                            if ($reversePkValue !== null) {
150 15
                                $reverseClass  = $schema->getReverseModelClass($class, $modelRelName);
151 15
                                $reversePkName = $schema->getPrimaryKey($reverseClass);
152
153 15
                                $identity                   = new $reverseClass;
154 15
                                $identity->{$reversePkName} = $reversePkValue;
155
                            }
156
157 16
                            $relationships[$jsonRelName] = [
158 16
                                static::DATA  => $identity,
159 16
                                static::LINKS => $this->getRelationshipLinks($model, $jsonRelName),
160
                            ];
161 16
                            continue;
162
                        }
163
164
                        // the relationship will be shown as a link
165
                    }
166
                }
167
168
                // if our storage do not have any data for this relationship or relationship would not
169
                // be included we return it as link
170 17
                if ($hasRelData === false ||
171 17
                    ($isRelToBeIncluded === false && $relType !== RelationshipTypes::BELONGS_TO)
172
                ) {
173 17
                    $isShowAsLink = true;
174
                }
175
176 17
                if ($isShowAsLink === true) {
177 17
                    $relationships[$jsonRelName] = $this->getRelationshipLinkRepresentation($model, $jsonRelName);
178 17
                    continue;
179
                }
180
181
                // if we are here this is a `to-Many` relationship and we have to show data and got the data
182
183 2
                $relUri                      = $this->getRelationshipSelfUrl($model, $jsonRelName);
184 2
                $relationships[$jsonRelName] = $this->getRelationshipDescription($model->{$modelRelName}, $relUri);
185
            }
186
        }
187
188 17
        return $relationships;
189
    }
190
191
    /**
192
     * @return ModelSchemeInfoInterface
193
     */
194 17
    protected function getModelSchemes(): ModelSchemeInfoInterface
195
    {
196 17
        return $this->modelSchemes;
197
    }
198
199
    /**
200
     * @param PaginatedDataInterface $data
201
     * @param string                 $uri
202
     *
203
     * @return array
204
     */
205 2
    protected function getRelationshipDescription(PaginatedDataInterface $data, string $uri): array
206
    {
207 2
        if ($data->hasMoreItems() === false) {
208 2
            return [static::DATA => $data->getData()];
209
        }
210
211 1
        $buildUrl = function ($offset) use ($data, $uri) {
212
            $paramsWithPaging = [
213 1
                PaginationStrategyInterface::PARAM_PAGING_SKIP => $offset,
214 1
                PaginationStrategyInterface::PARAM_PAGING_SIZE => $data->getLimit(),
215
            ];
216 1
            $fullUrl          = $uri . '?' . http_build_query($paramsWithPaging);
217
218 1
            return $fullUrl;
219 1
        };
220
221 1
        $links = [];
222
223
        // It looks like relationship can only hold first data rows so we might need `next` link but never `prev`
224
225 1
        if ($data->hasMoreItems() === true) {
226 1
            $offset                                 = $data->getOffset() + $data->getLimit();
227 1
            $links[DocumentInterface::KEYWORD_NEXT] = $this->createLink($buildUrl($offset));
228
        }
229
230
        return [
231 1
            static::DATA  => $data->getData(),
232 1
            static::LINKS => $links,
233
        ];
234
    }
235
236
    /**
237
     * @param mixed  $model
238
     * @param string $jsonRelationship
239
     *
240
     * @return array
241
     */
242 17
    protected function getRelationshipLinkRepresentation($model, string $jsonRelationship): array
243
    {
244
        return [
245 17
            static::LINKS     => $this->getRelationshipLinks($model, $jsonRelationship),
246 17
            static::SHOW_DATA => false,
247
        ];
248
    }
249
250
    /**
251
     * @param mixed  $model
252
     * @param string $jsonRelationship
253
     *
254
     * @return array
255
     */
256 17
    protected function getRelationshipLinks($model, string $jsonRelationship): array
257
    {
258 17
        $links = [];
259 17
        if ($this->showSelfInRelationship($jsonRelationship) === true) {
260 17
            $links[LinkInterface::SELF] = $this->getRelationshipSelfLink($model, $jsonRelationship);
261
        }
262 17
        if ($this->showRelatedInRelationship($jsonRelationship) === true) {
263 5
            $links[LinkInterface::RELATED] = $this->getRelationshipRelatedLink($model, $jsonRelationship);
264
        }
265
266 17
        return $links;
267
    }
268
269
    /**
270
     * Gets excludes from default 'show `self` link in relationships' rule.
271
     *
272
     * @return array Should be in ['jsonRelationship' => true] format.
273
     */
274 7
    protected function getExcludesFromDefaultShowSelfLinkInRelationships(): array
275
    {
276 7
        return [];
277
    }
278
279
    /**
280
     * Gets excludes from default 'show `related` link in relationships' rule.
281
     *
282
     * @return array Should be in ['jsonRelationship' => true] format.
283
     */
284 7
    protected function getExcludesFromDefaultShowRelatedLinkInRelationships(): array
285
    {
286 7
        return [];
287
    }
288
289
    /**
290
     * If `self` link should be shown in relationships by default.
291
     *
292
     * @return bool
293
     */
294 17
    protected function isShowSelfLinkInRelationships(): bool
295
    {
296 17
        return true;
297
    }
298
299
    /**
300
     * If `related` link should be shown in relationships by default.
301
     *
302
     * @return bool
303
     */
304 17
    protected function isShowRelatedLinkInRelationships(): bool
305
    {
306 17
        return true;
307
    }
308
309
    /**
310
     * @param mixed  $model
311
     * @param string $name
312
     *
313
     * @return bool
314
     */
315 17
    private function hasRelationship($model, string $name): bool
316
    {
317 17
        $hasRelationship = property_exists($model, $name);
318
319 17
        return $hasRelationship;
320
    }
321
322
    /**
323
     * @param string $jsonRelationship
324
     *
325
     * @return bool
326
     */
327 17
    private function showSelfInRelationship(string $jsonRelationship): bool
328
    {
329 17
        $default = $this->isShowSelfLinkInRelationships();
330 17
        $result  = isset($this->getExcludesFromDefaultShowSelfLinkInRelationships()[$jsonRelationship]) === true ?
331 17
            !$default : $default;
332
333 17
        return $result;
334
    }
335
336
    /**
337
     * @param string $jsonRelationship
338
     *
339
     * @return bool
340
     */
341 17
    private function showRelatedInRelationship(string $jsonRelationship): bool
342
    {
343 17
        $default = $this->isShowRelatedLinkInRelationships();
344 17
        $result  = isset($this->getExcludesFromDefaultShowRelatedLinkInRelationships()[$jsonRelationship]) === true ?
345 17
            !$default : $default;
346
347 17
        return $result;
348
    }
349
}
350