Skip to content

Commit 208964b

Browse files
authored
Merge pull request #3248 from api-platform/fix/normalize-relation-return-type
Ensure correct return type from AbstractItemNormalizer::normalizeRelation
2 parents c406ec2 + beea32b commit 208964b

File tree

3 files changed

+46
-23
lines changed

3 files changed

+46
-23
lines changed

phpstan.neon.dist

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ parameters:
2525
path: %currentWorkingDirectory%/src/Identifier/Normalizer/IntegerDenormalizer.php
2626

2727
# False positives
28+
-
29+
message: '#Variable \$iri might not be defined\.#'
30+
path: %currentWorkingDirectory%/src/JsonApi/Serializer/ItemNormalizer.php
2831
-
2932
message: '#Variable \$positionPm might not be defined\.#'
3033
path: %currentWorkingDirectory%/src/Util/ClassInfoTrait.php
@@ -60,7 +63,6 @@ parameters:
6063
-
6164
message: '#Property ApiPlatform\\Core\\Test\\DoctrineMongoDbOdmFilterTestCase::\$repository \(Doctrine\\ODM\\MongoDB\\Repository\\DocumentRepository\) does not accept Doctrine\\ORM\\EntityRepository<ApiPlatform\\Core\\Tests\\Fixtures\\TestBundle\\Document\\Dummy>\.#'
6265
path: %currentWorkingDirectory%/src/Test/DoctrineMongoDbOdmFilterTestCase.php
63-
- '#Method ApiPlatform\\Core\\(Serializer\\Abstract|JsonApi\\Serializer\\)ItemNormalizer::normalizeRelation\(\) should return array\|string but returns array\|bool\|float\|int\|string\|null\.#'
6466
- '#Method ApiPlatform\\Core\\Util\\RequestParser::parseRequestParams\(\) should return array but returns array\|false\.#'
6567
-
6668
message: '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Util\\QueryBuilderHelper::mapJoinAliases() should return array<string, array<string>\|string> but returns array<int|string, mixed>\.#'
@@ -116,3 +118,8 @@ parameters:
116118
path: %currentWorkingDirectory%/src/Action/ExceptionAction.php
117119
- '#Call to method get(Class|Headers|StatusCode)\(\) on an unknown class Symfony\\Component\\ErrorHandler\\Exception\\FlattenException\.#'
118120
- '#Class Symfony\\Component\\ErrorHandler\\Exception\\FlattenException not found\.#'
121+
-
122+
message: '#Instanceof between bool\|float\|int|null and ArrayObject will always evaluate to false\.#'
123+
paths:
124+
- %currentWorkingDirectory%/src/JsonApi/Serializer/ItemNormalizer.php
125+
- %currentWorkingDirectory%/src/Serializer/AbstractItemNormalizer.php

src/JsonApi/Serializer/ItemNormalizer.php

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
namespace ApiPlatform\Core\JsonApi\Serializer;
1515

