Completed
Branch master (9645b9)
by Neomerx
02:19
created

DocumentWriter::hasNotBeenAdded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Neomerx\JsonApi\Representation;
4
5
/**
6
 * Copyright 2015-2019 [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 56
    public function addResourceToData(
65
        ResourceInterface $resource,
66
        FieldSetFilterInterface $filter
67
    ): DocumentWriterInterface {
68 56
        $this->addToData($this->getResourceRepresentation($resource, $filter));
69
70 56
        return $this;
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76 21
    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 21
        if ($this->hasNotBeenAdded($resource) === true) {
88 21
            $this->registerResource($resource);
89 21
            $this->addToIncluded($this->getResourceRepresentation($resource, $filter));
90
        }
91
92 20
        return $this;
93
    }
94
95
    /**
96
     * @inheritdoc
97
     */
98 73
    protected function reset(): void
99
    {
100 73
        parent::reset();
101
102 73
        $this->addedResources = [];
103 73
    }
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 21
    protected function hasNotBeenAdded(ResourceInterface $resource): bool
113
    {
114 21
        return isset($this->addedResources[$resource->getId()][$resource->getType()]) === false;
115
    }
116
117
    /**
118
     * @param ResourceInterface $resource
119
     *
120
     * @return void
121
     */
122 21
    protected function registerResource(ResourceInterface $resource): void
123
    {
124 21
        assert($this->hasNotBeenAdded($resource));
125
126 21
        $this->addedResources[$resource->getId()][$resource->getType()] = true;
127 21
    }
128
129
    /**
130
     * @param IdentifierInterface $identifier
131
     *
132
     * @return array
133
     */
134 30
    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 30
        assert($identifier->getId() !== null);
138
139 30
        return $identifier->hasIdentifierMeta() === false ? [
140 27
            DocumentInterface::KEYWORD_TYPE => $identifier->getType(),
141 27
            DocumentInterface::KEYWORD_ID   => $identifier->getId(),
142
        ] : [
143 3
            DocumentInterface::KEYWORD_TYPE => $identifier->getType(),
144 3
            DocumentInterface::KEYWORD_ID   => $identifier->getId(),
145 30
            DocumentInterface::KEYWORD_META => $identifier->getIdentifierMeta(),
146
        ];
147
    }
148
149
    /**
150
     * @param ResourceInterface $resource
151
     *
152
     * @return array
153
     */
154 20
    protected function getIdentifierRepresentationFromResource(ResourceInterface $resource): array
155
    {
156 20
        return $resource->hasIdentifierMeta() === false ? [
157 19
            DocumentInterface::KEYWORD_TYPE => $resource->getType(),
158 19
            DocumentInterface::KEYWORD_ID   => $resource->getId(),
159
        ] : [
160 1
            DocumentInterface::KEYWORD_TYPE => $resource->getType(),
161 1
            DocumentInterface::KEYWORD_ID   => $resource->getId(),
162 20
            DocumentInterface::KEYWORD_META => $resource->getIdentifierMeta(),
163
        ];
164
    }
165
166
    /**
167
     * @param iterable $attributes
168
     *
169
     * @return array
170
     */
171 57
    protected function getAttributesRepresentation(iterable $attributes): array
172
    {
173 57
        $representation = [];
174 57
        foreach ($attributes as $name => $value) {
175 57
            $representation[$name] = $value;
176
        }
177
178 57
        return $representation;
179
    }
180
181
    /**
182
     * @param iterable $relationships
183
     *
184
     * @return array
185
     */
186 57
    protected function getRelationshipsRepresentation(iterable $relationships): array
187
    {
188 57
        $representation = [];
189 57
        foreach ($relationships as $name => $relationship) {
190 39
            assert(is_string($name) === true && empty($name) === false);
191 39
            assert($relationship instanceof RelationshipInterface);
192 39
            $representation[$name] = $this->getRelationshipRepresentation($relationship);
193
        }
194
195 57
        return $representation;
196
    }
197
198
    /**
199
     * @param RelationshipInterface $relationship
200
     *
201
     * @return array
202
     */
