Completed
Branch next (d66390)
by Neomerx
02:32
created

Encoder::resetPropertiesToDefaults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 1
rs 9.9332
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Neomerx\JsonApi\Encoder;
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\Encoder\EncoderInterface;
22
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
23
use Neomerx\JsonApi\Contracts\Parser\DocumentDataInterface;
24
use Neomerx\JsonApi\Contracts\Parser\IdentifierInterface;
25
use Neomerx\JsonApi\Contracts\Parser\ParserInterface;
26
use Neomerx\JsonApi\Contracts\Parser\ResourceInterface;
27
use Neomerx\JsonApi\Contracts\Representation\BaseWriterInterface;
28
use Neomerx\JsonApi\Contracts\Representation\DocumentWriterInterface;
29
use Neomerx\JsonApi\Contracts\Representation\ErrorWriterInterface;
30
use Neomerx\JsonApi\Contracts\Schema\ErrorInterface;
31
use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
32
use Neomerx\JsonApi\Exceptions\InvalidArgumentException;
33
use Neomerx\JsonApi\Factories\Factory;
34
35
/**
36
 * @package Neomerx\JsonApi
37
 */
38
class Encoder implements EncoderInterface
39
{
40
    use EncoderPropertiesTrait;
41
42
    /**
43
     * Default value.
44
     */
45
    const DEFAULT_URL_PREFIX = '';
46
47
    /**
48
     * Default value.
49
     */
50
    const DEFAULT_INCLUDE_PATHS = [];
51
52
    /**
53
     * Default value.
54
     */
55
    const DEFAULT_FIELD_SET_FILTERS = [];
56
57
    /**
58
     * Default encode options.
59
     *
60
     * @link http://php.net/manual/en/function.json-encode.php
61
     */
62
    const DEFAULT_JSON_ENCODE_OPTIONS = 0;
63
64
    /**
65
     * Default encode depth.
66
     *
67
     * @link http://php.net/manual/en/function.json-encode.php
68
     */
69
    const DEFAULT_JSON_ENCODE_DEPTH = 512;
70
71
    /**
72
     * @param FactoryInterface         $factory
73
     * @param SchemaContainerInterface $container
74
     */
75 81
    public function __construct(
76
        FactoryInterface $factory,
77
        SchemaContainerInterface $container
78
    ) {
79 81
        $this->setFactory($factory)->setContainer($container)->resetPropertiesToDefaults();
80 81
    }
81
82
    /**
83
     * Create encoder instance.
84
     *
85
     * @param array $schemas Schema providers.
86
     *
87
     * @return EncoderInterface
88
     */
89 81
    public static function instance(array $schemas = []): EncoderInterface
90
    {
91 81
        $factory   = static::createFactory();
92 81
        $container = $factory->createSchemaContainer($schemas);
0 ignored issues
show
Documentation introduced by
$schemas is of type array, but the function expects a object<Neomerx\JsonApi\C...cts\Factories\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
93 81
        $encoder   = $factory->createEncoder($container);
94
95 81
        return $encoder;
96
    }
97
98
    /**
99
     * @inheritdoc
100
     */
101 64
    public function encodeData($data): string
102
    {
103
        // encode to json
104 64
        $array  = $this->encodeDataToArray($data);
105 61
        $result = $this->encodeToJson($array);
106
107 61
        $this->resetPropertiesToDefaults();
108
109 61
        return $result;
110
    }
111
112
    /**
113
     * @inheritdoc
114
     */
115 5
    public function encodeIdentifiers($data): string
116
    {
117
        // encode to json
118 5
        $array  = $this->encodeIdentifiersToArray($data);
119 5
        $result = $this->encodeToJson($array);
120
121 5
        $this->resetPropertiesToDefaults();
122
123 5
        return $result;
124
    }
125
126
    /**
127
     * @inheritdoc
128
     */
129 3
    public function encodeError(ErrorInterface $error): string
130
    {
131
        // encode to json
132 3
        $array  = $this->encodeErrorToArray($error);
133 3
        $result = $this->encodeToJson($array);
134
135 3
        $this->resetPropertiesToDefaults();
136
137 3
        return $result;
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143 3
    public function encodeErrors(iterable $errors): string
144
    {
145
        // encode to json
146 3
        $array  = $this->encodeErrorsToArray($errors);
147 3
        $result = $this->encodeToJson($array);
148
149 3
        $this->resetPropertiesToDefaults();
150
151 3
        return $result;
152
    }
153
154
    /**
155
     * @inheritdoc
156
     */
157 1
    public function encodeMeta($meta): string
158
    {
159
        // encode to json
160 1
        $array  = $this->encodeMetaToArray($meta);
161 1
        $result = $this->encodeToJson($array);
162
163 1
        $this->resetPropertiesToDefaults();
164
165 1
        return $result;
166
    }
167
168
    /**
169
     * @return FactoryInterface
170
     */
171 74
    protected static function createFactory(): FactoryInterface
172
    {
173 74
        return new Factory();
174
    }
175
176
    /**
177
     * @param object|iterable|null $data Data to encode.
178
     *
179
     * @return array
180
     *
181
     * @SuppressWarnings(PHPMD.ElseExpression)
182
     */
183 65
    protected function encodeDataToArray($data): array
184
    {
185 65
        if (is_array($data) === false && is_object($data) === false && $data !== null) {
186 1
            throw new InvalidArgumentException();
187
        }
188
189 64
        $parser = $this->getFactory()->createParser($this->getSchemaContainer());
190 64
        $writer = $this->createDocumentWriter();
191 64
        $filter = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
192
193
        // write header
194 64
        $this->writeHeader($writer);
195
196
        // write body
197 64
        foreach ($parser->parse($data, $this->getIncludePaths()) as $item) {
198 63
            if ($item instanceof ResourceInterface) {
199 55
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
200 20
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
201 20
                        $writer->addResourceToIncluded($item, $filter);
202
                    }
203
                } else {
204 55
                    $writer->addResourceToData($item, $filter);
205
                }
206 63
            } elseif ($item instanceof IdentifierInterface) {
207 1
                assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
208 1
                $writer->addIdentifierToData($item);
209 View Code Duplication
            } else {
210 63
                assert($item instanceof DocumentDataInterface);
211 63
                assert($item->getPosition()->getLevel() === 0);
212 63
                if ($item->isCollection() === true) {
213 21
                    $writer->setDataAsArray();
214 44
                } elseif ($item->isNull() === true) {
215 63
                    $writer->setNullToData();
216
                }
217
            }
218
        }
219
220
        // write footer
221 62
        $this->writeFooter($writer);
222
223 62
        $array = $writer->getDocument();
224
225 62
        return $array;
226
    }
