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