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

Encoder   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 395
Duplicated Lines 4.56 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 18
loc 395
c 0
b 0
f 0
ccs 132
cts 132
cp 1
rs 9.0399
wmc 42
lcom 1
cbo 13

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A instance() 0 8 1
A encodeData() 0 8 1
A encodeIdentifiers() 0 8 1
A encodeError() 0 8 1
A encodeErrors() 0 8 1
A encodeMeta() 0 8 1
A createFactory() 0 4 1
B encodeDataToArray() 9 44 11
B encodeIdentifiersToArray() 9 59 9
A encodeErrorToArray() 0 17 1
A encodeErrorsToArray() 0 21 2
A encodeMetaToArray() 0 19 1
B writeHeader() 0 22 6
A writeFooter() 0 4 1
A encodeToJson() 0 4 1
A createDocumentWriter() 0 7 1
A createErrorWriter() 0 7 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Encoder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Encoder, and based on these observations, apply Extract Interface, too.

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
 * @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 82
    public function __construct(
78
        FactoryInterface $factory,
79
        SchemaContainerInterface $container
80
    ) {
81 82
        $this->setFactory($factory)->setContainer($container)->reset();
82 82
    }
83
84
    /**
85
     * Create encoder instance.
86
     *
87
     * @param array $schemas Schema providers.
88
     *
89
     * @return EncoderInterface
90
     */
91 82
    public static function instance(array $schemas = []): EncoderInterface
92
    {
93 82
        $factory   = static::createFactory();
94 82
        $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 82
        $encoder   = $factory->createEncoder($container);
96
97 82
        return $encoder;
98
    }
99
100
    /**
101
     * @inheritdoc
102
     */
103 65
    public function encodeData($data): string
104
    {
105
        // encode to json
106 65
        $array  = $this->encodeDataToArray($data);
107 62
        $result = $this->encodeToJson($array);
108
109 62
        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 75
    protected static function createFactory(): FactoryInterface
164
    {
165 75
        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 66
    protected function encodeDataToArray($data): array
177
    {
178 66
        if (is_array($data) === false && is_object($data) === false && $data !== null) {
179 1
            throw new InvalidArgumentException();
180
        }
181
182 65
        $parser = $this->getFactory()->createParser($this->getSchemaContainer());
183 65
        $writer = $this->createDocumentWriter();
184 65
        $filter = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
185
186
        // write header
187 65
        $this->writeHeader($writer);
188
189
        // write body
190 65
        foreach ($parser->parse($data, $this->getIncludePaths()) as $item) {
191 64
            if ($item instanceof ResourceInterface) {
192 56
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
193 20
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
194 20
                        $writer->addResourceToIncluded($item, $filter);
195
                    }
196
                } else {
197 56
                    $writer->addResourceToData($item, $filter);
198
                }
199 64
            } elseif ($item instanceof IdentifierInterface) {
200 1
                assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
201 1
                $writer->addIdentifierToData($item);
202 View Code Duplication
            } else {
203 64
                assert($item instanceof DocumentDataInterface);
204 64
                assert($item->getPosition()->getLevel() === 0);
205 64
                if ($item->isCollection() === true) {
206 21
                    $writer->setDataAsArray();
207 45
                } elseif ($item->isNull() === true) {
208 64
                    $writer->setNullToData();
209
                }
210
            }
211
        }
212
213
        // write footer
214 63
        $this->writeFooter($writer);
215
216 63
        $array = $writer->getDocument();
217
218 63
        return $array;
219
    }
220
221
    /**
222
     * @param object|iterable|null $data Data to encode.
223
     *
224
     * @return array
225
     *
226
     * @SuppressWarnings(PHPMD.ElseExpression)
227
     */
228 6
    protected function encodeIdentifiersToArray($data): array
229
    {
230 6
        $parser = $this->getFactory()->createParser($this->getSchemaContainer());
231 6
        $writer = $this->createDocumentWriter();
232 6
        $filter = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
233
234
        // write header
235 6
        $this->writeHeader($writer);
236
237
        // write body
238 6
        $includePaths   = $this->getIncludePaths();
239 6
        $expectIncluded = empty($includePaths) === false;
240
241
        // https://github.com/neomerx/json-api/issues/218
242
        //
243
        // if we expect included resources we have to include top level resources in `included` as well
244
        // Spec:
245
        //
246
        // GET /articles/1/relationships/comments?include=comments.author HTTP/1.1
247
        // Accept: application/vnd.api+json
248
        //
249
        // In this case, the primary data would be a collection of resource identifier objects that
250
        // represent linkage to comments for an article, while the full comments and comment authors
251
        // would be returned as included data.
252
253 6
        foreach ($parser->parse($data, $includePaths) as $item) {
254 6
            if ($item instanceof ResourceInterface) {
255 4
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
256 1
                    assert($expectIncluded === true);
257 1
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
258 1
                        $writer->addResourceToIncluded($item, $filter);
259
                    }
260
                } else {
261 4
                    $writer->addIdentifierToData($item);
262 4
                    if ($expectIncluded === true) {
263 4
                        $writer->addResourceToIncluded($item, $filter);
264
                    }
265
                }
266 6
            } elseif ($item instanceof IdentifierInterface) {
267 1
                assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
268 1
                $writer->addIdentifierToData($item);
269 View Code Duplication
            } else {
270 6
                assert($item instanceof DocumentDataInterface);
271 6
                assert($item->getPosition()->getLevel() === 0);
272 6
                if ($item->isCollection() === true) {
273 2
                    $writer->setDataAsArray();
274 5
                } elseif ($item->isNull() === true) {
275 6
                    $writer->setNullToData();
276
                }
277
            }
