Completed
Branch next (8f844e)
by Neomerx
04:06
created

Encoder::getProfile()   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 0
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\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\LinkInterface;
32
use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
33
use Neomerx\JsonApi\Exceptions\InvalidArgumentException;
34
use Neomerx\JsonApi\Factories\Factory;
35
36
/**
37
 * @package Neomerx\JsonApi
38
 */
39
class Encoder implements EncoderInterface
40
{
41
    /**
42
     * Default value.
43
     */
44
    const DEFAULT_URL_PREFIX = '';
45
46
    /**
47
     * Default value.
48
     */
49
    const DEFAULT_INCLUDE_PATHS = [];
50
51
    /**
52
     * Default value.
53
     */
54
    const DEFAULT_FIELD_SET_FILTERS = [];
55
56
    /**
57
     * Default encode options.
58
     *
59
     * @link http://php.net/manual/en/function.json-encode.php
60
     */
61
    const DEFAULT_JSON_ENCODE_OPTIONS = 0;
62
63
    /**
64
     * Default encode depth.
65
     *
66
     * @link http://php.net/manual/en/function.json-encode.php
67
     */
68
    const DEFAULT_JSON_ENCODE_DEPTH = 512;
69
70
    /**
71
     * @var SchemaContainerInterface
72
     */
73
    private $container;
74
75
    /**
76
     * @var FactoryInterface
77
     */
78
    private $factory;
79
80
    /**
81
     * @var string
82
     */
83
    private $urlPrefix;
84
85
    /**
86
     * @var array
87
     */
88
    private $includePaths;
89
90
    /**
91
     * @var array
92
     */
93
    private $fieldSets;
94
95
    /**
96
     * @var int
97
     */
98
    private $encodeOptions;
99
100
    /**
101
     * @var int
102
     */
103
    private $encodeDepth;
104
105
    /**
106
     * @var iterable
107
     */
108
    private $links;
109
110
    /**
111
     * @var iterable
112
     */
113
    private $profile;
114
115
    /**
116
     * @var bool
117
     */
118
    private $hasMeta;
119
120
    /**
121
     * @var mixed
122
     */
123
    private $meta;
124
125
    /**
126
     * @var string|null
127
     */
128
    private $jsonApiVersion;
129
130
    /**
131
     * @var mixed
132
     */
133
    private $jsonApiMeta;
134
135
    /**
136
     * @var bool
137
     */
138
    private $hasJsonApiMeta;
139
140
    /**
141
     * @param FactoryInterface         $factory
142
     * @param SchemaContainerInterface $container
143
     */
144 81
    public function __construct(
145
        FactoryInterface $factory,
146
        SchemaContainerInterface $container
147
    ) {
148 81
        $this->factory   = $factory;
149 81
        $this->container = $container;
150
151 81
        $this->reset();
152 81
    }
153
154
    /**
155
     * Create encoder instance.
156
     *
157
     * @param array $schemas Schema providers.
158
     *
159
     * @return EncoderInterface
160
     */
161 81
    public static function instance(array $schemas = []): EncoderInterface
162
    {
163 81
        $factory   = static::createFactory();
164 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...
165 81
        $encoder   = $factory->createEncoder($container);
166
167 81
        return $encoder;
168
    }
169
170
    /**
171
     * @inheritdoc
172
     */
173 81
    public function withUrlPrefix(string $prefix): EncoderInterface
174
    {
175 81
        $this->urlPrefix = $prefix;
176
177 81
        return $this;
178
    }
179
180
    /**
181
     * @inheritdoc
182
     */
183 81
    public function withIncludedPaths(iterable $paths): EncoderInterface
184
    {
185 81
        assert(
186 81
            call_user_func(
187
                function (array $paths): bool {
188 81
                    $pathsOk = true;
189 81
                    foreach ($paths as $path) {
190 22
                        $pathsOk = $pathsOk === true && is_string($path) === true && empty($path) === false;
191
                    }
192
193 81
                    return $pathsOk;
194 81
                },
195 81
                $paths
196
            )
197
        );
198
199 81
        $this->includePaths = $paths;
200
201 81
        return $this;
202
    }
203
204
    /**
205
     * @inheritdoc
206
     */
207 81
    public function withFieldSets(array $fieldSets): EncoderInterface
208
    {
209 81
        $this->fieldSets = $fieldSets;
210
211 81
        return $this;
212
    }
213
214
    /**
215
     * @inheritdoc
216
     */
217 81
    public function withEncodeOptions(int $options): EncoderInterface
218
    {
219 81
        $this->encodeOptions = $options;
220
221 81
        return $this;
222
    }
223
224
    /**
225
     * @inheritdoc
226
     */
227 81
    public function withEncodeDepth(int $depth): EncoderInterface
228
    {
229 81
        assert($depth > 0);
230
231 81
        $this->encodeDepth = $depth;
232
233 81
        return $this;
234
    }
235
236
    /**
237
     * @inheritdoc
238
     */
239 5
    public function withLinks(iterable $links): EncoderInterface
240
    {
241 5
        $this->links = $this->hasLinks() === true ? $this->mergeIterables($this->links, $links) : $links;
0 ignored issues
show
Documentation introduced by
$links 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...
242
243 5
        return $this;
244
    }
245
246
    /**
247
     * @inheritdoc
248
     */
249 1
    public function withProfile(iterable $links): EncoderInterface
250
    {
251 1
        $this->profile = $links;
252
253 1
        return $this;
254
    }
255
256
    /**
257
     * @inheritdoc
258
     */
259 6
    public function withMeta($meta): EncoderInterface
260
    {
261 6
        $this->meta    = $meta;
262 6
        $this->hasMeta = true;
263
264 6
        return $this;
265
    }
266
267
    /**
268
     * @inheritdoc
269
     */
270 2
    public function withJsonApiVersion(string $version = self::JSON_API_VERSION): EncoderInterface
271
    {
272 2
        $this->jsonApiVersion = $version;
273
274 2
        return $this;
275
    }
276
277
    /**
278
     * @inheritdoc
279
     */
280 2
    public function withJsonApiMeta($meta): EncoderInterface
281
    {
282 2
        $this->jsonApiMeta    = $meta;
283 2
        $this->hasJsonApiMeta = true;
284
285 2
        return $this;
286
    }
287
288
    /**
289
     * @inheritdoc
290
     */
291 1 View Code Duplication
    public function withRelationshipSelfLink($resource, string $relationshipName): EncoderInterface
292
    {
293
        $link = $this
294 1
            ->getSchemaContainer()->getSchema($resource)
295 1
            ->getRelationshipSelfLink($resource, $relationshipName);
296
297 1
        return $this->withLinks(
298
            [
299 1
                LinkInterface::SELF => $link,
300
            ]
301
        );
302
    }
303
304
    /**
305
     * @inheritdoc
306
     */
307 1 View Code Duplication
    public function withRelationshipRelatedLink($resource, string $relationshipName): EncoderInterface
308
    {
309
        $link = $this
310 1
            ->getSchemaContainer()->getSchema($resource)
311 1
            ->getRelationshipRelatedLink($resource, $relationshipName);
312
313 1
        return $this->withLinks(
314
            [
315 1
                LinkInterface::RELATED => $link,
316
            ]
317
        );
318
    }
319
320
    /**
321
     * @inheritdoc
322
     */
323 64
    public function encodeData($data): string
324
    {
325
        // encode to json
326 64
        $array  = $this->encodeDataToArray($data);
327 61
        $result = $this->encodeToJson($array);
328
329 61
        $this->reset();
330
331 61
        return $result;
332
    }
333
334
    /**
335
     * @inheritdoc
336
     */
337 5
    public function encodeIdentifiers($data): string
338
    {
339
        // encode to json
340 5
        $array  = $this->encodeIdentifiersToArray($data);
341 5
        $result = $this->encodeToJson($array);
342
343 5
        $this->reset();
344
345 5
        return $result;
346
    }
347
348
    /**
349
     * @inheritdoc
350
     */
351 3
    public function encodeError(ErrorInterface $error): string
352
    {
353
        // encode to json
354 3
        $array  = $this->encodeErrorToArray($error);
355 3
        $result = $this->encodeToJson($array);
356
357 3
        $this->reset();
358
359 3
        return $result;
360
    }
361
362
    /**
363
     * @inheritdoc
364
     */
365 3
    public function encodeErrors(iterable $errors): string
366
    {
367
        // encode to json
368 3
        $array  = $this->encodeErrorsToArray($errors);
369 3
        $result = $this->encodeToJson($array);
370
371 3
        $this->reset();
372
373 3
        return $result;
374
    }
375
376
    /**
377
     * @inheritdoc
378
     */
379 1
    public function encodeMeta($meta): string
380
    {
381
        // encode to json
382 1
        $array  = $this->encodeMetaToArray($meta);
383 1
        $result = $this->encodeToJson($array);
384
385 1
        $this->reset();
386
387 1
        return $result;
388
    }
389
390
    /**
391
     * @return FactoryInterface
392
     */
393 74
    protected static function createFactory(): FactoryInterface
394
    {
395 74
        return new Factory();
396
    }
397
398
    /**
399
     * @return SchemaContainerInterface
400
     */
401 70
    protected function getSchemaContainer(): SchemaContainerInterface
402
    {
403 70
        return $this->container;
404
    }
405
406
    /**
407
     * @return FactoryInterface
408
     */
409 79
    protected function getFactory(): FactoryInterface
410
    {
411 79
        return $this->factory;
412
    }
413
414
    /**
415
     * Reset to initial state.
416
     */
417 81
    protected function reset(): void
418
    {
419 81
        $this->links          = null;
420 81
        $this->profile        = null;
421 81
        $this->hasMeta        = false;
422 81
        $this->meta           = null;
423 81
        $this->jsonApiVersion = null;
424 81
        $this->jsonApiMeta    = null;
425 81
        $this->hasJsonApiMeta = false;
426
427
        $this
428 81
            ->withUrlPrefix(static::DEFAULT_URL_PREFIX)
429 81
            ->withIncludedPaths(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\C...racts\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...
430 81
            ->withFieldSets(static::DEFAULT_FIELD_SET_FILTERS)
431 81
            ->withEncodeOptions(static::DEFAULT_JSON_ENCODE_OPTIONS)
432 81
            ->withEncodeDepth(static::DEFAULT_JSON_ENCODE_DEPTH);
433 81
    }
434
435
    /**
436
     * @return bool
437
     */
438 79
    protected function hasLinks(): bool
439
    {
440 79
        return $this->links !== null;
441
    }
442
443
    /**
444
     * @return bool
445
     */
446 79
    protected function hasProfile(): bool
447
    {
448 79
        return $this->profile !== null;
449
    }
450
451
    /**
452
     * @return iterable
453
     */
454 5
    protected function getLinks(): iterable
455
    {
456 5
        return $this->links;
457
    }
458
459
    /**
460
     * @return iterable
461
     */
462 1
    protected function getProfile(): iterable
463
    {
464 1
        return $this->profile;
465
    }
466
467
    /**
468
     * @return bool
469
     */
470 79
    protected function hasMeta(): bool
471
    {
472 79
        return $this->hasMeta;
473
    }
474
475
    /**
476
     * @return mixed
477
     */
478 6
    public function getMeta()
479
    {
480 6
        return $this->meta;
481
    }
482
483
    /**
484
     * @return bool
485
     */
486 79
    protected function hasJsonApiVersion(): bool
487
    {
488 79
        return $this->jsonApiVersion !== null;
489
    }
490
491
    /**
492
     * @return string
493
     */
494 2
    protected function getJsonApiVersion(): string
495
    {
496 2
        return $this->jsonApiVersion;
497
    }
498
499
    /**
500
     * @return bool
501
     */
502 79
    protected function hasJsonApiMeta(): bool
503
    {
504 79
        return $this->hasJsonApiMeta;
505
    }
506
507
    /**
508
     * @return mixed
509
     */
510 2
    protected function getJsonApiMeta()
511
    {
512 2
        return $this->jsonApiMeta;
513
    }
514
515
    /**
516
     * @param object|iterable|null $data Data to encode.
517
     *
518
     * @return array
519
     *
520
     * @SuppressWarnings(PHPMD.ElseExpression)
521
     */
522 65
    protected function encodeDataToArray($data): array
523
    {
524 65
        if (is_array($data) === false && is_object($data) === false && $data !== null) {
525 1
            throw new InvalidArgumentException();
526
        }
527
528 64
        $parser = $this->getFactory()->createParser($this->getSchemaContainer());
529 64
        $writer = $this->createDocumentWriter();
530 64
        $filter = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
531
532
        // write header
533 64
        $this->writeHeader($writer);
534
535
        // write body
536 64
        foreach ($parser->parse($data, $this->getIncludePaths()) as $item) {
537 63
            if ($item instanceof ResourceInterface) {
538 55
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
539 20
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
540 20
                        $writer->addResourceToIncluded($item, $filter);
541
                    }
542
                } else {
543 55
                    $writer->addResourceToData($item, $filter);
544
                }
545 63
            } elseif ($item instanceof IdentifierInterface) {
546 1
                assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
547 1
                $writer->addIdentifierToData($item);
548 View Code Duplication
            } else {
549 63
                assert($item instanceof DocumentDataInterface);
550 63
                assert($item->getPosition()->getLevel() === 0);
551 63
                if ($item->isCollection() === true) {
552 21
                    $writer->setDataAsArray();
553 44
                } elseif ($item->isNull() === true) {
554 63
                    $writer->setNullToData();
555
                }
556
            }
557
        }
558
559
        // write footer
560 62
        $this->writeFooter($writer);
561
562 62
        $array = $writer->getDocument();
563
564 62
        return $array;
565
    }
