Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .commitlintrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"openapi",
"serializer",
"jsonschema",
"validation"
"validation",
"state"
]
],
"scope-empty": [
Expand Down
8 changes: 6 additions & 2 deletions src/State/CreateProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace ApiPlatform\State;

use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Link;
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 7 additions & 4 deletions src/State/ObjectProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <soyuka@gmail.com>
*
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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;
}
}
19 changes: 19 additions & 0 deletions tests/State/CreateProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
44 changes: 44 additions & 0 deletions tests/State/ObjectProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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);
}
}