Skip to content

Commit 471f86e

Browse files
committed
Fix fatal error when subresource type is not defined
Currently, when a property has a @ApiSubresource declaration, but no proper type declaration, this results in a "Fatal error: Call to a member function isCollection() on null", which is hard to debug. Instead throw an exception referencing the offending property, so the declaration can be fixed. Additionally, add some unit tests for the AnnotationSubresourceMetadataFactory.
1 parent 09bbfbe commit 471f86e

File tree

2 files changed

+85
-3
lines changed

2 files changed

+85
-3
lines changed

src/Metadata/Property/Factory/AnnotationSubresourceMetadataFactory.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Metadata\Property\Factory;
1515

1616
use ApiPlatform\Core\Annotation\ApiSubresource;
17+
use ApiPlatform\Core\Exception\InvalidResourceException;
1718
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
1819
use ApiPlatform\Core\Metadata\Property\SubresourceMetadata;
1920
use ApiPlatform\Core\Util\Reflection;
@@ -52,7 +53,7 @@ public function create(string $resourceClass, string $property, array $options =
5253
$annotation = $this->reader->getPropertyAnnotation($reflectionClass->getProperty($property), ApiSubresource::class);
5354

5455
if (null !== $annotation) {
55-
return $this->updateMetadata($annotation, $propertyMetadata, $resourceClass);
56+
return $this->updateMetadata($annotation, $propertyMetadata, $resourceClass, $property);
5657
}
5758
}
5859

@@ -70,16 +71,19 @@ public function create(string $resourceClass, string $property, array $options =
7071
$annotation = $this->reader->getMethodAnnotation($reflectionMethod, ApiSubresource::class);
7172

7273
if (null !== $annotation) {
73-
return $this->updateMetadata($annotation, $propertyMetadata, $resourceClass);
74+
return $this->updateMetadata($annotation, $propertyMetadata, $resourceClass, $property);
7475
}
7576
}
7677

7778
return $propertyMetadata;
7879
}
7980

80-
private function updateMetadata(ApiSubresource $annotation, PropertyMetadata $propertyMetadata, string $originResourceClass): PropertyMetadata
81+
private function updateMetadata(ApiSubresource $annotation, PropertyMetadata $propertyMetadata, string $originResourceClass, string $propertyName): PropertyMetadata
8182
{
8283
$type = $propertyMetadata->getType();
84+
if (null === $type) {
85+
throw new InvalidResourceException(sprintf('Property "%s" on resource "%s" is declared as a subresource, but its type could not be determined.', $propertyName, $originResourceClass));
86+
}
8387
$isCollection = $type->isCollection();
8488
$resourceClass = $isCollection ? $type->getCollectionValueType()->getClassName() : $type->getClassName();
8589
$maxDepth = $annotation->maxDepth;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Tests\Metadata\Property\Factory;
15+
16+
use ApiPlatform\Core\Annotation\ApiSubresource;
17+
use ApiPlatform\Core\Metadata\Property\Factory\AnnotationSubresourceMetadataFactory;
18+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
19+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
20+
use ApiPlatform\Core\Metadata\Property\SubresourceMetadata;
21+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
22+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
23+
use Doctrine\Common\Annotations\Reader;
24+
use Doctrine\Common\Collections\ArrayCollection;
25+
use PHPUnit\Framework\TestCase;
26+
use Prophecy\Argument;
27+
use Prophecy\Prophecy\ProphecyInterface;
28+
use Symfony\Component\PropertyInfo\Type;
29+
30+
class AnnotationSubresourceMetadataFactoryTest extends TestCase
31+
{
32+
/**
33+
* @dataProvider getDependencies
34+
*/
35+
public function testCreateProperty(ProphecyInterface $reader, ProphecyInterface $decorated = null)
36+
{
37+
$factory = new AnnotationSubresourceMetadataFactory($reader->reveal(), $decorated->reveal());
38+
$metadata = $factory->create(Dummy::class, 'relatedDummies');
39+
40+
$this->assertEquals(new SubresourceMetadata(RelatedDummy::class, true, null), $metadata->getSubresource());
41+
}
42+
43+
public function getDependencies()
44+
{
45+
$annotation = new ApiSubresource();
46+
47+
$propertyReaderProphecy = $this->prophesize(Reader::class);
48+
$propertyReaderProphecy->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiSubresource::class)->willReturn($annotation)->shouldBeCalled();
49+
50+
$relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
51+
$subresourceType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType);
52+
53+
$decoratedReturnProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
54+
$decoratedReturnProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn(new PropertyMetadata($subresourceType, 'Several dummies'))->shouldBeCalled();
55+
56+
return [
57+
[$propertyReaderProphecy, $decoratedReturnProphecy],
58+
];
59+
}
60+
61+
/**
62+
* @expectedException \ApiPlatform\Core\Exception\InvalidResourceException
63+
* @expectedExceptionMessage Property "relatedDummies" on resource "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy" is declared as a subresource, but its type could not be determined.
64+
*/
65+
public function testCreatePropertyUnknownType()
66+
{
67+
$annotation = new ApiSubresource();
68+
69+
$propertyReaderProphecy = $this->prophesize(Reader::class);
70+
$propertyReaderProphecy->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiSubresource::class)->willReturn($annotation)->shouldBeCalled();
71+
72+
$decoratedReturnProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
73+
$decoratedReturnProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn(new PropertyMetadata(null, 'Several dummies'))->shouldBeCalled();
74+
75+
$factory = new AnnotationSubresourceMetadataFactory($propertyReaderProphecy->reveal(), $decoratedReturnProphecy->reveal());
76+
$metadata = $factory->create(Dummy::class, 'relatedDummies');
77+
}
78+
}

0 commit comments

Comments
 (0)