278
        }
279
280
        // write footer
281 6
        $this->writeFooter($writer);
282
283 6
        $array = $writer->getDocument();
284
285 6
        return $array;
286
    }
287
288
    /**
289
     * @param ErrorInterface $error
290
     *
291
     * @return array
292
     */
293 4
    protected function encodeErrorToArray(ErrorInterface $error): array
294
    {
295 4
        $writer = $this->createErrorWriter();
296
297
        // write header
298 4
        $this->writeHeader($writer);
299
300
        // write body
301 4
        $writer->addError($error);
302
303
        // write footer
304 4
        $this->writeFooter($writer);
305
306 4
        $array = $writer->getDocument();
307
308 4
        return $array;
309
    }
310
311
    /**
312
     * @param iterable $errors
313
     *
314
     * @return array
315
     */
316 4
    protected function encodeErrorsToArray(iterable $errors): array
317
    {
318 4
        $writer = $this->createErrorWriter();
319
320
        // write header
321 4
        $this->writeHeader($writer);
322
323
        // write body
324 4
        foreach ($errors as $error) {
325 3
            assert($error instanceof ErrorInterface);
326 3
            $writer->addError($error);
327
        }
328
329
        // write footer
330 4
        $this->writeFooter($writer);
331
332
        // encode to json
333 4
        $array = $writer->getDocument();
334
335 4
        return $array;
336
    }
337
338
    /**
339
     * @param $meta
340
     *
341
     * @return array
342
     */
343 2
    protected function encodeMetaToArray($meta): array
344
    {
345 2
        $this->withMeta($meta);
346
347 2
        $writer = $this->getFactory()->createDocumentWriter();
348
349 2
        $writer->setUrlPrefix($this->getUrlPrefix());
350
351
        // write header
352 2
        $this->writeHeader($writer);
353
354
        // write footer
355 2
        $this->writeFooter($writer);
356
357
        // encode to json
358 2
        $array = $writer->getDocument();
359
360 2
        return $array;
361
    }
362
363
    /**
364
     * @param BaseWriterInterface $writer
365
     *
366
     * @return void
367
     */
368 80
    protected function writeHeader(BaseWriterInterface $writer): void
369
    {
370 80
        if ($this->hasMeta() === true) {
371 6
            $writer->setMeta($this->getMeta());
372
        }
373
374 80
        if ($this->hasJsonApiVersion() === true) {
375 2
            $writer->setJsonApiVersion($this->getJsonApiVersion());
376
        }
377
378 80
        if ($this->hasJsonApiMeta() === true) {
379 2
            $writer->setJsonApiMeta($this->getJsonApiMeta());
380
        }
381
382 80
        if ($this->hasLinks() === true) {
383 5
            $writer->setLinks($this->getLinks());
384
        }
385
386 80
        if ($this->hasProfile() === true) {
387 1
            $writer->setProfile($this->getProfile());
388
        }
389 80
    }
390
391
    /**
392
     * @param BaseWriterInterface $writer
393
     *
394
     * @return void
395
     */
396 78
    protected function writeFooter(BaseWriterInterface $writer): void
397
    {
398 78
        assert($writer !== null);
399 78
    }
400
401
    /**
402
     * Encode array to JSON.
403
     *
404
     * @param array $document
405
     *
406
     * @return string
407
     */
408 74
    protected function encodeToJson(array $document): string
409
    {
410 74
        return json_encode($document, $this->getEncodeOptions(), $this->getEncodeDepth());
411
    }
412
413
    /**
414
     * @return DocumentWriterInterface
415
     */
416 71
    private function createDocumentWriter(): DocumentWriterInterface
417
    {
418 71
        $writer = $this->getFactory()->createDocumentWriter();
419 71
        $writer->setUrlPrefix($this->getUrlPrefix());
420
421 71
        return $writer;
422
    }
423
424
    /**
425
     * @return ErrorWriterInterface
426
     */
427 7
    private function createErrorWriter(): ErrorWriterInterface
428
    {
429 7
        $writer = $this->getFactory()->createErrorWriter();
430 7
        $writer->setUrlPrefix($this->getUrlPrefix());
431
432 7
        return $writer;
433
    }
434
}
435