Skip to content

Commit 8d625d0

Browse files
committed
Merge branch '2.4'
2 parents 204c21a + ee4bf84 commit 8d625d0

File tree

10 files changed

+150
-4
lines changed

10 files changed

+150
-4
lines changed

features/authorization/deny.feature

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Feature: Authorization checking
1616
Then the response status code should be 200
1717
And the response should be in JSON
1818

19+
Scenario: Data provider that's return generator has null previous object
20+
When I add "Accept" header equal to "application/ld+json"
21+
And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg=="
22+
And I send a "GET" request to "/custom_data_provider_generator"
23+
Then the response status code should be 200
24+
1925
Scenario: A standard user cannot create a secured resource
2026
When I add "Accept" header equal to "application/ld+json"
2127
And I add "Content-Type" header equal to "application/ld+json"

src/EventListener/ReadListener.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2424
use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait;
2525
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
26+
use ApiPlatform\Core\Util\CloneTrait;
2627
use ApiPlatform\Core\Util\RequestAttributesExtractor;
2728
use ApiPlatform\Core\Util\RequestParser;
2829
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@@ -35,6 +36,7 @@
3536
*/
3637
final class ReadListener
3738
{
39+
use CloneTrait;
3840
use OperationDataProviderTrait;
3941
use ToggleableOperationAttributeTrait;
4042

@@ -115,6 +117,6 @@ public function onKernelRequest(GetResponseEvent $event): void
115117
}
116118

117119
$request->attributes->set('data', $data);
118-
$request->attributes->set('previous_data', \is_object($data) ? clone $data : $data);
120+
$request->attributes->set('previous_data', $this->clone($data));
119121
}
120122
}

src/GraphQl/Resolver/Factory/CollectionResolverFactory.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStageInterface;
1919
use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface;
2020
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
21+
use ApiPlatform\Core\Util\CloneTrait;
2122
use GraphQL\Type\Definition\ResolveInfo;
2223
use Psr\Container\ContainerInterface;
2324
use Symfony\Component\HttpFoundation\RequestStack;
@@ -32,6 +33,8 @@
3233
*/
3334
final class CollectionResolverFactory implements ResolverFactoryInterface
3435
{
36+
use CloneTrait;
37+
3538
private $readStage;
3639
private $denyAccessStage;
3740
private $serializeStage;
@@ -83,7 +86,7 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul
8386
($this->denyAccessStage)($resourceClass, $operationName, $resolverContext + [
8487
'extra_variables' => [
8588
'object' => $collection,
86-
'previous_object' => \is_object($collection) ? clone $collection : $collection,
89+
'previous_object' => $this->clone($collection),
8790
],
8891
]);
8992

src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use ApiPlatform\Core\GraphQl\Resolver\Stage\WriteStageInterface;
2323
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2424
use ApiPlatform\Core\Util\ClassInfoTrait;
25+
use ApiPlatform\Core\Util\CloneTrait;
2526
use GraphQL\Error\Error;
2627
use GraphQL\Type\Definition\ResolveInfo;
2728
use Psr\Container\ContainerInterface;
@@ -36,6 +37,7 @@
3637
final class ItemMutationResolverFactory implements ResolverFactoryInterface
3738
{
3839
use ClassInfoTrait;
40+
use CloneTrait;
3941

4042
private $readStage;
4143
private $denyAccessStage;
@@ -71,8 +73,7 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul
7173
if (null !== $item && !\is_object($item)) {
7274
throw new \LogicException('Item from read stage should be a nullable object.');
7375
}
74-
75-
$previousItem = \is_object($item) ? clone $item : $item;
76+
$previousItem = $this->clone($item);
7677

7778
if ('delete' === $operationName) {
7879
($this->denyAccessStage)($resourceClass, $operationName, $resolverContext + [

src/Util/CloneTrait.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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\Util;
15+
16+
/**
17+
* Clones given data if cloneable.
18+
*
19+
* @internal
20+
*
21+
* @author Quentin Barloy <[email protected]>
22+
*/
23+
trait CloneTrait
24+
{
25+
public function clone($data)
26+
{
27+
if (!\is_object($data)) {
28+
return $data;
29+
}
30+
31+
try {
32+
return (new \ReflectionClass($data))->isCloneable() ? clone $data : null;
33+
} catch (\ReflectionException $reflectionException) {
34+
return null;
35+
}
36+
}
37+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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\Fixtures\TestBundle\DataProvider;
15+
16+
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
17+
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
18+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\SecuredDummy;
19+
20+
class GeneratorDataProvider implements CollectionDataProviderInterface, RestrictedDataProviderInterface
21+
{
22+
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
23+
{
24+
return SecuredDummy::class === $resourceClass && 'get_from_data_provider_generator' === $operationName;
25+
}
26+
27+
public function getCollection(string $resourceClass, string $operationName = null)
28+
{
29+
yield from [new class() {
30+
}, new class() {
31+
}];
32+
}
33+
}

tests/Fixtures/TestBundle/Document/SecuredDummy.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
* attributes={"access_control"="is_granted('ROLE_USER')"},
2828
* collectionOperations={
2929
* "get",
30+
* "get_from_data_provider_generator"={
31+
* "method"="GET",
32+
* "path"="custom_data_provider_generator",
33+
* "access_control"="is_granted('ROLE_USER')"
34+
* },
3035
* "post"={"access_control"="is_granted('ROLE_ADMIN')"}
3136
* },
3237
* itemOperations={

tests/Fixtures/TestBundle/Entity/SecuredDummy.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
* attributes={"access_control"="is_granted('ROLE_USER')"},
2727
* collectionOperations={
2828
* "get",
29+
* "get_from_data_provider_generator"={
30+
* "method"="GET",
31+
* "path"="custom_data_provider_generator",
32+
* "access_control"="is_granted('ROLE_USER')"
33+
* },
2934
* "post"={"access_control"="is_granted('ROLE_ADMIN')"}
3035
* },
3136
* itemOperations={

tests/Fixtures/app/config/config_test.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ services:
7373
arguments: [ { 'name': 'ipartial', 'description': 'ipartial' } ]
7474
tags: [ { name: 'api_platform.filter', id: 'related_to_dummy_friend.name' } ]
7575

76+
ApiPlatform\Core\Tests\Fixtures\TestBundle\DataProvider\GeneratorDataProvider:
77+
public: false
78+
tags:
79+
- name: 'api_platform.item_data_provider'
80+
7681
ApiPlatform\Core\Tests\Fixtures\TestBundle\DataProvider\ProductItemDataProvider:
7782
public: false
7883
arguments:

tests/Util/CloneTraitTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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\Util;
15+
16+
use ApiPlatform\Core\Util\CloneTrait;
17+
use PHPUnit\Framework\TestCase;
18+
19+
/**
20+
* @author Quentin Barloy <[email protected]>
21+
*/
22+
class CloneTraitTest extends TestCase
23+
{
24+
use CloneTrait;
25+
26+
public function testScalarClone(): void
27+
{
28+
$this->assertSame(5, $this->clone(5));
29+
}
30+
31+
public function testObjectClone(): void
32+
{
33+
$data = new \stdClass();
34+
$result = $this->clone($data);
35+
36+
$this->assertNotSame($data, $result);
37+
$this->assertEquals($data, $result);
38+
}
39+
40+
public function testGeneratorClone(): void
41+
{
42+
$this->assertNull($this->clone($this->generator()));
43+
}
44+
45+
private function generator(): \Generator
46+
{
47+
yield 1;
48+
}
49+
}

0 commit comments

Comments
 (0)