566
567
    /**
568
     * @param object|iterable|null $data Data to encode.
569
     *
570
     * @return array
571
     *
572
     * @SuppressWarnings(PHPMD.ElseExpression)
573
     */
574 6
    protected function encodeIdentifiersToArray($data): array
575
    {
576 6
        $parser = $this->getFactory()->createParser($this->getSchemaContainer());
577 6
        $writer = $this->createDocumentWriter();
578 6
        $filter = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
579
580
        // write header
581 6
        $this->writeHeader($writer);
582
583
        // write body
584 6
        $includePaths   = $this->getIncludePaths();
585 6
        $expectIncluded = empty($includePaths) === false;
586
587
        // https://github.com/neomerx/json-api/issues/218
588
        //
589
        // if we expect included resources we have to include top level resources in `included` as well
590
        // Spec:
591
        //
592
        // GET /articles/1/relationships/comments?include=comments.author HTTP/1.1
593
        // Accept: application/vnd.api+json
594
        //
595
        // In this case, the primary data would be a collection of resource identifier objects that
596
        // represent linkage to comments for an article, while the full comments and comment authors
597
        // would be returned as included data.
598
599 6
        foreach ($parser->parse($data, $includePaths) as $item) {
600 6
            if ($item instanceof ResourceInterface) {
601 4
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
602 1
                    assert($expectIncluded === true);
603 1
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
604 1
                        $writer->addResourceToIncluded($item, $filter);
605
                    }
606
                } else {
607 4
                    $writer->addIdentifierToData($item);
608 4
                    if ($expectIncluded === true) {
609 4
                        $writer->addResourceToIncluded($item, $filter);
610
                    }
611
                }
612 6
            } elseif ($item instanceof IdentifierInterface) {
613 1
                assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
614 1
                $writer->addIdentifierToData($item);
615 View Code Duplication
            } else {
616 6
                assert($item instanceof DocumentDataInterface);
617 6
                assert($item->getPosition()->getLevel() === 0);
618 6
                if ($item->isCollection() === true) {
619 2
                    $writer->setDataAsArray();
620 5
                } elseif ($item->isNull() === true) {
621 6
                    $writer->setNullToData();
622
                }
623
            }
