Completed
Branch next (29fadd)
by Neomerx
03:15
created

Encoder::mergeIterables()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 2
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 78
    public function __construct(
145
        FactoryInterface $factory,
146
        SchemaContainerInterface $container
147
    ) {
148 78
        $this->factory   = $factory;
149 78
        $this->container = $container;
150
151 78
        $this->reset();
152 78
    }
153
154
    /**
155
     * Create encoder instance.
156
     *
157
     * @param array $schemas Schema providers.
158
     *
159
     * @return EncoderInterface
160
     */
161 78
    public static function instance(array $schemas = []): EncoderInterface
162
    {
163 78
        $factory   = static::createFactory();
164 78
        $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 78
        $encoder   = $factory->createEncoder($container);
166
167 78
        return $encoder;
168
    }
169
170
    /**
171
     * @inheritdoc
172
     */
173 78
    public function withUrlPrefix(string $prefix): EncoderInterface
174
    {
175 78
        $this->urlPrefix = $prefix;
176
177 78
        return $this;
178
    }
179
180
    /**
181
     * @inheritdoc
182
     */
183 78
    public function withIncludedPaths(iterable $paths): EncoderInterface
184
    {
185 78
        assert(
186 78
            call_user_func(
187
                function (array $paths): bool {
188 78
                    $pathsOk = true;
189 78
                    foreach ($paths as $path) {
190 22
                        $pathsOk = $pathsOk === true && is_string($path) === true && empty($path) === false;
191
                    }
192
193 78
                    return $pathsOk;
194 78
                },
195 78
                $paths
196
            )
197
        );
198
199 78
        $this->includePaths = $paths;
200
201 78
        return $this;
202
    }
203
204
    /**
205
     * @inheritdoc
206
     */
207 78
    public function withFieldSets(array $fieldSets): EncoderInterface
208
    {
209 78
        $this->fieldSets = $fieldSets;
210
211 78
        return $this;
212
    }
213
214
    /**
215
     * @inheritdoc
216
     */
217 78
    public function withEncodeOptions(int $options): EncoderInterface
218
    {
219 78
        $this->encodeOptions = $options;
220
221 78
        return $this;
222
    }
223
224
    /**
225
     * @inheritdoc
226
     */
227 78
    public function withEncodeDepth(int $depth): EncoderInterface
