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

IdentifierAndResource::cacheLinks()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 0
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 4
rs 9.9
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Neomerx\JsonApi\Parser;
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 IteratorAggregate;
22
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
23
use Neomerx\JsonApi\Contracts\Parser\IdentifierInterface as ParserIdentifierInterface;
24
use Neomerx\JsonApi\Contracts\Parser\ParserInterface;
25
use Neomerx\JsonApi\Contracts\Parser\PositionInterface;
26
use Neomerx\JsonApi\Contracts\Parser\RelationshipDataInterface;
27
use Neomerx\JsonApi\Contracts\Parser\ResourceInterface;
28
use Neomerx\JsonApi\Contracts\Schema\IdentifierInterface as SchemaIdentifierInterface;
29
use Neomerx\JsonApi\Contracts\Schema\LinkInterface;
30
use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
31
use Neomerx\JsonApi\Contracts\Schema\SchemaInterface;
32
use Neomerx\JsonApi\Exceptions\InvalidArgumentException;
33
use Neomerx\JsonApi\Exceptions\LogicException;
34
use Traversable;
35
use function Neomerx\JsonApi\I18n\format as _;
36
37
/**
38
 * @package Neomerx\JsonApi
39
 */
40
class IdentifierAndResource implements ResourceInterface
41
{
42
    /** @var string */
43
    public const MSG_NO_SCHEMA_FOUND = 'No Schema found for resource `%s` at path `%s`.';
44
45
    /** @var string */
46
    public const MSG_INVALID_OPERATION = 'Invalid operation.';
47
48
    /**
49
     * @var PositionInterface
50
     */
51
    private $position;
52
53
    /**
54
     * @var FactoryInterface
55
     */
56
    private $factory;
57
58
    /**
59
     * @var SchemaContainerInterface
60
     */
61
    private $schemaContainer;
62
63
    /**
64
     * @var SchemaInterface
65
     */
66
    private $schema;
67
68
    /**
69
     * @var mixed
70
     */
71
    private $data;
72
73
    /**
74
     * @var string
75
     */
76
    private $index;
77
78
    /**
79
     * @var string
80
     */
81
    private $type;
82
83
    /**
84
     * @var null|array
85
     */
86
    private $links = null;
87
88
    /**
89
     * @var null|array
90
     */
91
    private $relationshipsCache = null;
92
93
    /**
94
     * @param PositionInterface        $position
95
     * @param FactoryInterface         $factory
96
     * @param SchemaContainerInterface $container
97
     * @param mixed                    $data
98
     */
99 59
    public function __construct(
100
        PositionInterface $position,
101
        FactoryInterface $factory,
102
        SchemaContainerInterface $container,
103
        $data
104
    ) {
105 59
        $schema = $container->getSchema($data);
106
        $this
107 59
            ->setPosition($position)
108 59
            ->setFactory($factory)
109 59
            ->setSchemaContainer($container)
110 59
            ->setSchema($schema)
111 59
            ->setData($data);
112 59
    }
113
114
    /**
115
     * @inheritdoc
116
     */
117 59
    public function getPosition(): PositionInterface
118
    {
119 59
        return $this->position;
120
    }
121
122
    /**
123
     * @inheritdoc
124
     */
125 59
    public function getId(): ?string
126
    {
127 59
        return $this->index;
128
    }
129
130
    /**
131
     * @inheritdoc
132
     */
133 59
    public function getType(): string
134
    {
135 59
        return $this->type;
136
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141 31
    public function hasIdentifierMeta(): bool
142
    {
143 31
        return $this->getSchema()->hasIdentifierMeta($this->getData());
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149
    public function getIdentifierMeta()
150
    {
151
        return $this->getSchema()->getIdentifierMeta($this->getData());
152
    }
153
154
    /**
155
     * @inheritdoc
156
     */
157 56
    public function getAttributes(): iterable
158
    {
159 56
        return $this->getSchema()->getAttributes($this->getData());
160
    }
161
162
    /**
163
     * @inheritdoc
164
     */
165 59
    public function getRelationships(): iterable
166
    {
167 59
        if ($this->relationshipsCache !== null) {
168 56
            yield from $this->relationshipsCache;
169
170 55
            return;
171
        }
172
173 59
        $this->relationshipsCache = [];
174
175 59
        $currentType    = $this->getType();
176 59
        $currentPath    = $this->getPosition()->getPath();
177 59
        $nextLevel      = $this->getPosition()->getLevel() + 1;
178 59
        $nextPathPrefix = empty($currentPath) === true ? '' : $currentPath . PositionInterface::PATH_SEPARATOR;
179 59
        foreach ($this->getSchema()->getRelationships($this->getData()) as $name => $description) {
180 44
            assert(
181 44
                is_string($name) === true && empty($name) === false,
182 44
                "Relationship names for type `" . $currentType . '` should be non-empty strings.'
183
            );
184 44
            assert(
185 44
                is_array($description) === true && empty($description) === false,
186 44
                "Relationship `$name` for type `" . $currentType . '` should be a non-empty array.'
187
            );
188
189 44
            $hasData = array_key_exists(SchemaInterface::RELATIONSHIP_DATA, $description);
190
            // either no data or data should be array/object/null
191 44
            assert(
192 44
                $hasData === false ||
193
                (
194 34
                    is_array($data = $description[SchemaInterface::RELATIONSHIP_DATA]) === true ||
195 29
                    is_object($data) === true ||
196 44
                    $data === null
197
                )
198
            );
199
200 44
            $hasMeta = array_key_exists(SchemaInterface::RELATIONSHIP_META, $description);
201
202 44
            $addSelfLink    = $description[SchemaInterface::RELATIONSHIP_LINKS_SELF] ??
203 44
                $this->getSchema()->isAddSelfLinkInRelationshipByDefault();
204 44
            $addRelatedLink = $description[SchemaInterface::RELATIONSHIP_LINKS_RELATED] ??
205 44
                $this->getSchema()->isAddRelatedLinkInRelationshipByDefault();
206 44
            assert(is_bool($addSelfLink) === true || $addSelfLink instanceof LinkInterface);
207 44
            assert(is_bool($addRelatedLink) === true || $addRelatedLink instanceof LinkInterface);
208
209 44
            $schemaLinks = array_key_exists(SchemaInterface::RELATIONSHIP_LINKS, $description) === true ?
210 44
                $description[SchemaInterface::RELATIONSHIP_LINKS] : [];
211 44
            assert(is_array($schemaLinks));
212
213
            // if `self` or `related` link was given as LinkInterface merge it with the other links
214 44
            if (is_bool($addSelfLink) === false) {
215
                $extraSchemaLinks[LinkInterface::SELF] = $addSelfLink;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$extraSchemaLinks was never initialized. Although not strictly required by PHP, it is generally a good practice to add $extraSchemaLinks = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
216
                $addSelfLink                           = false;
217
            }
218 44
            if (is_bool($addRelatedLink) === false) {
219
                $extraSchemaLinks[LinkInterface::RELATED] = $addRelatedLink;
0 ignored issues
show
Bug introduced by
The variable $extraSchemaLinks does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
220
                $addRelatedLink                           = false;
221
            }
222 44
            if (isset($extraSchemaLinks) === true) {
223
                // IDE do not understand it's defined without he line below
224
                assert(isset($extraSchemaLinks));
225
                $schemaLinks = array_merge($extraSchemaLinks, $schemaLinks);
226
                unset($extraSchemaLinks);
227
            }
228 44
            assert(is_bool($addSelfLink) === true && is_bool($addRelatedLink) === true);
229
230 44
            $hasLinks = $addSelfLink === true || $addRelatedLink === true || empty($schemaLinks) === false;
231 44
            assert(
232 44
                $hasData || $hasMeta || $hasLinks,
233 44
                "Relationship `$name` for type `" . $currentType .
234 44
                '` MUST contain at least one of the following: links, data or meta.'
235
            );
236
237 44
            $nextPosition = $this->getFactory()->createPosition(
238 44
                $nextLevel,
239 44
                $nextPathPrefix . $name,
240 44
                $currentType,
241 44
                $name
242
            );
243
244 44
            $relationshipData = $hasData === true ? $this->parseData(
245 34
                $nextPosition,
246 34
                $description[SchemaInterface::RELATIONSHIP_DATA]
247 44
            ) : null;
248
249 44
            $relationship = $this->getFactory()->createRelationship(
250 44
                $nextPosition,
251 44
                $hasData,
252 44
                $relationshipData,
253 44
                $hasLinks,
254 44
                $hasLinks === true ? $this->parseLinks($name, $schemaLinks, $addSelfLink, $addRelatedLink) : null,
0 ignored issues
show
Documentation introduced by
$schemaLinks is of type array, but the function expects a object<Neomerx\JsonApi\Parser\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...
255 44
                $hasMeta,
256 44
                $hasMeta === true ? $description[SchemaInterface::RELATIONSHIP_META] : null
257
            );
258
259 44
            $this->relationshipsCache[$name] = $relationship;
260
261 44
            yield $name => $relationship;
262
        }
263 59
    }
264
265
    /**
266
     * @inheritdoc
267
     */
268 56
    public function hasLinks(): bool
269
    {
270 56
        $this->cacheLinks();
271
272 56
        return empty($this->links) === false;
273
    }
274
275
    /**
276
     * @inheritdoc
277
     */
278 55
    public function getLinks(): iterable
279
    {
280 55
        $this->cacheLinks();
281
282 55
        return $this->links;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->links; of type null|array adds the type array to the return on line 282 which is incompatible with the return type declared by the interface Neomerx\JsonApi\Contract...urceInterface::getLinks of type Neomerx\JsonApi\Contracts\Parser\iterable.
Loading history...
283
    }
284
285
    /**
286
     * @inheritdoc
287
     */
288 56
    public function hasResourceMeta(): bool
289
    {
290 56
        return $this->getSchema()->hasResourceMeta($this->getData());
291
    }
292
293
    /**
294
     * @inheritdoc
295
     */
296
    public function getResourceMeta()
297
    {
298
        return $this->getSchema()->getResourceMeta($this->getData());
299
    }
300
301
    /**
302
     * @inheritdoc
303
     */
304 59
    protected function setPosition(PositionInterface $position): self
305
    {
306 59
        assert($position->getLevel() >= ParserInterface::ROOT_LEVEL);
307
308 59
        $this->position = $position;
309
310 59
        return $this;
311
    }
312
313
    /**
314
     * @return FactoryInterface
315
     */
316 44
    protected function getFactory(): FactoryInterface
317
    {
318 44
        return $this->factory;
319
    }
320
321
    /**
322
     * @param FactoryInterface $factory
323
     *
324
     * @return self
325
     */
326 59
    protected function setFactory(FactoryInterface $factory): self
327
    {
328 59
        $this->factory = $factory;
329
330 59
        return $this;
331
    }
332
333
    /**
334
     * @return SchemaContainerInterface
335
     */
336 34
    protected function getSchemaContainer(): SchemaContainerInterface
337
    {
338 34
        return $this->schemaContainer;
339
    }
340
341
    /**
342
     * @param SchemaContainerInterface $container
343
     *
344
     * @return self
345
     */
346 59
    protected function setSchemaContainer(SchemaContainerInterface $container): self
347
    {
348 59
        $this->schemaContainer = $container;
349
350 59
        return $this;
351
    }
352
353
    /**
354
     * @return SchemaInterface
355
     */
356 59
    protected function getSchema(): SchemaInterface
357
    {
358 59
        return $this->schema;
359
    }
360
361
    /**
362
     * @param SchemaInterface $schema
363
     *
364
     * @return self
365
     */
366 59
    protected function setSchema(SchemaInterface $schema): self
367
    {
368 59
        $this->schema = $schema;
369
370 59
        return $this;
371
    }
372
373
    /**
374
     * @return mixed
375
     */
376 59
    protected function getData()
377
    {
378 59
        return $this->data;
379
    }
380
381
    /**
382
     * @param mixed $data
383
     *
384
     * @return self
385
     */
386 59
    protected function setData($data): self
387
    {
388 59
        $this->data  = $data;
389 59
        $this->index = $this->getSchema()->getId($data);
390 59
        $this->type  = $this->getSchema()->getType();
391
392 59
        return $this;
393
    }
394
395
    /**
396
     * Read and parse links from schema.
397
     */
398 56
    private function cacheLinks(): void
399
    {
400 56
        if ($this->links === null) {
401 56
            $this->links = [];
402 56
            foreach ($this->getSchema()->getLinks($this->getData()) as $name => $link) {
403 55
                assert(is_string($name) === true && empty($name) === false);
404 55
                assert($link instanceof LinkInterface);
405 55
                $this->links[$name] = $link;
406
            }
407
        }
408 56
    }
409
410
    /**
411
     * @param PositionInterface $position
412
     * @param mixed             $data
413
     *
414
     * @return RelationshipDataInterface
415
     */
416 34
    private function parseData(
417
        PositionInterface $position,
418
        $data
419
    ): RelationshipDataInterface {
420
        // support if data is callable (e.g. a closure used to postpone actual data reading)
421 34
        if (is_callable($data) === true) {
422 1
            $data = call_user_func($data);
423
        }
424
425 34
        if ($this->getSchemaContainer()->hasSchema($data) === true) {
426 23
            return $this->createRelationshipDataIsResource($position, $data);
427 33
        } elseif ($data instanceof SchemaIdentifierInterface) {
428 1
            return $this->createRelationshipDataIsIdentifier($position, $data);
429 32
        } elseif (is_array($data) === true) {
430 30
            return $this->createRelationshipDataIsCollection($position, $data);
0 ignored issues
show
Documentation introduced by
$data is of type array, but the function expects a object<Neomerx\JsonApi\Parser\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...
431 6
        } elseif ($data instanceof Traversable) {
432 1
            return $this->createRelationshipDataIsCollection(
433 1
                $position,
434 1
                $data instanceof IteratorAggregate ? $data->getIterator() : $data
0 ignored issues
show
Documentation introduced by
$data instanceof \Iterat...->getIterator() : $data is of type object<Traversable>, but the function expects a object<Neomerx\JsonApi\Parser\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...
435
            );
436 5
        } elseif ($data === null) {
437 5
            return $this->createRelationshipDataIsNull();
438
        }
439
440 1
        throw new InvalidArgumentException(
441 1
            _(static::MSG_NO_SCHEMA_FOUND, get_class($data), $position->getPath())
442
        );
443
    }
444
445
    /**
446
     * @param string   $relationshipName
447
     * @param iterable $schemaLinks
448
     * @param bool     $addSelfLink
449
     * @param bool     $addRelatedLink
450
     *
451
     * @return iterable
452
     */
453 21
    private function parseLinks(
454
        string $relationshipName,
455
        iterable $schemaLinks,
456
        bool $addSelfLink,
457
        bool $addRelatedLink
458
    ): iterable {
459 21
        $gotSelf    = false;
460 21
        $gotRelated = false;
461
462 21
        foreach ($schemaLinks as $name => $link) {
463 11
            assert($link instanceof LinkInterface);
464 11
            if ($name === LinkInterface::SELF) {
465 6
                assert($gotSelf === false);
466 6
                $gotSelf     = true;
467 6
                $addSelfLink = false;
468 5
            } elseif ($name === LinkInterface::RELATED) {
469
                assert($gotRelated === false);
470
                $gotRelated     = true;
471
                $addRelatedLink = false;
472
            }
473
474 11
            yield $name => $link;
475
        }
476
477 21
        if ($addSelfLink === true) {
478 15
            $link = $this->getSchema()->getRelationshipSelfLink($this->getData(), $relationshipName);
479 15
            yield LinkInterface::SELF => $link;
480 15
            $gotSelf = true;
481
        }
482 21
        if ($addRelatedLink === true) {
483 13
            $link = $this->getSchema()->getRelationshipRelatedLink($this->getData(), $relationshipName);
484 13
            yield LinkInterface::RELATED => $link;
485 13
            $gotRelated = true;
486
        }
487
488
        // spec: check links has at least one of the following: self or related
489 21
        assert($gotSelf || $gotRelated);
490 21
    }
491
492
    /**
493
     * @param PositionInterface $position
494
     * @param mixed             $resource
495
     *
496
     * @return RelationshipDataInterface
497
     */
498 23
    private function createRelationshipDataIsResource(
499
        PositionInterface $position,
500
        $resource
501
    ): RelationshipDataInterface {
502 23
        $factory         = $this->getFactory();
503 23
        $schemaContainer = $this->getSchemaContainer();
504
505
        return new class (
506 23
            $position,
507 23
            $factory,
508 23
            $schemaContainer,
509 23
            $resource
510 23
        ) extends BaseRelationshipData implements RelationshipDataInterface
511
        {
512
            /**
513
             * @var mixed
514
             */
515
            private $resource;
516
517
            /**
518
             * @var null|ResourceInterface
519
             */
520
            private $parsedResource = null;
521
522
            /**
523
             * @param PositionInterface        $position
524
             * @param FactoryInterface         $factory
525
             * @param SchemaContainerInterface $schemaContainer
526
             * @param mixed                    $resource
527
             */
528
            public function __construct(
529
                PositionInterface $position,
530
                FactoryInterface $factory,
531
                SchemaContainerInterface $schemaContainer,
532
                $resource
533
            ) {
534 23
                parent::__construct($position, $factory, $schemaContainer);
535
536 23
                $this->resource = $resource;
537 23
            }
538
539
            /**
540
             * @inheritdoc
541
             */
542
            public function isCollection(): bool
543
            {
544
                return false;
545
            }
546
547
            /**
548
             * @inheritdoc
549
             */
550
            public function isNull(): bool
551
            {
552
                return false;
553
            }
554
555
            /**
556
             * @inheritdoc
557
             */
558
            public function isResource(): bool
559
            {
560 22
                return true;
561
            }
562
563
            /**
564
             * @inheritdoc
565
             */
566
            public function isIdentifier(): bool
567
            {
568
                return false;
569
            }
570
571
            /**
572
             * @inheritdoc
573
             */
574
            public function getIdentifier(): ParserIdentifierInterface
575
            {
576
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
577
            }
578
579
            /**
580
             * @inheritdoc
581
             */
582
            public function getIdentifiers(): iterable
583
            {
584
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
585
            }
586
587
            /**
588
             * @inheritdoc
589
             */
590
            public function getResource(): ResourceInterface
591
            {
592 22
                if ($this->parsedResource === null) {
593 22
                    $this->parsedResource = $this->createParsedResource($this->resource);
594
                }
595
596 22
                return $this->parsedResource;
597
            }
598
599
            /**
600
             * @inheritdoc
601
             */
602
            public function getResources(): iterable
603
            {
604
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
605
            }
606
        };
607
    }
608
609
    /**
610
     * @param PositionInterface         $position
611
     * @param SchemaIdentifierInterface $identifier
612
     *
613
     * @return RelationshipDataInterface
614
     */
615 1
    private function createRelationshipDataIsIdentifier(
616
        PositionInterface $position,
617
        SchemaIdentifierInterface $identifier
618
    ): RelationshipDataInterface {
619 1
        $factory         = $this->getFactory();
620 1
        $schemaContainer = $this->getSchemaContainer();
621
622
        return new class (
623 1
            $position,
624 1
            $factory,
625 1
            $schemaContainer,
626 1
            $identifier
627 1
        ) extends BaseRelationshipData implements RelationshipDataInterface
628
        {
629
            /**
630
             * @var mixed
631
             */
632
            private $identifier;
633
634
            /**
635
             * @var null|ParserIdentifierInterface
636
             */
637
            private $parsedIdentifier = null;
638
639
            /**
640
             * @param PositionInterface         $position
641
             * @param FactoryInterface          $factory
642
             * @param SchemaContainerInterface  $schemaContainer
643
             * @param SchemaIdentifierInterface $identifier
644
             */
645
            public function __construct(
646
                PositionInterface $position,
647
                FactoryInterface $factory,
648
                SchemaContainerInterface $schemaContainer,
649
                SchemaIdentifierInterface $identifier
650
            ) {
651 1
                parent::__construct($position, $factory, $schemaContainer);
652
653 1
                $this->identifier = $identifier;
654 1
            }
655
656
            /**
657
             * @inheritdoc
658
             */
659
            public function isCollection(): bool
660
            {
661
                return false;
662
            }
663
664
            /**
665
             * @inheritdoc
666
             */
667
            public function isNull(): bool
668
            {
669
                return false;
670
            }
671
672
            /**
673
             * @inheritdoc
674
             */
675
            public function isResource(): bool
676
            {
677 1
                return false;
678
            }
679
680
            /**
681
             * @inheritdoc
682
             */
683
            public function isIdentifier(): bool
684
            {
685 1
                return true;
686
            }
687
688
            /**
689
             * @inheritdoc
690
             */
691
            public function getIdentifier(): ParserIdentifierInterface
692
            {
693 1
                if ($this->parsedIdentifier === null) {
694 1
                    $this->parsedIdentifier = $this->createParsedIdentifier($this->identifier);
695
                }
696
697 1
                return $this->parsedIdentifier;
698
            }
699
700
            /**
701
             * @inheritdoc
702
             */
703
            public function getIdentifiers(): iterable
704
            {
705
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
706
            }
707
708
            /**
709
             * @inheritdoc
710
             */
711
            public function getResource(): ResourceInterface
712
            {
713
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
714
            }
715
716
            /**
717
             * @inheritdoc
718
             */
719
            public function getResources(): iterable
720
            {
721
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
722
            }
723
        };
724
    }
725
726
    /**
727
     * @param PositionInterface $position
728
     * @param iterable          $resources
729
     *
730
     * @return RelationshipDataInterface
731
     */
732 31
    private function createRelationshipDataIsCollection(
733
        PositionInterface $position,
734
        iterable $resources
735
    ): RelationshipDataInterface {
736 31
        $factory         = $this->getFactory();
737 31
        $schemaContainer = $this->getSchemaContainer();
738
739
        return new class (
740 31
            $position,
741 31
            $factory,
742 31
            $schemaContainer,
743 31
            $resources
744 31
        ) extends BaseRelationshipData implements RelationshipDataInterface
745
        {
746
            /**
747
             * @var iterable
748
             */
749
            private $resources;
750
751
            /**
752
             * @var iterable
753
             */
754
            private $parsedResources = null;
755
756
            /**
757
             * @param PositionInterface        $position
758
             * @param FactoryInterface         $factory
759
             * @param SchemaContainerInterface $schemaContainer
760
             * @param iterable                 $resources
761
             */
762
            public function __construct(
763
                PositionInterface $position,
764
                FactoryInterface $factory,
765
                SchemaContainerInterface $schemaContainer,
766
                iterable $resources
767
            ) {
768 31
                parent::__construct($position, $factory, $schemaContainer);
769
770 31
                $this->resources = $resources;
771 31
            }
772
773
            /**
774
             * @inheritdoc
775
             */
776
            public function isCollection(): bool
777
            {
778 30
                return true;
779
            }
780
781
            /**
782
             * @inheritdoc
783
             */
784
            public function isNull(): bool
785
            {
786
                return false;
787
            }
788
789
            /**
790
             * @inheritdoc
791
             */
792
            public function isResource(): bool
793
            {
794 30
                return false;
795
            }
796
797
            /**
798
             * @inheritdoc
799
             */
800
            public function isIdentifier(): bool
801
            {
802 27
                return false;
803
            }
804
805
            /**
806
             * @inheritdoc
807
             */
808
            public function getIdentifier(): ParserIdentifierInterface
809
            {
810
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
811
            }
812
813
            /**
814
             * @inheritdoc
815
             */
816
            public function getIdentifiers(): iterable
817
            {
818 27
                return $this->getResources();
819
            }
820
821
            /**
822
             * @inheritdoc
823
             */
824
            public function getResource(): ResourceInterface
825
            {
826
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
827
            }
828
829
            /**
830
             * @inheritdoc
831
             */
832
            public function getResources(): iterable
833
            {
834 30
                if ($this->parsedResources === null) {
835 30
                    foreach ($this->resources as $resource) {
836 27
                        $parsedResource          = $this->createParsedResource($resource);
837 27
                        $this->parsedResources[] = $parsedResource;
838
839 27
                        yield $parsedResource;
840
                    }
841
842 30
                    return;
843
                }
844
845 15
                yield from $this->parsedResources;
846 14
            }
847
        };
848
    }
849
850
    /**
851
     * @return RelationshipDataInterface
852
     */
853
    private function createRelationshipDataIsNull(): RelationshipDataInterface
854
    {
855
        return new class() implements RelationshipDataInterface
856
        {
857
            /**
858
             * @inheritdoc
859
             */
860 5
            public function isCollection(): bool
861
            {
862 5
                return false;
863
            }
864
865
            /**
866
             * @inheritdoc
867
             */
868 5
            public function isNull(): bool
869
            {
870 5
                return true;
871
            }
872
873
            /**
874
             * @inheritdoc
875
             */
876 5
            public function isResource(): bool
877
            {
878 5
                return false;
879
            }
880
881
            /**
882
             * @inheritdoc
883
             */
884 5
            public function isIdentifier(): bool
885
            {
886 5
                return false;
887
            }
888
889
            /**
890
             * @inheritdoc
891
             */
892
            public function getIdentifier(): ParserIdentifierInterface
893
            {
894
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
895
            }
896
897
            /**
898
             * @inheritdoc
899
             */
900
            public function getIdentifiers(): iterable
901
            {
902
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
903
            }
904
905
            /**
906
             * @inheritdoc
907
             */
908
            public function getResource(): ResourceInterface
909
            {
910
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
911
            }
912
913
            /**
914
             * @inheritdoc
915
             */
916
            public function getResources(): iterable
917
            {
918
                throw new LogicException(_(IdentifierAndResource::MSG_INVALID_OPERATION));
919
            }
920
        };
921
    }
922
}
923