624
        }
625
626
        // write footer
627 6
        $this->writeFooter($writer);
628
629 6
        $array = $writer->getDocument();
630
631 6
        return $array;
632
    }
633
634
    /**
635
     * @param ErrorInterface $error
636
     *
637
     * @return array
638
     */
639 4
    protected function encodeErrorToArray(ErrorInterface $error): array
640
    {
641 4
        $writer = $this->createErrorWriter();
642
643
        // write header
644 4
        $this->writeHeader($writer);
645
646
        // write body
647 4
        $writer->addError($error);
648
649
        // write footer
650 4
        $this->writeFooter($writer);
651
652 4
        $array = $writer->getDocument();
653
654 4
        return $array;
655
    }
656
657
    /**
658
     * @param iterable $errors
659
     *
660
     * @return array
661
     */
662 4
    protected function encodeErrorsToArray(iterable $errors): array
663
    {
664 4
        $writer = $this->createErrorWriter();
665
666
        // write header
667 4
        $this->writeHeader($writer);
668
669
        // write body
670 4
        foreach ($errors as $error) {
671 3
            assert($error instanceof ErrorInterface);
672 3
            $writer->addError($error);
673
        }
674
675
        // write footer
676 4
        $this->writeFooter($writer);
677
678
        // encode to json
679 4
        $array = $writer->getDocument();
680
681 4
        return $array;
682
    }