1616
use ApiPlatform\Core\Api\IriConverterInterface;
17-
use ApiPlatform\Core\Api\OperationType;
1817
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
1918
use ApiPlatform\Core\Exception\ItemNotFoundException;
2019
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
@@ -199,34 +198,29 @@ protected function denormalizeRelation(string $attributeName, PropertyMetadata $
199198
* {@inheritdoc}
200199
*
201200
* @see http://jsonapi.org/format/#document-resource-object-linkage
202-
*
203-
* @throws LogicException
204201
*/
205202
protected function normalizeRelation(PropertyMetadata $propertyMetadata, $relatedObject, string $resourceClass, ?string $format, array $context)
206203
{
207-
if (null === $relatedObject) {
208-
if (isset($context['operation_type'], $context['subresource_resources'][$resourceClass]) && OperationType::SUBRESOURCE === $context['operation_type']) {
209-
$iri = $this->iriConverter->getItemIriFromResourceClass($resourceClass, $context['subresource_resources'][$resourceClass]);
210-
} else {
211-
if ($this->serializer instanceof NormalizerInterface) {
212-
return $this->serializer->normalize($relatedObject, $format, $context);
213-
}
214-
throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
215-
}
216-
} else {
204+
if (null !== $relatedObject) {
217205
$iri = $this->iriConverter->getIriFromItem($relatedObject);
218206
$context['iri'] = $iri;
219207

220208
if (isset($context['resources'])) {
221209
$context['resources'][$iri] = $iri;
222210
}
223-
if (isset($context['api_included'])) {
224-
if (!$this->serializer instanceof NormalizerInterface) {
225-
throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
226-
}
211+
}
227212

228-
return $this->serializer->normalize($relatedObject, $format, $context);
213+
if (null === $relatedObject || isset($context['api_included'])) {
214+
if (!$this->serializer instanceof NormalizerInterface) {
215+
throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
229216
}
217+
218+
$normalizedRelatedObject = $this->serializer->normalize($relatedObject, $format, $context);
219+
if (!\is_string($normalizedRelatedObject) && !\is_array($normalizedRelatedObject) && !$normalizedRelatedObject instanceof \ArrayObject && null !== $normalizedRelatedObject) {
220+
throw new UnexpectedValueException('Expected normalized relation to be an IRI, array, \ArrayObject or null');
221+
}
222+
223+
return $normalizedRelatedObject;
230224
}
231225

232226
return [

src/Serializer/AbstractItemNormalizer.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ protected function createRelationSerializationContext(string $resourceClass, arr
495495
* {@inheritdoc}
496496
*
497497
* @throws NoSuchPropertyException
498+
* @throws UnexpectedValueException
498499
* @throws LogicException
499500
*/
500501
protected function getAttributeValue($object, $attribute, $format = null, array $context = [])
@@ -515,13 +516,16 @@ protected function getAttributeValue($object, $attribute, $format = null, array
515516
$type = $propertyMetadata->getType();
516517

517518
if (
518-
is_iterable($attributeValue) &&
519519
$type &&
520520
$type->isCollection() &&
521521
($collectionValueType = $type->getCollectionValueType()) &&
522522
($className = $collectionValueType->getClassName()) &&
523523
$this->resourceClassResolver->isResourceClass($className)
524524
) {
525+
if (!is_iterable($attributeValue)) {
526+
throw new UnexpectedValueException('Unexpected non-iterable value for to-many relation.');
527+
}
528+
525529
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
526530
$childContext = $this->createChildContext($context, $attribute, $format);
527531
$childContext['resource_class'] = $resourceClass;
@@ -535,6 +539,10 @@ protected function getAttributeValue($object, $attribute, $format = null, array
535539
($className = $type->getClassName()) &&
536540
$this->resourceClassResolver->isResourceClass($className)
537541
) {
542+
if (!\is_object($attributeValue) && null !== $attributeValue) {
543+
throw new UnexpectedValueException('Unexpected non-object value for to-one relation.');
544+
}
545+
538546
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
539547
$childContext = $this->createChildContext($context, $attribute, $format);
540548
$childContext['resource_class'] = $resourceClass;
@@ -556,23 +564,32 @@ protected function getAttributeValue($object, $attribute, $format = null, array
556564
* Normalizes a collection of relations (to-many).
557565
*
558566
* @param iterable $attributeValue
567+
*
568+
* @throws UnexpectedValueException
559569
*/
560570
protected function normalizeCollectionOfRelations(PropertyMetadata $propertyMetadata, $attributeValue, string $resourceClass, ?string $format, array $context): array
561571
{
562572
$value = [];
563573
foreach ($attributeValue as $index => $obj) {
574+
if (!\is_object($obj) && null !== $obj) {
575+
throw new UnexpectedValueException('Unexpected non-object element in to-many relation.');
576+
}
577+
564578
$value[$index] = $this->normalizeRelation($propertyMetadata, $obj, $resourceClass, $format, $context);
565579
}
566580

567581
return $value;
568582
}
569583

570584
/**
571-
* Normalizes a relation as an object if is a Link or as an URI.
585+
* Normalizes a relation.
586+
*
587+
* @param object|null $relatedObject
572588
*
573589
* @throws LogicException
590+
* @throws UnexpectedValueException
574591
*
575-
* @return string|array
592+
* @return string|array|\ArrayObject|null IRI or normalized object data
576593
*/
577594
protected function normalizeRelation(PropertyMetadata $propertyMetadata, $relatedObject, string $resourceClass, ?string $format, array $context)
578595
{
@@ -581,7 +598,12 @@ protected function normalizeRelation(PropertyMetadata $propertyMetadata, $relate
581598
throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
582599
}
583600

584-
return $this->serializer->normalize($relatedObject, $format, $context);
601+
$normalizedRelatedObject = $this->serializer->normalize($relatedObject, $format, $context);
602+
if (!\is_string($normalizedRelatedObject) && !\is_array($normalizedRelatedObject) && !$normalizedRelatedObject instanceof \ArrayObject && null !== $normalizedRelatedObject) {
603+
throw new UnexpectedValueException('Expected normalized relation to be an IRI, array, \ArrayObject or null');
604+
}
605+
606+
return $normalizedRelatedObject;
585607
}
586608

587609
$iri = $this->iriConverter->getIriFromItem($relatedObject);

0 commit comments

Comments
 (0)