From 20b2180f13ac4de5d84351ac1f5cf463e9a5b338 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 5 Oct 2022 15:01:23 +0200 Subject: [PATCH] fix(state): improve DX around the generic `ObjectProvider` --- .commitlintrc | 3 +- src/State/CreateProvider.php | 8 +- src/State/ObjectProvider.php | 11 ++- .../DummyResourceWithComplexConstructor.php | 73 +++++++++++++++++++ tests/State/CreateProviderTest.php | 19 +++++ tests/State/ObjectProviderTest.php | 44 +++++++++++ 6 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Entity/DummyResourceWithComplexConstructor.php create mode 100644 tests/State/ObjectProviderTest.php diff --git a/.commitlintrc b/.commitlintrc index acfdca37b54..e552e57c20a 100644 --- a/.commitlintrc +++ b/.commitlintrc @@ -18,7 +18,8 @@ "openapi", "serializer", "jsonschema", - "validation" + "validation", + "state" ] ], "scope-empty": [ diff --git a/src/State/CreateProvider.php b/src/State/CreateProvider.php index ec4394a19a4..ad1d6dc094e 100644 --- a/src/State/CreateProvider.php +++ b/src/State/CreateProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\State; +use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; @@ -58,8 +59,11 @@ public function provide(Operation $operation, array $uriVariables = [], array $c } $relation = $this->decorated->provide(new Get(uriVariables: $relationUriVariables, class: $relationClass), $uriVariables); - $refl = new \ReflectionClass($operation->getClass()); - $resource = $refl->newInstanceWithoutConstructor(); + try { + $resource = new ($operation->getClass()); + } catch (\Throwable $e) { + throw new RuntimeException(sprintf('An error occurred while trying to create an instance of the "%s" resource. Consider writing your own "%s" implementation and setting it as `provider` on your operation instead.', $operation->getClass(), ProviderInterface::class), 0, $e); + } $this->propertyAccessor->setValue($resource, $key, $relation); return $resource; diff --git a/src/State/ObjectProvider.php b/src/State/ObjectProvider.php index 18b9e8558be..dd837541dc5 100644 --- a/src/State/ObjectProvider.php +++ b/src/State/ObjectProvider.php @@ -13,10 +13,11 @@ namespace ApiPlatform\State; +use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Operation; /** - * An ItemProvider that just create a new object. + * An ItemProvider that just creates a new object. * * @author Antoine Bluchet * @@ -26,8 +27,10 @@ final class ObjectProvider implements ProviderInterface { public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?object { - $refl = new \ReflectionClass($operation->getClass()); - - return $refl->newInstanceWithoutConstructor(); + try { + return new ($operation->getClass()); + } catch (\Throwable $e) { + throw new RuntimeException(sprintf('An error occurred while trying to create an instance of the "%s" resource. Consider writing your own "%s" implementation and setting it as `provider` on your operation instead.', $operation->getClass(), ProviderInterface::class), 0, $e); + } } } diff --git a/tests/Fixtures/TestBundle/Entity/DummyResourceWithComplexConstructor.php b/tests/Fixtures/TestBundle/Entity/DummyResourceWithComplexConstructor.php new file mode 100644 index 00000000000..224206c15bf --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/DummyResourceWithComplexConstructor.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\Post; + +#[Post] +#[ApiResource( + uriTemplate: '/companies/{companyId}/employees/{id}', + uriVariables: [ + 'companyId' => ['from_class' => Company::class, 'to_property' => 'company'], + 'id' => ['from_class' => DummyResourceWithComplexConstructor::class], + ] +)] +#[Get] +class DummyResourceWithComplexConstructor +{ + private \DateTimeInterface $someInternalTimestamp; + private ?Company $company; + + public function __construct(private int $id, private string $name) + { + $this->someInternalTimestamp = new \DateTimeImmutable(); + } + + public function getId() + { + return $this->id; + } + + public function getCompany(): Company + { + return $this->company; + } + + public function setCompany(Company $company): void + { + $this->company = $company; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getSomeInternalTimestamp(): \DateTimeInterface + { + return $this->someInternalTimestamp; + } + + public function setSomeInternalTimestamp(\DateTimeInterface $timestamp): void + { + $this->someInternalTimestamp = $timestamp; + } +} diff --git a/tests/State/CreateProviderTest.php b/tests/State/CreateProviderTest.php index 5df860329c5..8ec43d58682 100644 --- a/tests/State/CreateProviderTest.php +++ b/tests/State/CreateProviderTest.php @@ -13,12 +13,14 @@ namespace ApiPlatform\Tests\State; +use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Post; use ApiPlatform\State\CreateProvider; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Company; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyResourceWithComplexConstructor; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Employee; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -41,6 +43,23 @@ public function testProvide(): void $createProvider->provide($operation, ['company' => 1]); } + public function testProvideFailsProperlyOnComplexConstructor(): void + { + $link = new Link(identifiers: ['id'], fromClass: Company::class, parameterName: 'company'); + $decorated = $this->prophesize(ProviderInterface::class); + $decorated->provide( + new Get(uriVariables: ['id' => $link], class: Company::class), + ['company' => 1] + )->shouldBeCalled()->willReturn(new Company()); + $operation = new Post(class: DummyResourceWithComplexConstructor::class, uriTemplate: '/company/{company}/employees', uriVariables: ['company' => $link]); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('An error occurred while trying to create an instance of the "ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyResourceWithComplexConstructor" resource. Consider writing your own "ApiPlatform\State\ProviderInterface" implementation and setting it as `provider` on your operation instead.'); + + $createProvider = new CreateProvider($decorated->reveal()); + $createProvider->provide($operation, ['company' => 1]); + } + public function testSkipWhenController(): void { $decorated = $this->prophesize(ProviderInterface::class); diff --git a/tests/State/ObjectProviderTest.php b/tests/State/ObjectProviderTest.php new file mode 100644 index 00000000000..c2f93f55e3a --- /dev/null +++ b/tests/State/ObjectProviderTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\State; + +use ApiPlatform\Exception\RuntimeException; +use ApiPlatform\Metadata\Post; +use ApiPlatform\State\ObjectProvider; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyResourceWithComplexConstructor; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; + +class ObjectProviderTest extends TestCase +{ + use ProphecyTrait; + + public function testProvide(): void + { + $operation = new Post(class: \stdClass::class); + $objectProvider = new ObjectProvider(); + $this->assertInstanceOf(\stdClass::class, $objectProvider->provide($operation)); + } + + public function testProvideFailsProperlyOnComplexConstructor(): void + { + $operation = new Post(class: DummyResourceWithComplexConstructor::class); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('An error occurred while trying to create an instance of the "ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyResourceWithComplexConstructor" resource. Consider writing your own "ApiPlatform\State\ProviderInterface" implementation and setting it as `provider` on your operation instead.'); + + $objectProvider = new ObjectProvider(); + $objectProvider->provide($operation); + } +}