683
684
    /**
685
     * @param $meta
686
     *
687
     * @return array
688
     */
689 2
    protected function encodeMetaToArray($meta): array
690
    {
691 2
        $this->withMeta($meta);
692
693 2
        $writer = $this->getFactory()->createDocumentWriter();
694
695 2
        $writer->setUrlPrefix($this->getUrlPrefix());
696
697
        // write header
698 2
        $this->writeHeader($writer);
699
700
        // write footer
701 2
        $this->writeFooter($writer);
702
703
        // encode to json
704 2
        $array = $writer->getDocument();
705
706 2
        return $array;
707
    }
708
709
    /**
710
     * @param BaseWriterInterface $writer
711
     *
712
     * @return void
713
     */
714 79
    protected function writeHeader(BaseWriterInterface $writer): void
715
    {
716 79
        if ($this->hasMeta() === true) {
717 6
            $writer->setMeta($this->getMeta());
718
        }
719
720 79
        if ($this->hasJsonApiVersion() === true) {
721 2
            $writer->setJsonApiVersion($this->getJsonApiVersion());
722
        }
723
724 79
        if ($this->hasJsonApiMeta() === true) {
725 2
            $writer->setJsonApiMeta($this->getJsonApiMeta());
726
        }
727
728 79
        if ($this->hasLinks() === true) {
729 5
            $writer->setLinks($this->getLinks());
730
        }
731
732 79
        if ($this->hasProfile() === true) {
733 1
            $writer->setProfile($this->getProfile());
734
        }
735 79
    }
