Encoder::writeHeader()   B
last analyzed

Complexity

Conditions 6
Paths 32

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
nc 32
nop 1
dl 0
loc 22
ccs 12
cts 12
cp 1
crap 6
rs 8.9457
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Neomerx\JsonApi\Encoder;
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\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
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
39
 */
40
class Encoder implements EncoderInterface
41
{
42
    use EncoderPropertiesTrait;
43
44
    /**
45
     * Default value.
46
     */
47
    const DEFAULT_URL_PREFIX = '';
48
49
    /**
50
     * Default value.
51
     */
52
    const DEFAULT_INCLUDE_PATHS = [];
53
54
    /**
55
     * Default value.
56
     */
57
    const DEFAULT_FIELD_SET_FILTERS = [];
58
59
    /**
60
     * Default encode options.
61
     *
62
     * @link http://php.net/manual/en/function.json-encode.php
63
     */
64
    const DEFAULT_JSON_ENCODE_OPTIONS = 0;
65
66
    /**
67
     * Default encode depth.
68
     *
69
     * @link http://php.net/manual/en/function.json-encode.php
70
     */
71
    const DEFAULT_JSON_ENCODE_DEPTH = 512;
72
73
    /**
74
     * @param FactoryInterface         $factory
75
     * @param SchemaContainerInterface $container
76
     */
77 87
    public function __construct(
78
        FactoryInterface $factory,
79
        SchemaContainerInterface $container
80
    ) {
81 87
        $this->setFactory($factory)->setContainer($container)->reset();
82 87
    }
83
84
    /**
85
     * Create encoder instance.
86
     *
87
     * @param array $schemas Schema providers.
88
     *
89
     * @return EncoderInterface
90
     */
91 87
    public static function instance(array $schemas = []): EncoderInterface
92
    {
93 87
        $factory   = static::createFactory();
94 87
        $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...
95 87
        $encoder   = $factory->createEncoder($container);
96
97 87
        return $encoder;
98
    }
99
100
    /**
101
     * @inheritdoc
102
     */
103 70
    public function encodeData($data): string
104
    {
105
        // encode to json
106 70
        $array  = $this->encodeDataToArray($data);
107 67
        $result = $this->encodeToJson($array);
108
109 67
        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
        return $result;
122
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127 3
    public function encodeError(ErrorInterface $error): string
128
    {
129
        // encode to json
130 3
        $array  = $this->encodeErrorToArray($error);
131 3
        $result = $this->encodeToJson($array);
132
133 3
        return $result;
134
    }
135
136
    /**
137
     * @inheritdoc
138
     */
139 3
    public function encodeErrors(iterable $errors): string
140
    {
141
        // encode to json
142 3
        $array  = $this->encodeErrorsToArray($errors);
143 3
        $result = $this->encodeToJson($array);
144
145 3
        return $result;
146
    }
147
148
    /**
149
     * @inheritdoc
150
     */
151 1
    public function encodeMeta($meta): string
152
    {
153
        // encode to json
154 1
        $array  = $this->encodeMetaToArray($meta);
155 1
        $result = $this->encodeToJson($array);
156
157 1
        return $result;
158
    }
159
160
    /**
161
     * @return FactoryInterface
162
     */
163 77
    protected static function createFactory(): FactoryInterface
164
    {
165 77
        return new Factory();
166
    }
167
168
    /**
169
     * @param object|iterable|null $data Data to encode.
170
     *
171
     * @return array
172
     *
173
     * @SuppressWarnings(PHPMD.ElseExpression)
174
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
175
     */
176 71
    protected function encodeDataToArray($data): array
177
    {
178 71
        if (\is_array($data) === false && \is_object($data) === false && $data !== null) {
179 1
            throw new InvalidArgumentException();
180
        }
181
182 70
        $context = $this->getFactory()->createParserContext($this->getFieldSets(), $this->getIncludePaths());
183 70
        $parser  = $this->getFactory()->createParser($this->getSchemaContainer(), $context);
184 70
        $writer  = $this->createDocumentWriter();
185 70
        $filter  = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
186
187
        // write header
188 70
        $this->writeHeader($writer);
189
190
        // write body
191 70
        foreach ($parser->parse($data, $this->getIncludePaths()) as $item) {
192 69
            if ($item instanceof ResourceInterface) {
193 61
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
194 24
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
195 24
                        $writer->addResourceToIncluded($item, $filter);
196
                    }
197
                } else {
198 61
                    $writer->addResourceToData($item, $filter);
199
                }
200 69
            } elseif ($item instanceof IdentifierInterface) {
201 1
                \assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
202 1
                $writer->addIdentifierToData($item);
203 View Code Duplication
            } else {
204 69
                \assert($item instanceof DocumentDataInterface);
205 69
                \assert($item->getPosition()->getLevel() === 0);
206 69
                if ($item->isCollection() === true) {
207 21
                    $writer->setDataAsArray();
208 50
                } elseif ($item->isNull() === true) {
209 69
                    $writer->setNullToData();
210
                }
211
            }
212
        }
213
214
        // write footer
215 68
        $this->writeFooter($writer);
216
217 68
        $array = $writer->getDocument();
218
219 68
        return $array;
220
    }
