DocumentWriter::getRelationshipsRepresentation()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 1
dl 0
loc 11
ccs 7
cts 7
cp 1
crap 3
rs 9.9
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Neomerx\JsonApi\Representation;
4
5
/**
6
 * Copyright 2015-2020 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Neomerx\JsonApi\Contracts\Parser\IdentifierInterface;
22
use Neomerx\JsonApi\Contracts\Parser\RelationshipDataInterface;
23
use Neomerx\JsonApi\Contracts\Parser\RelationshipInterface;
24
use Neomerx\JsonApi\Contracts\Parser\ResourceInterface;
25
use Neomerx\JsonApi\Contracts\Representation\DocumentWriterInterface;
26
use Neomerx\JsonApi\Contracts\Representation\FieldSetFilterInterface;
27
use Neomerx\JsonApi\Contracts\Schema\DocumentInterface;
28
29
/**
30
 * @package Neomerx\JsonApi
31
 */
32
class DocumentWriter extends BaseWriter implements DocumentWriterInterface
33
{
34
    /**
35
     * @var array
36
     */
37
    private $addedResources;
38
39
    /**
40
     * @inheritdoc
41
     */
42 3
    public function setNullToData(): DocumentWriterInterface
43
    {
44
        // check data has not been added yet
45 3
        \assert(isset($this->data[DocumentInterface::KEYWORD_DATA]) === false);
46 3
        $this->data[DocumentInterface::KEYWORD_DATA] = null;
47
48 3
        return $this;
49
    }
50
51
    /**
52
     * @inheritdoc
53
     */
54 6
    public function addIdentifierToData(IdentifierInterface $identifier): DocumentWriterInterface
55
    {
56 6
        $this->addToData($this->getIdentifierRepresentation($identifier));
57
58 6
        return $this;
59
    }
60
61
    /**
62
     * @inheritdoc
63
     */
64 61
    public function addResourceToData(
65
        ResourceInterface $resource,
66
        FieldSetFilterInterface $filter
67
    ): DocumentWriterInterface {
68 61
        $this->addToData($this->getResourceRepresentation($resource, $filter));
69
70 61
        return $this;
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76 25
    public function addResourceToIncluded(
77
        ResourceInterface $resource,
78
        FieldSetFilterInterface $filter
79
    ): DocumentWriterInterface {
80
        // We track resources only in included section to avoid duplicates there.
81
        // If those resources duplicate main it is not bad because if we remove them
82
        // (and sometimes we would have to rollback and remove some of them if we meet it in the main resources)
83
        // the client app will have to search them not only in included section but in the main as well.
84
        //
85
        // The spec seems to be OK with it.
86
87 25
        if ($this->hasNotBeenAdded($resource) === true) {
88 25
            $this->registerResource($resource);
89 25
            $this->addToIncluded($this->getResourceRepresentation($resource, $filter));
90
        }
91
92 24
        return $this;
93
    }
94
95
    /**
96
     * @inheritdoc
97
     */
98 78
    protected function reset(): void
99
    {
100 78
        parent::reset();
101
102 78
        $this->addedResources = [];
103 78
    }
104
105
    /**
106
     * If full resource has not been added yet either to includes section.
107
     *
108
     * @param ResourceInterface $resource
109
     *
110
     * @return bool
111
     */
112 25
    protected function hasNotBeenAdded(ResourceInterface $resource): bool
113
    {
114 25
        return isset($this->addedResources[$resource->getId()][$resource->getType()]) === false;
115
    }
116
117
    /**
118
     * @param ResourceInterface $resource
119
     *
120
     * @return void
121
     */
122 25
    protected function registerResource(ResourceInterface $resource): void
123
    {
124 25
        \assert($this->hasNotBeenAdded($resource));
125
126 25
        $this->addedResources[$resource->getId()][$resource->getType()] = true;
127 25
    }
128
129
    /**
130
     * @param IdentifierInterface $identifier
131
     *
132
     * @return array
133
     */
134 33
    protected function getIdentifierRepresentation(IdentifierInterface $identifier): array
135
    {
136
        // it's odd not to have actual ID for identifier (which is OK for newly created resource).
137 33
        \assert($identifier->getId() !== null);
138
139 33
        return $identifier->hasIdentifierMeta() === false ? [
140 30
            DocumentInterface::KEYWORD_TYPE => $identifier->getType(),
141 30
            DocumentInterface::KEYWORD_ID   => $identifier->getId(),
142
        ] : [
143 3
            DocumentInterface::KEYWORD_TYPE => $identifier->getType(),
144 3
            DocumentInterface::KEYWORD_ID   => $identifier->getId(),
145 33
            DocumentInterface::KEYWORD_META => $identifier->getIdentifierMeta(),
146
        ];
147
    }
148
149
    /**
150
     * @param ResourceInterface $resource
151
     *
152
     * @return array
153
     */
154 24
    protected function getIdentifierRepresentationFromResource(ResourceInterface $resource): array
155
    {
156 24
        return $resource->hasIdentifierMeta() === false ? [
157 23
            DocumentInterface::KEYWORD_TYPE => $resource->getType(),
158 23
            DocumentInterface::KEYWORD_ID   => $resource->getId(),
159
        ] : [
160 1
            DocumentInterface::KEYWORD_TYPE => $resource->getType(),
161 1
            DocumentInterface::KEYWORD_ID   => $resource->getId(),
162 24
            DocumentInterface::KEYWORD_META => $resource->getIdentifierMeta(),
163
        ];
164
    }
165
166
    /**
167
     * @param iterable $attributes
168
     *
169
     * @return array
170
     */
171 62
    protected function getAttributesRepresentation(iterable $attributes): array
172
    {
173 62
        $representation = [];
174 62
        foreach ($attributes as $name => $value) {
175 62
            $representation[$name] = $value;
176
        }
177
178 62
        return $representation;
179
    }
180
181
    /**
182
     * @param iterable $relationships
183
     *
184
     * @return array
185
     */
186 62
    protected function getRelationshipsRepresentation(iterable $relationships): array
187
    {
188 62
        $representation = [];
189 62
        foreach ($relationships as $name => $relationship) {
190 44
            \assert(\is_string($name) === true && empty($name) === false);
191 44
            \assert($relationship instanceof RelationshipInterface);
192 44
            $representation[$name] = $this->getRelationshipRepresentation($relationship);
193
        }
194
195 62
        return $representation;
196
    }
197
198
    /**
199
     * @param RelationshipInterface $relationship
200
     *
201
     * @return array
202
     */
203 44
    protected function getRelationshipRepresentation(RelationshipInterface $relationship): array
204
    {
205 44
        $representation = [];
206
207 44
        if ($relationship->hasLinks() === true) {
208 27
            $representation[DocumentInterface::KEYWORD_LINKS] =
209 27
                $this->getLinksRepresentation($this->getUrlPrefix(), $relationship->getLinks());
210
        }
211
212 44
        if ($relationship->hasData() === true) {
213 37
            $representation[DocumentInterface::KEYWORD_DATA] = $this->getRelationshipDataRepresentation(
214 37
                $relationship->getData()
215
            );
216
        }
217
218 44
        if ($relationship->hasMeta() === true) {
219 2
            $representation[DocumentInterface::KEYWORD_META] = $relationship->getMeta();
220
        }
221
222 44
        return $representation;
223
    }
224
225
    /**
226
     * @param RelationshipDataInterface $data
227
     *
228
     * @return array|null
229
     */
230 37
    protected function getRelationshipDataRepresentation(RelationshipDataInterface $data): ?array
231
    {
232 37
        if ($data->isResource() === true) {
233 24
            return $this->getIdentifierRepresentationFromResource($data->getResource());
234 33
        } elseif ($data->isIdentifier() === true) {
235 1
            return $this->getIdentifierRepresentation($data->getIdentifier());
236 32
        } elseif ($data->isCollection() === true) {
237 31
            $representation = [];
238 31
            foreach ($data->getIdentifiers() as $identifier) {
239 27
                \assert($identifier instanceof IdentifierInterface);
240 27
                $representation[] = $this->getIdentifierRepresentation($identifier);
241
            }
242
243 31
            return $representation;
244
        }
245
246 5
        \assert($data->isNull() === true);
247
248 5
        return null;
249
    }
250
251
    /**
252
     * @param ResourceInterface       $resource
253
     * @param FieldSetFilterInterface $filter
254
     *
255
     * @return array
256
     *
257
     * @SuppressWarnings(PHPMD.IfStatementAssignment)
258
     */
259 62
    protected function getResourceRepresentation(ResourceInterface $resource, FieldSetFilterInterface $filter): array
260
    {
261
        $representation = [
262 62
            DocumentInterface::KEYWORD_TYPE => $resource->getType(),
263
        ];
264
265 62
        if (($index = $resource->getId()) !== null) {
266 61
            $representation[DocumentInterface::KEYWORD_ID] = $index;
267
        }
268
269 62
        $attributes = $this->getAttributesRepresentation($filter->getAttributes($resource));
270 62
        if (empty($attributes) === false) {
271 62
            \assert(
272 62
                \json_encode($attributes) !== false,
273 62
                'Attributes for resource type `' . $resource->getType() .
274 62
                '` cannot be converted into JSON. Please check its Schema returns valid data.'
275
            );
276 62
            $representation[DocumentInterface::KEYWORD_ATTRIBUTES] = $attributes;
277
        }
278
279 62
        $relationships = $this->getRelationshipsRepresentation($filter->getRelationships($resource));
280 62
        if (empty($relationships) === false) {
281 44
            \assert(
282 44
                \json_encode($relationships) !== false,
283 44
                'Relationships for resource type `' . $resource->getType() .
284 44
                '` cannot be converted into JSON. Please check its Schema returns valid data.'
285
            );
286 44
            $representation[DocumentInterface::KEYWORD_RELATIONSHIPS] = $relationships;
287
        }
288
289 62
        if ($resource->hasLinks() === true) {
290 61
            $links = $this->getLinksRepresentation($this->getUrlPrefix(), $resource->getLinks());
291 61
            \assert(
292 61
                \json_encode($links) !== false,
293 61
                'Links for resource type `' . $resource->getType() .
294 61
                '` cannot be converted into JSON. Please check its Schema returns valid data.'
295
            );
296 61
            $representation[DocumentInterface::KEYWORD_LINKS] = $links;
297
        }
298
299 62
        if ($resource->hasResourceMeta() === true) {
300 1
            $meta = $resource->getResourceMeta();
301 1
            \assert(
302 1
                \json_encode($meta) !== false,
303 1
                'Meta for resource type `' . $resource->getType() .
304 1
                '` cannot be converted into JSON. Please check its Schema returns valid data.'
305
            );
306 1
            $representation[DocumentInterface::KEYWORD_META] = $meta;
307
        }
308
309 62
        return $representation;
310
    }
311
312
    /**
313
     * @param array $representation
314
     *
315
     * @return void
316
     */
317 67
    private function addToData(array $representation): void
318
    {
319 67
        if ($this->isDataAnArray() === true) {
320 18
            $this->data[DocumentInterface::KEYWORD_DATA][] = $representation;
321
322 18
            return;
323
        }
324
325
        // check data has not been added yet
326 52
        \assert(\array_key_exists(DocumentInterface::KEYWORD_DATA, $this->data) === false);
327 52
        $this->data[DocumentInterface::KEYWORD_DATA] = $representation;
328 52
    }
329
330
    /**
331
     * @param array $representation
332
     *
333
     * @return void
334
     */
335 24
    private function addToIncluded(array $representation): void
336
    {
337 24
        $this->data[DocumentInterface::KEYWORD_INCLUDED][] = $representation;
338 24
    }
339
}
340