228
    {
229 78
        assert($depth > 0);
230
231 78
        $this->encodeDepth = $depth;
232
233 78
        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 63
    public function encodeData($data): string
324
    {
325
        // encode to json
326 63
        $array  = $this->encodeDataToArray($data);
327 60
        $result = $this->encodeToJson($array);
328
329 60
        $this->reset();
330
331 60
        return $result;
332
    }
333
334
    /**
335
     * @inheritdoc
336
     */
337 3
    public function encodeIdentifiers($data): string
338
    {
339
        // encode to json
340 3
        $array  = $this->encodeIdentifiersToArray($data);
341 3
        $result = $this->encodeToJson($array);
342
343 3
        $this->reset();
344
345 3
        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 71
    protected static function createFactory(): FactoryInterface
394
    {
395 71
        return new Factory();
396
    }
397
398
    /**
399
     * @return SchemaContainerInterface
400
     */
401 67
    protected function getSchemaContainer(): SchemaContainerInterface
402
    {
403 67
        return $this->container;
404
    }
405
406
    /**
407
     * @return FactoryInterface
408
     */
409 76
    protected function getFactory(): FactoryInterface
410
    {
411 76
        return $this->factory;
412
    }
413
414
    /**
415
     * Reset to initial state.
416
     */
417 78
    protected function reset(): void
418
    {
419 78
        $this->links          = null;
420 78
        $this->profile        = null;
421 78
        $this->hasMeta        = false;
422 78
        $this->meta           = null;
423 78
        $this->jsonApiVersion = null;
424 78
        $this->jsonApiMeta    = null;
425 78
        $this->hasJsonApiMeta = false;
426
427
        $this
428 78
            ->withUrlPrefix(static::DEFAULT_URL_PREFIX)
429 78
            ->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 78
            ->withFieldSets(static::DEFAULT_FIELD_SET_FILTERS)
431 78
            ->withEncodeOptions(static::DEFAULT_JSON_ENCODE_OPTIONS)
432 78
            ->withEncodeDepth(static::DEFAULT_JSON_ENCODE_DEPTH);
433 78
    }
434
435
    /**
436
     * @return bool
437
     */
438 76
    protected function hasLinks(): bool
439
    {
440 76
        return $this->links !== null;
441
    }
442
443
    /**
444
     * @return bool
445
     */
446 76
    protected function hasProfile(): bool
447
    {
448 76
        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 76
    protected function hasMeta(): bool
471
    {
472 76
        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 76
    protected function hasJsonApiVersion(): bool
487
    {
488 76
        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 76
    protected function hasJsonApiMeta(): bool
503
    {
504 76
        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 64
    protected function encodeDataToArray($data): array
523
    {
524 64
        if (is_array($data) === false && is_object($data) === false && $data !== null) {
525 1
            throw new InvalidArgumentException();
526
        }
527
528 63
        $parser = $this->getFactory()->createParser($this->getSchemaContainer());
529 63
        $writer = $this->createDocumentWriter();
530 63
        $filter = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
531
532
        // write header
533 63
        $this->writeHeader($writer);
534
535
        // write body
536 63
        foreach ($parser->parse($data, $this->getIncludePaths()) as $item) {
537 62
            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 62
            } elseif ($item instanceof IdentifierInterface) {
546
                assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
547
                $writer->addIdentifierToData($item);
548 View Code Duplication
            } else {
549 62
                assert($item instanceof DocumentDataInterface);
550 62
                if ($item->isCollection() === true) {
551 20
                    $writer->setDataAsArray();
552 43
                } elseif ($item->isNull() === true) {
553 62
                    $writer->setNullToData();
554
                }
555
            }
556
        }
557
558
        // write footer
559 61
        $this->writeFooter($writer);
560
561 61
        $array = $writer->getDocument();
562
563 61
        return $array;
564
    }
565
566
    /**
567
     * @param object|iterable|null $data Data to encode.
568
     *
569
     * @return array
570
     *
571
     * @SuppressWarnings(PHPMD.ElseExpression)
572
     */
573 4
    protected function encodeIdentifiersToArray($data): array
574
    {
575 4
        $parser = $this->getFactory()->createParser($this->getSchemaContainer());
576 4
        $writer = $this->createDocumentWriter();
577 4
        $filter = $this->getFactory()->createFieldSetFilter($this->getFieldSets());
578
579
        // write header
580 4
        $this->writeHeader($writer);
581
582
        // write body
583 4
        $includePaths   = $this->getIncludePaths();
584 4
        $expectIncluded = empty($includePaths) === false;
585
586
        // https://github.com/neomerx/json-api/issues/218
587
        //
588
        // if we expect included resources we have to include top level resources in `included` as well
589
        // Spec:
590
        //
591
        // GET /articles/1/relationships/comments?include=comments.author HTTP/1.1
592
        // Accept: application/vnd.api+json
593
        //
594
        // In this case, the primary data would be a collection of resource identifier objects that
595
        // represent linkage to comments for an article, while the full comments and comment authors
596
        // would be returned as included data.
597
598 4
        foreach ($parser->parse($data, $includePaths) as $item) {
599 4
            if ($item instanceof ResourceInterface) {
600 4
                if ($item->getPosition()->getLevel() > ParserInterface::ROOT_LEVEL) {
601 1
                    assert($expectIncluded === true);
602 1
                    if ($filter->shouldOutputRelationship($item->getPosition()) === true) {
603 1
                        $writer->addResourceToIncluded($item, $filter);
604
                    }
605
                } else {
606 4
                    $writer->addIdentifierToData($item);
607 4
                    if ($expectIncluded === true) {
608 4
                        $writer->addResourceToIncluded($item, $filter);
609
                    }
610
                }
611 4
            } elseif ($item instanceof IdentifierInterface) {
612
                assert($item->getPosition()->getLevel() <= ParserInterface::ROOT_LEVEL);
613
                $writer->addIdentifierToData($item);
614 View Code Duplication
            } else {
615 4
                assert($item instanceof DocumentDataInterface);
616 4
                if ($item->isCollection() === true) {
617 1
                    $writer->setDataAsArray();
618 3
                } elseif ($item->isNull() === true) {
619 4
                    $writer->setNullToData();
620
                }
621
            }
622
        }
623
624
        // write footer
625 4
        $this->writeFooter($writer);
626
627 4
        $array = $writer->getDocument();
628
629 4
        return $array;
630
    }
631
632
    /**
633
     * @param ErrorInterface $error
634
     *
635
     * @return array
636
     */
637 4
    protected function encodeErrorToArray(ErrorInterface $error): array
638
    {
639 4
        $writer = $this->createErrorWriter();
640
641
        // write header
642 4
        $this->writeHeader($writer);
643
644
        // write body
645 4
        $writer->addError($error);
646
647
        // write footer
648 4
        $this->writeFooter($writer);
649
650 4
        $array = $writer->getDocument();
651
652 4
        return $array;
653
    }
654
655
    /**
656
     * @param iterable $errors
657
     *
658
     * @return array
659
     */
660 4
    protected function encodeErrorsToArray(iterable $errors): array
661
    {
662 4
        $writer = $this->createErrorWriter();
663
664
        // write header
665 4
        $this->writeHeader($writer);
666
667
        // write body
668 4
        foreach ($errors as $error) {
669 3
            assert($error instanceof ErrorInterface);
670 3
            $writer->addError($error);
671
        }
672
673
        // write footer
674 4
        $this->writeFooter($writer);
675
676
        // encode to json
677 4
        $array = $writer->getDocument();
678
679 4
        return $array;
680
    }
681
682
    /**
683
     * @param $meta
684
     *
685
     * @return array
686
     */
687 2
    protected function encodeMetaToArray($meta): array
688
    {
689 2
        $this->withMeta($meta);
690
691 2
        $writer = $this->getFactory()->createDocumentWriter();
692
693 2
        $writer->setUrlPrefix($this->getUrlPrefix());
694
695
        // write header
696 2
        $this->writeHeader($writer);
697
698
        // write footer
699 2
        $this->writeFooter($writer);
700
701
        // encode to json
702 2
        $array = $writer->getDocument();
703
704 2
        return $array;
705
    }
706
707
    /**
708
     * @param BaseWriterInterface $writer
709
     *
710
     * @return void
711
     */
712 76
    protected function writeHeader(BaseWriterInterface $writer): void
713
    {
714 76
        if ($this->hasMeta() === true) {
715 6
            $writer->setMeta($this->getMeta());
716
        }
717
718 76
        if ($this->hasJsonApiVersion() === true) {
719 2
            $writer->setJsonApiVersion($this->getJsonApiVersion());
720
        }
721
722 76
        if ($this->hasJsonApiMeta() === true) {
723 2
            $writer->setJsonApiMeta($this->getJsonApiMeta());
724
        }
725
726 76
        if ($this->hasLinks() === true) {
727 5
            $writer->setLinks($this->getLinks());
728
        }
729
730 76
        if ($this->hasProfile() === true) {
731 1
            $writer->setProfile($this->getProfile());
732
        }
733 76
    }
734
735
    /**
736
     * @param BaseWriterInterface $writer
737
     *
738
     * @return void
739
     */
740 74
    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...
741
    {
742 74
    }
743
744
    /**
745
     * Encode array to JSON.
746
     *
747
     * @param array $document
748
     *
749
     * @return string
750
     */
751 70
    protected function encodeToJson(array $document): string
752
    {
753 70
        return json_encode($document, $this->getEncodeOptions(), $this->getEncodeDepth());
754
    }
755
756
    /**
757
     * @return string
758
     */
759 76
    protected function getUrlPrefix(): string
760
    {
761 76
        return $this->urlPrefix;
762
    }
763
764
    /**
765
     * @return array
766
     */
767 67
    protected function getIncludePaths(): array
768
    {
769 67
        return $this->includePaths;
770
    }
771
772
    /**
773
     * @return array
774
     */
775 67
    protected function getFieldSets(): array
776
    {
777 67
        return $this->fieldSets;
778
    }
779
780
    /**
781
     * @return int
782
     */
783 70
    protected function getEncodeOptions(): int
784
    {
785 70
        return $this->encodeOptions;
786
    }
787
788
    /**
789
     * @return int
790
     */
791 70
    protected function getEncodeDepth(): int
792
    {
793 70
        return $this->encodeDepth;
794
    }
795
796
    /**
797
     * @return DocumentWriterInterface
798
     */
799 67
    private function createDocumentWriter(): DocumentWriterInterface
800
    {
801 67
        $writer = $this->getFactory()->createDocumentWriter();
802 67
        $writer->setUrlPrefix($this->getUrlPrefix());
803
804 67
        return $writer;
805
    }
806
807
    /**
808
     * @return ErrorWriterInterface
809
     */
810 7
    private function createErrorWriter(): ErrorWriterInterface
811
    {
812 7
        $writer = $this->getFactory()->createErrorWriter();
813 7
        $writer->setUrlPrefix($this->getUrlPrefix());
814
815 7
        return $writer;
816
    }
817
818
    /**
819
     * @param iterable $iterable1
820
     * @param iterable $iterable2
821
     *
822
     * @return iterable
823
     */
824
    private function mergeIterables(iterable $iterable1, iterable $iterable2): iterable
825
    {
826
        yield from $iterable1;
827
        yield from $iterable2;
828
    }
829
}
830