221
222
    /**
223
     * @param object|iterable|null $data Data to encode.
224
     *
225
     * @return array
226
     *
227
     * @SuppressWarnings(PHPMD.ElseExpression)
228
     */
229 6
    protected function encodeIdentifiersToArray($data): array
230
    {
231 6
        $context = $this->getFactory()->createParserContext($this->getFieldSets(), $this->getIncludePaths());
232 6
        $parser  = $this->getFactory()->createParser($this->getSchemaContainer(), $context);
233 6
        $writer  = $this->createDocumentWriter();
234 6
        $filter  = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
235
236
        // write header
237 6
        $this->writeHeader($writer);
238
239
        // write body
240 6
        $includePaths   = $this->getIncludePaths();
241 6
        $expectIncluded = empty($includePaths) === false;
242
243
        // https://github.com/neomerx/json-api/issues/218
244
        //
245
        // if we expect included resources we have to include top level resources in `included` as well
246
        // Spec:
247
        //
248
        // GET /articles/1/relationships/comments?include=comments.author HTTP/1.1
249
        // Accept: application/vnd.api+json
250
        //
251
        // In this case, the primary data would be a collection of resource identifier objects that
252
        // represent linkage to comments for an article, while the full comments and comment authors
253
        // would be returned as included data.
254
255 6
        foreach ($parser->parse($data, $includePaths) as $item) {
256 6
            if ($item instanceof ResourceInterface) {
257 4
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
258 1
                    \assert($expectIncluded === true);
259 1
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
260 1
                        $writer->addResourceToIncluded($item, $filter);
261
                    }
262
                } else {
263 4
                    $writer->addIdentifierToData($item);
264 4
                    if ($expectIncluded === true) {
265 4
                        $writer->addResourceToIncluded($item, $filter);
266
                    }
267
                }
268 6
            } elseif ($item instanceof IdentifierInterface) {
269 1
                \assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
270 1
                $writer->addIdentifierToData($item);
271 View Code Duplication
            } else {
272 6
                \assert($item instanceof DocumentDataInterface);
273 6
                \assert($item->getPosition()->getLevel() === 0);
274 6
                if ($item->isCollection() === true) {
275 2
                    $writer->setDataAsArray();
276 5
                } elseif ($item->isNull() === true) {
277 6
                    $writer->setNullToData();
278
                }
279
            }
280
        }
281
282
        // write footer
283 6
        $this->writeFooter($writer);
284
285 6
        $array = $writer->getDocument();
286
287 6
        return $array;
288
    }
289
290
    /**
291
     * @param ErrorInterface $error
292
     *
293
     * @return array
294
     */
