Skip to content

Commit 00db2cd

Browse files
authored
fix(state): improve DX around the generic ObjectProvider (api-platform#5051)
1 parent 87eae48 commit 00db2cd

File tree

6 files changed

+151
-7
lines changed

6 files changed

+151
-7
lines changed

.commitlintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"openapi",
1919
"serializer",
2020
"jsonschema",
21-
"validation"
21+
"validation",
22+
"state"
2223
]
2324
],
2425
"scope-empty": [

src/State/CreateProvider.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\State;
1515

16+
use ApiPlatform\Exception\RuntimeException;
1617
use ApiPlatform\Metadata\Get;
1718
use ApiPlatform\Metadata\HttpOperation;
1819
use ApiPlatform\Metadata\Link;
@@ -58,8 +59,11 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5859
}
5960

6061
$relation = $this->decorated->provide(new Get(uriVariables: $relationUriVariables, class: $relationClass), $uriVariables);
61-
$refl = new \ReflectionClass($operation->getClass());
62-
$resource = $refl->newInstanceWithoutConstructor();
62+
try {
63+
$resource = new ($operation->getClass());
64+
} catch (\Throwable $e) {
65+
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);
66+
}
6367
$this->propertyAccessor->setValue($resource, $key, $relation);
6468

6569
return $resource;

src/State/ObjectProvider.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313

1414
namespace ApiPlatform\State;
1515

16+
use ApiPlatform\Exception\RuntimeException;
1617
use ApiPlatform\Metadata\Operation;
1718

1819
/**
19-
* An ItemProvider that just create a new object.
20+
* An ItemProvider that just creates a new object.
2021
*
2122
* @author Antoine Bluchet <[email protected]>
2223
*
@@ -26,8 +27,10 @@ final class ObjectProvider implements ProviderInterface
2627
{
2728
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?object
2829
{
29-
$refl = new \ReflectionClass($operation->getClass());
30-
31-
return $refl->newInstanceWithoutConstructor();
30+
try {
31+
return new ($operation->getClass());
32+
} catch (\Throwable $e) {
33+
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);
34+
}
3235
}
3336
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\Post;
19+
20+
#[Post]
21+
#[ApiResource(
22+
uriTemplate: '/companies/{companyId}/employees/{id}',
23+
uriVariables: [
24+
'companyId' => ['from_class' => Company::class, 'to_property' => 'company'],
25+
'id' => ['from_class' => DummyResourceWithComplexConstructor::class],
26+
]
27+
)]
28+
#[Get]
29+
class DummyResourceWithComplexConstructor
30+
{
31+
private \DateTimeInterface $someInternalTimestamp;
32+
private ?Company $company;
33+
34+
public function __construct(private int $id, private string $name)
35+
{
36+
$this->someInternalTimestamp = new \DateTimeImmutable();
37+
}
38+
39+
public function getId()
40+
{
41+
return $this->id;
42+
}
43+
44+
public function getCompany(): Company
45+
{
46+
return $this->company;
47+
}
48+
49+
public function setCompany(Company $company): void
50+
{
51+
$this->company = $company;
52+
}
53+
54+
public function getName(): string
55+
{
56+
return $this->name;
57+
}
58+
59+
public function setName(string $name): void
60+
{
61+
$this->name = $name;
62+
}
63+
64+
public function getSomeInternalTimestamp(): \DateTimeInterface
65+
{
66+
return $this->someInternalTimestamp;
67+
}
68+
69+
public function setSomeInternalTimestamp(\DateTimeInterface $timestamp): void
70+
{
71+
$this->someInternalTimestamp = $timestamp;
72+
}
73+
}

tests/State/CreateProviderTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313

1414
namespace ApiPlatform\Tests\State;
1515

16+
use ApiPlatform\Exception\RuntimeException;
1617
use ApiPlatform\Metadata\Get;
1718
use ApiPlatform\Metadata\Link;
1819
use ApiPlatform\Metadata\Post;
1920
use ApiPlatform\State\CreateProvider;
2021
use ApiPlatform\State\ProviderInterface;
2122
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Company;
23+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyResourceWithComplexConstructor;
2224
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Employee;
2325
use PHPUnit\Framework\TestCase;
2426
use Prophecy\PhpUnit\ProphecyTrait;
@@ -41,6 +43,23 @@ public function testProvide(): void
4143
$createProvider->provide($operation, ['company' => 1]);
4244
}
4345

46+
public function testProvideFailsProperlyOnComplexConstructor(): void
47+
{
48+
$link = new Link(identifiers: ['id'], fromClass: Company::class, parameterName: 'company');
49+
$decorated = $this->prophesize(ProviderInterface::class);
50+
$decorated->provide(
51+
new Get(uriVariables: ['id' => $link], class: Company::class),
52+
['company' => 1]
53+
)->shouldBeCalled()->willReturn(new Company());
54+
$operation = new Post(class: DummyResourceWithComplexConstructor::class, uriTemplate: '/company/{company}/employees', uriVariables: ['company' => $link]);
55+
56+
$this->expectException(RuntimeException::class);
57+
$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.');
58+
59+
$createProvider = new CreateProvider($decorated->reveal());
60+
$createProvider->provide($operation, ['company' => 1]);
61+
}
62+
4463
public function testSkipWhenController(): void
4564
{
4665
$decorated = $this->prophesize(ProviderInterface::class);

tests/State/ObjectProviderTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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\Tests\State;
15+
16+
use ApiPlatform\Exception\RuntimeException;
17+
use ApiPlatform\Metadata\Post;
18+
use ApiPlatform\State\ObjectProvider;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyResourceWithComplexConstructor;
20+
use PHPUnit\Framework\TestCase;
21+
use Prophecy\PhpUnit\ProphecyTrait;
22+
23+
class ObjectProviderTest extends TestCase
24+
{
25+
use ProphecyTrait;
26+
27+
public function testProvide(): void
28+
{
29+
$operation = new Post(class: \stdClass::class);
30+
$objectProvider = new ObjectProvider();
31+
$this->assertInstanceOf(\stdClass::class, $objectProvider->provide($operation));
32+
}
33+
34+
public function testProvideFailsProperlyOnComplexConstructor(): void
35+
{
36+
$operation = new Post(class: DummyResourceWithComplexConstructor::class);
37+
38+
$this->expectException(RuntimeException::class);
39+
$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.');
40+
41+
$objectProvider = new ObjectProvider();
42+
$objectProvider->provide($operation);
43+
}
44+
}

0 commit comments

Comments
 (0)