203 39
    protected function getRelationshipRepresentation(RelationshipInterface $relationship): array
204
    {
205 39
        $representation = [];
206
207 39
        if ($relationship->hasLinks() === true) {
208 22
            $representation[DocumentInterface::KEYWORD_LINKS] =
209 22
                $this->getLinksRepresentation($this->getUrlPrefix(), $relationship->getLinks());
210
        }
211
212 39
        if ($relationship->hasData() === true) {
213 32
            $representation[DocumentInterface::KEYWORD_DATA] = $this->getRelationshipDataRepresentation(
214 32
                $relationship->getData()
215
            );
216
        }
217
218 39
        if ($relationship->hasMeta() === true) {
219 1
            $representation[DocumentInterface::KEYWORD_META] = $relationship->getMeta();
220
        }
221
222 39
        return $representation;
223
    }
224
225
    /**
226
     * @param RelationshipDataInterface $data
227
     *
228
     * @return array|null
229
     */
230 32
    protected function getRelationshipDataRepresentation(RelationshipDataInterface $data): ?array
231
    {
232 32
        if ($data->isResource() === true) {
233 20
            return $this->getIdentifierRepresentationFromResource($data->getResource());
234 29
        } elseif ($data->isIdentifier() === true) {
235 1
            return $this->getIdentifierRepresentation($data->getIdentifier());
236 28
        } elseif ($data->isCollection() === true) {
237 27
            $representation = [];
238 27
            foreach ($data->getIdentifiers() as $identifier) {
239 24
                assert($identifier instanceof IdentifierInterface);
240 24
                $representation[] = $this->getIdentifierRepresentation($identifier);
241
            }
242
243 27
            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 57
    protected function getResourceRepresentation(ResourceInterface $resource, FieldSetFilterInterface $filter): array
258
    {
259
        $representation = [
260 57
            DocumentInterface::KEYWORD_TYPE => $resource->getType(),
261
        ];
262
263 57
        if (($index = $resource->getId()) !== null) {
264 56
            $representation[DocumentInterface::KEYWORD_ID] = $index;
265
        }
266
267 57
        $attributes = $this->getAttributesRepresentation($filter->getAttributes($resource));
268 57
        if (empty($attributes) === false) {
269 57
            $representation[DocumentInterface::KEYWORD_ATTRIBUTES] = $attributes;
270
        }
271
272 57
        $relationships = $this->getRelationshipsRepresentation($filter->getRelationships($resource));
273 57
        if (empty($relationships) === false) {
274 39
            $representation[DocumentInterface::KEYWORD_RELATIONSHIPS] = $relationships;
275
        }
276
277 57
        if ($resource->hasLinks() === true) {
278 56
            $representation[DocumentInterface::KEYWORD_LINKS] =
279 56
                $this->getLinksRepresentation($this->getUrlPrefix(), $resource->getLinks());
280
        }
281
282 57
        if ($resource->hasResourceMeta() === true) {
283 1
            $representation[DocumentInterface::KEYWORD_META] = $resource->getResourceMeta();
284
        }
285
286 57
        return $representation;
287
    }
288
289
    /**
290
     * @param array $representation
291
     *
292
     * @return void
293
     */
294 62
    private function addToData(array $representation): void
295
    {
296 62
        if ($this->isDataAnArray() === true) {
297 18
            $this->data[DocumentInterface::KEYWORD_DATA][] = $representation;
298
299 18
            return;
300
        }
301
302
        // check data has not been added yet
303 47
        assert(array_key_exists(DocumentInterface::KEYWORD_DATA, $this->data) === false);
304 47
        $this->data[DocumentInterface::KEYWORD_DATA] = $representation;
305 47
    }
306
307
    /**
308
     * @param array $representation
309
     *
310
     * @return void
311
     */
312 20
    private function addToIncluded(array $representation): void
313
    {
314 20
        $this->data[DocumentInterface::KEYWORD_INCLUDED][] = $representation;
315 20
    }
316
}
317