295 4
    protected function encodeErrorToArray(ErrorInterface $error): array
296
    {
297 4
        $writer = $this->createErrorWriter();
298
299
        // write header
300 4
        $this->writeHeader($writer);
301
302
        // write body
303 4
        $writer->addError($error);
304
305
        // write footer
306 4
        $this->writeFooter($writer);
307
308 4
        $array = $writer->getDocument();
309
310 4
        return $array;
311
    }
312
313
    /**
314
     * @param iterable $errors
315
     *
316
     * @return array
317
     */
318 4
    protected function encodeErrorsToArray(iterable $errors): array
319
    {
320 4
        $writer = $this->createErrorWriter();
321
322
        // write header
323 4
        $this->writeHeader($writer);
324
325
        // write body
326 4
        foreach ($errors as $error) {
327 3
            \assert($error instanceof ErrorInterface);
328 3
            $writer->addError($error);
329
        }
330
331
        // write footer
332 4
        $this->writeFooter($writer);
333
334
        // encode to json
335 4
        $array = $writer->getDocument();
336
337 4
        return $array;
338
    }
339
340
    /**
341
     * @param $meta
342
     *
343
     * @return array
344
     */
345 2
    protected function encodeMetaToArray($meta): array
346
    {
347 2
        $this->withMeta($meta);
348
349 2
        $writer = $this->getFactory()->createDocumentWriter();
350
351 2
        $writer->setUrlPrefix($this->getUrlPrefix());
352
353
        // write header
354 2
        $this->writeHeader($writer);
355
356
        // write footer
357 2
        $this->writeFooter($writer);
358
359
        // encode to json
360 2
        $array = $writer->getDocument();
361
362 2
        return $array;
363
    }
364
365
    /**
366
     * @param BaseWriterInterface $writer
367
     *
368
     * @return void
369
     */
370 85
    protected function writeHeader(BaseWriterInterface $writer): void
371
    {
372 85
        if ($this->hasMeta() === true) {
373 6
            $writer->setMeta($this->getMeta());
374
        }
375
376 85
        if ($this->hasJsonApiVersion() === true) {
377 2
            $writer->setJsonApiVersion($this->getJsonApiVersion());
378
        }
379
380 85
        if ($this->hasJsonApiMeta() === true) {
381 2
            $writer->setJsonApiMeta($this->getJsonApiMeta());
382
        }
383
384 85
        if ($this->hasLinks() === true) {
385 5
            $writer->setLinks($this->getLinks());
386
        }
387
388 85
        if ($this->hasProfile() === true) {
389 1
            $writer->setProfile($this->getProfile());
390
        }
391 85
    }
392
393
    /**
394
     * @param BaseWriterInterface $writer
395
     *
396
     * @return void
397
     */
398 83
    protected function writeFooter(BaseWriterInterface $writer): void
399
    {
400 83
        \assert($writer !== null);
401 83
    }
402
403
    /**
404
     * Encode array to JSON.
405
     *
406
     * @param array $document
407
     *
408
     * @return string
409
     */
410 79
    protected function encodeToJson(array $document): string
411
    {
412 79
        return \json_encode($document, $this->getEncodeOptions(), $this->getEncodeDepth());
413
    }
414
415
    /**
416
     * @return DocumentWriterInterface
417
     */
418 76
    private function createDocumentWriter(): DocumentWriterInterface
419
    {
420 76
        $writer = $this->getFactory()->createDocumentWriter();
421 76
        $writer->setUrlPrefix($this->getUrlPrefix());
422
423 76
        return $writer;
424
    }
425
426
    /**
427
     * @return ErrorWriterInterface
428
     */
429 7
    private function createErrorWriter(): ErrorWriterInterface
430
    {
431 7
        $writer = $this->getFactory()->createErrorWriter();
432 7
        $writer->setUrlPrefix($this->getUrlPrefix());
433
434 7
        return $writer;
435
    }
436
}
437