227
228
    /**
229
     * @param object|iterable|null $data Data to encode.
230
     *
231
     * @return array
232
     *
233
     * @SuppressWarnings(PHPMD.ElseExpression)
234
     */
235 6
    protected function encodeIdentifiersToArray($data): array
236
    {
237 6
        $parser = $this->getFactory()->createParser($this->getSchemaContainer());
238 6
        $writer = $this->createDocumentWriter();
239 6
        $filter = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
240
241
        // write header
242 6
        $this->writeHeader($writer);
243
244
        // write body
245 6
        $includePaths   = $this->getIncludePaths();
246 6
        $expectIncluded = empty($includePaths) === false;
247
248
        // https://github.com/neomerx/json-api/issues/218
249
        //
250
        // if we expect included resources we have to include top level resources in `included` as well
251
        // Spec:
252
        //
253
        // GET /articles/1/relationships/comments?include=comments.author HTTP/1.1
254
        // Accept: application/vnd.api+json
255
        //
256
        // In this case, the primary data would be a collection of resource identifier objects that
257
        // represent linkage to comments for an article, while the full comments and comment authors
258
        // would be returned as included data.
259
260 6
        foreach ($parser->parse($data, $includePaths) as $item) {
261 6
            if ($item instanceof ResourceInterface) {
262 4
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
263 1
                    assert($expectIncluded === true);
264 1
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
265 1
                        $writer->addResourceToIncluded($item, $filter);
266
                    }
267
                } else {
268 4
                    $writer->addIdentifierToData($item);
269 4
                    if ($expectIncluded === true) {
270 4
                        $writer->addResourceToIncluded($item, $filter);
271
                    }
272
                }
273 6
            } elseif ($item instanceof IdentifierInterface) {
274 1
                assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
275 1
                $writer->addIdentifierToData($item);
276 View Code Duplication
            } else {
277 6
                assert($item instanceof DocumentDataInterface);
278 6
                assert($item->getPosition()->getLevel() === 0);
279 6
                if ($item->isCollection() === true) {
280 2
                    $writer->setDataAsArray();
281 5
                } elseif ($item->isNull() === true) {
282 6
                    $writer->setNullToData();
283
                }
284
            }
285
        }
286
287
        // write footer
288 6
        $this->writeFooter($writer);
289
290 6
        $array = $writer->getDocument();
291
292 6
        return $array;
293
    }
294
295
    /**
296
     * @param ErrorInterface $error
297
     *
298
     * @return array
299
     */
