Completed
Pull Request — master (#1036)
by Hector
02:56
created

ItemNormalizer::normalize()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 51
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 51
rs 8.8981
c 0
b 0
f 0
cc 4
eloc 25
nc 5
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ApiPlatform\Core\JsonApi\Serializer;
13
14
use ApiPlatform\Core\Api\IriConverterInterface;
15
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
16
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
17
use ApiPlatform\Core\Exception\InvalidArgumentException;
18
use ApiPlatform\Core\Exception\RuntimeException;
19
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
20
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
21
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
22
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
23
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
24
use ApiPlatform\Core\Serializer\ContextTrait;
25
use ApiPlatform\Core\Util\ClassInfoTrait;
26
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
27
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
28
29
/**
30
 * Converts between objects and array.
31
 *
32
 * @author Kévin Dunglas <[email protected]>
33
 * @author Amrouche Hamza <[email protected]>
34
 */
35
final class ItemNormalizer extends AbstractItemNormalizer
36
{
37
    use ContextTrait;
38
    use ClassInfoTrait;
39
40
    const FORMAT = 'jsonapi';
41
42
    private $componentsCache = [];
43
44
    private $resourceMetadataFactory;
45
46
    private $itemDataProvider;
47
48 View Code Duplication
    public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
49
        PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
50
        PropertyMetadataFactoryInterface $propertyMetadataFactory,
51
        IriConverterInterface $iriConverter,
52
        ResourceClassResolverInterface $resourceClassResolver,
53
        PropertyAccessorInterface $propertyAccessor = null,
54
        NameConverterInterface $nameConverter = null,
55
        ResourceMetadataFactoryInterface $resourceMetadataFactory,
56
        ItemDataProviderInterface $itemDataProvider
57
    ) {
58
        parent::__construct(
59
            $propertyNameCollectionFactory,
60
            $propertyMetadataFactory,
61
            $iriConverter,
62
            $resourceClassResolver,
63
            $propertyAccessor,
64
            $nameConverter
65
        );
66
67
        $this->resourceMetadataFactory = $resourceMetadataFactory;
68
        $this->itemDataProvider = $itemDataProvider;
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function supportsNormalization($data, $format = null)
75
    {
76
        return self::FORMAT === $format && parent::supportsNormalization($data, $format);
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function normalize($object, $format = null, array $context = [])
83
    {
84
        $context['cache_key'] = $this->getCacheKey($format, $context);
85
86
        // Get and populate attributes data
87
        $objectAttributesData = parent::normalize($object, $format, $context);
88
89
        if (!is_array($objectAttributesData)) {
90
            return $objectAttributesData;
91
        }
92
93
        // Get and populate identifier if existent
94
        $identifier = $this->getIdentifierFromItem($object);
95
96
        // Get and populate item type
97
        $resourceClass = $this->resourceClassResolver->getResourceClass(
98
            $object,
99
            $context['resource_class'] ?? null,
100
            true
101
        );
102
        $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
103
104
        // Get and populate relations
105
        $components = $this->getComponents($object, $format, $context);
106
        $objectRelationshipsData = $this->getPopulatedRelations(
107
            $object,
108
            $format,
109
            $context,
110
            $components
111
        );
112
113
        // TODO: Pending population of links
114
        // $item = $this->populateRelation($item, $object, $format, $context, $components, 'links');
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
115
116
        $item = [
117
            // The id attribute must be a string
118
            // See: http://jsonapi.org/format/#document-resource-object-identification
119
            'id' => (string) $identifier,
120
            'type' => $resourceMetadata->getShortName(),
121
        ];
122
123
        if ($objectAttributesData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $objectAttributesData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
124
            $item['attributes'] = $objectAttributesData;
125
        }
126
127
        if ($objectRelationshipsData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $objectRelationshipsData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
128
            $item['relationships'] = $objectRelationshipsData;
129
        }
130
131
        return ['data' => $item];
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function supportsDenormalization($data, $type, $format = null)
138
    {
139
        return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format);
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function denormalize($data, $class, $format = null, array $context = [])
146
    {
147
        // TODO: Test what is this about
148
        // Avoid issues with proxies if we populated the object
149
        // if (isset($data['data']['id']) && !isset($context['object_to_populate'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
83% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
150
        //     if (isset($context['api_allow_update']) && true !== $context['api_allow_update']) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
151
        //         throw new InvalidArgumentException('Update is not allowed for this operation.');
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
152
        //     }
153
154
        //     $context['object_to_populate'] = $this->iriConverter->getItemFromIri(
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
155
        //         $data['data']['id'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
89% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
156
        //         $context + ['fetch_data' => false]
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
157
        //     );
158
        // }
159
160
        // Approach #1
161
        // Merge attributes and relations previous to apply parents denormalizing
162
        $dataToDenormalize = array_merge(
163
            isset($data['data']['attributes']) ?
164
                $data['data']['attributes'] : [],
165
            isset($data['data']['relationships']) ?
166
                $data['data']['relationships'] : []
167
        );
168
169
        return parent::denormalize(
170
            $dataToDenormalize,
171
            $class,
172
            $format,
173
            $context
174
        );
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    protected function getAttributes($object, $format, array $context)
181
    {
182
        return $this->getComponents($object, $format, $context)['attributes'];
183
    }
184
185
    /**
186
     * Gets JSON API components of the resource: attributes, relationships, meta and links.
187
     *
188
     * @param object      $object
189
     * @param string|null $format
190
     * @param array       $context
191
     *
192
     * @return array
193
     */
194
    private function getComponents($object, string $format = null, array $context)
195
    {
196
        if (isset($this->componentsCache[$context['cache_key']])) {
197
            return $this->componentsCache[$context['cache_key']];
198
        }
199
200
        $attributes = parent::getAttributes($object, $format, $context);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (getAttributes() instead of getComponents()). Are you sure this is correct? If so, you might want to change this to $this->getAttributes().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
201
202
        $options = $this->getFactoryOptions($context);
203
204
        $shortName = $className = '';
205
206
        $components = [
207
            'links' => [],
208
            'relationships' => [],
209
            'attributes' => [],
210
            'meta' => [],
211
        ];
212
213
        foreach ($attributes as $attribute) {
214
            $propertyMetadata = $this
215
                ->propertyMetadataFactory
216
                ->create($context['resource_class'], $attribute, $options);
217
218
            $type = $propertyMetadata->getType();
219
            $isOne = $isMany = false;
220
221
            if (null !== $type) {
222 View Code Duplication
                if ($type->isCollection()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
                    $valueType = $type->getCollectionValueType();
224
225
                    $isMany = null !== $valueType
226
                        && ($className = $valueType->getClassName())
227
                        && $this->resourceClassResolver->isResourceClass($className);
228
                } else {
229
                    $className = $type->getClassName();
230
231
                    $isOne = null !== $className
232
                        && $this->resourceClassResolver->isResourceClass($className);
233
                }
234
235
                $shortName =
236
                    (
237
                        (
238
                            null !== $className
239
                                && $this->resourceClassResolver->isResourceClass($className)
240
                        )
241
                            ? $this->resourceMetadataFactory->create($className)->getShortName() :
242
                            ''
243
                    );
244
            }
245
246
            if (!$isOne && !$isMany) {
247
                $components['attributes'][] = $attribute;
248
249
                continue;
250
            }
251
252
            $relation = [
253
                'name' => $attribute,
254
                'type' => $shortName,
255
                'cardinality' => $isOne ? 'one' : 'many',
256
            ];
257
258
            $components['relationships'][] = $relation;
259
        }
260
261
        return $this->componentsCache[$context['cache_key']] = $components;
262
    }
263
264
    /**
265
     * Populates links and relationships keys.
266
     *
267
     * @param array       $data
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
268
     * @param object      $object
269
     * @param string|null $format
270
     * @param array       $context
271
     * @param array       $components
272
     * @param string      $type
273
     *
274
     * @return array
275
     */
276
    private function getPopulatedRelations(
277
        $object,
278
        string $format = null,
279
        array $context,
280
        array $components,
281
        string $type = 'relationships'
282
    ): array {
283
        $data = [];
284
285
        $identifier = '';
0 ignored issues
show
Unused Code introduced by
$identifier is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
286
        foreach ($components[$type] as $relationDataArray) {
287
            $attributeValue = $this->getAttributeValue(
288
                $object,
289
                $relationDataArray['name'],
290
                $format,
291
                $context
292
            );
293
294
            if (!$attributeValue) {
295
                continue;
296
            }
297
298
            $data[$relationDataArray['name']] = [
299
                'data' => [],
300
            ];
301
302
            // Many to one relationship
303
            if ('one' === $relationDataArray['cardinality']) {
304
                $data[$relationDataArray['name']] = $attributeValue;
305
306
                continue;
307
            }
308
309
            // Many to many relationship
310
            foreach ($attributeValue as $attributeValueElement) {
311
                if (!isset($attributeValueElement['data'])) {
312
                    throw new RuntimeException(sprintf(
313
                        'Expected \'data\' attribute in collection for attribute \'%s\'',
314
                        $relationDataArray['name']
315
                    ));
316
                }
317
318
                $data[$relationDataArray['name']]['data'][] = $attributeValueElement['data'];
319
            }
320
        }
321
322
        return $data;
323
    }
324
325
    /**
326
     * Gets the IRI of the given relation.
327
     *
328
     * @param array|string $rel
329
     *
330
     * @return string
331
     */
332
    private function getRelationIri($rel): string
333
    {
334
        return isset($rel['links']['self']) ? $rel['links']['self'] : $rel;
335
    }
336
337
    /**
338
     * Gets the cache key to use.
339
     *
340
     * @param string|null $format
341
     * @param array       $context
342
     *
343
     * @return bool|string
344
     */
345 View Code Duplication
    private function getCacheKey(string $format = null, array $context)
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
    {
347
        try {
348
            return md5($format.serialize($context));
349
        } catch (\Exception $exception) {
350
            // The context cannot be serialized, skip the cache
351
            return false;
352
        }
353
    }
354
355
    /**
356
     * Denormalizes a resource linkage relation.
357
     *
358
     * See: http://jsonapi.org/format/#document-resource-object-linkage
359
     *
360
     * @param string           $attributeName    [description]
361
     * @param PropertyMetadata $propertyMetadata [description]
362
     * @param string           $className        [description]
363
     * @param [type]           $data             [description]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
364
     * @param string|null      $format           [description]
365
     * @param array            $context          [description]
366
     *
367
     * @return [type] [description]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
368
     */
369
    protected function denormalizeRelation(
370
        string $attributeName,
371
        PropertyMetadata $propertyMetadata,
372
        string $className,
373
        $data,
374
        string $format = null,
375
        array $context
376
    ) {
377
        // Null is allowed for empty to-one relationships, see
378
        // http://jsonapi.org/format/#document-resource-object-linkage
379
        if (null === $data['data']) {
380
            return;
381
        }
382
383
        // TODO: Add tests
384
        // An empty array is allowed for empty to-many relationships, see
385
        // http://jsonapi.org/format/#document-resource-object-linkage
386
        if ([] === $data['data']) {
387
            return;
388
        }
389
390
        if (!isset($data['data'])) {
391
            throw new InvalidArgumentException(
392
                'Key \'data\' expected. Only resource linkage currently supported, see: http://jsonapi.org/format/#document-resource-object-linkage'
393
            );
394
        }
395
396
        $data = $data['data'];
397
398
        if (!is_array($data) || 2 !== count($data)) {
399
            throw new InvalidArgumentException(
400
                'Only resource linkage supported currently supported, see: http://jsonapi.org/format/#document-resource-object-linkage'
401
            );
402
        }
403
404
        if (!isset($data['id'])) {
405
            throw new InvalidArgumentException(
406
                'Only resource linkage supported currently supported, see: http://jsonapi.org/format/#document-resource-object-linkage'
407
            );
408
        }
409
410
        return $this->itemDataProvider->getItem(
411
            $this->resourceClassResolver->getResourceClass(null, $className),
412
            $data['id']
413
        );
414
    }
415
416
    /**
417
     * Normalizes a relation as resource linkage relation.
418
     *
419
     * See: http://jsonapi.org/format/#document-resource-object-linkage
420
     *
421
     * For example, it may return the following array:
422
     *
423
     * [
424
     *     'data' => [
425
     *         'type' => 'dummy',
426
     *         'id' => '1'
427
     *     ]
428
     * ]
429
     *
430
     * @param PropertyMetadata $propertyMetadata
431
     * @param mixed            $relatedObject
432
     * @param string           $resourceClass
433
     * @param string|null      $format
434
     * @param array            $context
435
     *
436
     * @return string|array
437
     */
438
    protected function normalizeRelation(
439
        PropertyMetadata $propertyMetadata,
440
        $relatedObject,
441
        string $resourceClass,
442
        string $format = null,
443
        array $context
444
    ) {
445
        $resourceClass = $this->resourceClassResolver->getResourceClass(
446
            $relatedObject,
447
            null,
448
            true
449
        );
450
451
        $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
452
453
        $identifier = $this->getIdentifierFromItem($relatedObject);
454
455
        return ['data' => [
456
            'type' => $resourceMetadata->getShortName(),
457
            'id' => (string) $identifier,
458
        ]];
459
    }
460
461
    private function getIdentifierFromItem($item)
462
    {
463
        $identifiers = $this->getIdentifiersFromItem($item);
464
465
        if (count($identifiers) > 1) {
466
            throw new RuntimeException(sprintf(
467
                'Multiple identifiers are not supported during serialization of relationships (Entity: \'%s\')',
468
                $resourceClass
469
            ));
470
        }
471
472
        return reset($identifiers);
473
    }
474
475
    /**
476
     * Find identifiers from an Item (Object).
477
     *
478
     * Taken from ApiPlatform\Core\Bridge\Symfony\Routing\IriConverter
479
     *
480
     * TODO: Review if this would be useful if defined somewhere else
481
     *
482
     * @param object $item
483
     *
484
     * @throws RuntimeException
485
     *
486
     * @return array
487
     */
488 View Code Duplication
    private function getIdentifiersFromItem($item): array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
489
    {
490
        $identifiers = [];
491
        $resourceClass = $this->getObjectClass($item);
492
493
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
494
            $propertyMetadata = $this
495
                ->propertyMetadataFactory
496
                ->create($resourceClass, $propertyName);
497
498
            $identifier = $propertyMetadata->isIdentifier();
499
            if (null === $identifier || false === $identifier) {
500
                continue;
501
            }
502
503
            $identifiers[$propertyName] = $this
504
                ->propertyAccessor
505
                ->getValue($item, $propertyName);
506
507
            if (!is_object($identifiers[$propertyName])) {
508
                continue;
509
            }
510
511
            $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
512
            $relatedItem = $identifiers[$propertyName];
513
514
            unset($identifiers[$propertyName]);
515
516
            foreach (
517
                $this
518
                    ->propertyNameCollectionFactory
519
                    ->create($relatedResourceClass)
520
                    as $relatedPropertyName
521
            ) {
522
                $propertyMetadata = $this
523
                    ->propertyMetadataFactory
524
                    ->create($relatedResourceClass, $relatedPropertyName);
525
526
                if ($propertyMetadata->isIdentifier()) {
527
                    if (isset($identifiers[$propertyName])) {
528
                        throw new RuntimeException(sprintf(
529
                            'Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier',
530
                            $relatedResourceClass,
531
                            $propertyName,
532
                            $resourceClass
533
                        ));
534
                    }
535
536
                    $identifiers[$propertyName] = $this
537
                        ->propertyAccessor
538
                        ->getValue(
539
                            $relatedItem,
540
                            $relatedPropertyName
541
                        );
542
                }
543
            }
544
545
            if (!isset($identifiers[$propertyName])) {
546
                throw new RuntimeException(sprintf(
547
                    'No identifier found in "%s" through relation "%s" of "%s" used as identifier',
548
                    $relatedResourceClass,
549
                    $propertyName,
550
                    $resourceClass
551
                ));
552
            }
553
        }
554
555
        return $identifiers;
556
    }
557
}
558