736
737
    /**
738
     * @param BaseWriterInterface $writer
739
     *
740
     * @return void
741
     */
742 77
    protected function writeFooter(BaseWriterInterface $writer): void
0 ignored issues
show
Unused Code introduced by
The parameter $writer is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
743
    {
744 77
    }
745
746
    /**
747
     * Encode array to JSON.
748
     *
749
     * @param array $document
750
     *
751
     * @return string
752
     */
753 73
    protected function encodeToJson(array $document): string
754
    {
755 73
        return json_encode($document, $this->getEncodeOptions(), $this->getEncodeDepth());
756
    }
757
758
    /**
759
     * @return string
760
     */
761 79
    protected function getUrlPrefix(): string
762
    {
763 79
        return $this->urlPrefix;
764
    }
765
766
    /**
767
     * @return array
768
     */
769 70
    protected function getIncludePaths(): array
770
    {
771 70
        return $this->includePaths;
772
    }
773
774
    /**
775
     * @return array
776
     */
777 70
    protected function getFieldSets(): array
778
    {
779 70
        return $this->fieldSets;
780
    }
781
782
    /**
783
     * @return int
784
     */
785 73
    protected function getEncodeOptions(): int
786
    {
787 73
        return $this->encodeOptions;
788
    }
789
790
    /**
791
     * @return int
792
     */
793 73
    protected function getEncodeDepth(): int
794
    {
795 73
        return $this->encodeDepth;
796
    }
797
798
    /**
799
     * @return DocumentWriterInterface
800
     */
801 70
    private function createDocumentWriter(): DocumentWriterInterface
802
    {
803 70
        $writer = $this->getFactory()->createDocumentWriter();
804 70
        $writer->setUrlPrefix($this->getUrlPrefix());
805
806 70
        return $writer;
807
    }
808
809
    /**
810
     * @return ErrorWriterInterface
811
     */
812 7
    private function createErrorWriter(): ErrorWriterInterface
813
    {
814 7
        $writer = $this->getFactory()->createErrorWriter();
815 7
        $writer->setUrlPrefix($this->getUrlPrefix());
816
817 7
        return $writer;
818
    }
819
820
    /**
821
     * @param iterable $iterable1
822
     * @param iterable $iterable2
823
     *
824
     * @return iterable
825
     */
826 1
    private function mergeIterables(iterable $iterable1, iterable $iterable2): iterable
827
    {
828 1
        yield from $iterable1;
829 1
        yield from $iterable2;
830 1
    }
831
}
832