300 4
    protected function encodeErrorToArray(ErrorInterface $error): array
301
    {
302 4
        $writer = $this->createErrorWriter();
303
304
        // write header
305 4
        $this->writeHeader($writer);
306
307
        // write body
308 4
        $writer->addError($error);
309
310
        // write footer
311 4
        $this->writeFooter($writer);
312
313 4
        $array = $writer->getDocument();
314
315 4
        return $array;
316
    }
317
318
    /**
319
     * @param iterable $errors
320
     *
321
     * @return array
322
     */
323 4
    protected function encodeErrorsToArray(iterable $errors): array
324
    {
325 4
        $writer = $this->createErrorWriter();
326
327
        // write header
328 4
        $this->writeHeader($writer);
329
330
        // write body
331 4
        foreach ($errors as $error) {
332 3
            assert($error instanceof ErrorInterface);
333 3
            $writer->addError($error);
334
        }
335
336
        // write footer
337 4
        $this->writeFooter($writer);
338
339
        // encode to json
340 4
        $array = $writer->getDocument();
341
342 4
        return $array;
343
    }
344
345
    /**
346
     * @param $meta
347
     *
348
     * @return array
349
     */
350 2
    protected function encodeMetaToArray($meta): array
351
    {
352 2
        $this->withMeta($meta);
353
354 2
        $writer = $this->getFactory()->createDocumentWriter();
355
356 2
        $writer->setUrlPrefix($this->getUrlPrefix());
357
358
        // write header
359 2
        $this->writeHeader($writer);
360
361
        // write footer
362 2
        $this->writeFooter($writer);
363
364
        // encode to json
365 2
        $array = $writer->getDocument();
366
367 2
        return $array;
368
    }
369
370
    /**
371
     * @param BaseWriterInterface $writer
372
     *
373
     * @return void
374
     */
375 79
    protected function writeHeader(BaseWriterInterface $writer): void
376
    {
377 79
        if ($this->hasMeta() === true) {
378 6
            $writer->setMeta($this->getMeta());
379
        }
380
381 79
        if ($this->hasJsonApiVersion() === true) {
382 2
            $writer->setJsonApiVersion($this->getJsonApiVersion());
383
        }
384
385 79
        if ($this->hasJsonApiMeta() === true) {
386 2
            $writer->setJsonApiMeta($this->getJsonApiMeta());
387
        }
388
389 79
        if ($this->hasLinks() === true) {
390 5
            $writer->setLinks($this->getLinks());
391
        }
392
393 79
        if ($this->hasProfile() === true) {
394 1
            $writer->setProfile($this->getProfile());
395
        }
396 79
    }
397
398
    /**
399
     * @param BaseWriterInterface $writer
400
     *
401
     * @return void
402
     */
403 77
    protected function writeFooter(BaseWriterInterface $writer): void
404
    {
405 77
        assert($writer !== null);
406 77
    }
407
408
    /**
409
     * Encode array to JSON.
410
     *
411
     * @param array $document
412
     *
413
     * @return string
414
     */
415 73
    protected function encodeToJson(array $document): string
416
    {
417 73
        return json_encode($document, $this->getEncodeOptions(), $this->getEncodeDepth());
418
    }
419
420
    /**
421
     * @return DocumentWriterInterface
422
     */
423 70
    private function createDocumentWriter(): DocumentWriterInterface
424
    {
425 70
        $writer = $this->getFactory()->createDocumentWriter();
426 70
        $writer->setUrlPrefix($this->getUrlPrefix());
427
428 70
        return $writer;
429
    }
430
431
    /**
432
     * @return ErrorWriterInterface
433
     */
434 7
    private function createErrorWriter(): ErrorWriterInterface
435
    {
436 7
        $writer = $this->getFactory()->createErrorWriter();
437 7
        $writer->setUrlPrefix($this->getUrlPrefix());
438
439 7
        return $writer;
440
    }
441
442
    /**
443
     * @return self
444
     */
445 81
    private function resetPropertiesToDefaults(): self
446
    {
447 81
        return $this->reset(
448 81
            static::DEFAULT_URL_PREFIX,
449 81
            static::DEFAULT_INCLUDE_PATHS,
0 ignored issues
show
Documentation introduced by
static::DEFAULT_INCLUDE_PATHS is of type array, but the function expects a object<Neomerx\JsonApi\Encoder\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
450 81
            static::DEFAULT_FIELD_SET_FILTERS,
451 81
            static::DEFAULT_JSON_ENCODE_OPTIONS,
452 81
            static::DEFAULT_JSON_ENCODE_DEPTH
453
        );
454
    }
455
}
456