Skip to content

Commit 18c37ff

Browse files
author
Grégoire Paris
committed
Call a separate method over catching exceptions
This should make the collection data provider generator-friendly, because it will allow deferring code execution to the piece of code where the generator is iterated over instead of requiring it to be executed inside the try/catch block. Fixes #1422
1 parent 2865274 commit 18c37ff

10 files changed

+170
-30
lines changed

src/Bridge/Doctrine/Orm/CollectionDataProvider.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
1818
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
1919
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
20-
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
20+
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
2121
use ApiPlatform\Core\Exception\RuntimeException;
2222
use Doctrine\Common\Persistence\ManagerRegistry;
2323

@@ -27,7 +27,7 @@
2727
* @author Kévin Dunglas <dunglas@gmail.com>
2828
* @author Samuel ROZE <samuel.roze@gmail.com>
2929
*/
30-
class CollectionDataProvider implements CollectionDataProviderInterface
30+
class CollectionDataProvider implements CollectionDataProviderInterface, RestrictedDataProviderInterface
3131
{
3232
private $managerRegistry;
3333
private $collectionExtensions;
@@ -42,6 +42,11 @@ public function __construct(ManagerRegistry $managerRegistry, array $collectionE
4242
$this->collectionExtensions = $collectionExtensions;
4343
}
4444

45+
public function supports(string $resourceClass, string $operationName = null): bool
46+
{
47+
return null !== $this->managerRegistry->getManagerForClass($resourceClass);
48+
}
49+
4550
/**
4651
* {@inheritdoc}
4752
*
@@ -50,9 +55,6 @@ public function __construct(ManagerRegistry $managerRegistry, array $collectionE
5055
public function getCollection(string $resourceClass, string $operationName = null)
5156
{
5257
$manager = $this->managerRegistry->getManagerForClass($resourceClass);
53-
if (null === $manager) {
54-
throw new ResourceClassNotSupportedException();
55-
}
5658

5759
$repository = $manager->getRepository($resourceClass);
5860
if (!method_exists($repository, 'createQueryBuilder')) {

src/Bridge/Doctrine/Orm/ItemDataProvider.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\IdentifierManagerTrait;
1919
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
2020
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
21-
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
21+
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
2222
use ApiPlatform\Core\Exception\RuntimeException;
2323
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2424
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
@@ -32,7 +32,7 @@
3232
* @author Kévin Dunglas <dunglas@gmail.com>
3333
* @author Samuel ROZE <samuel.roze@gmail.com>
3434
*/
35-
class ItemDataProvider implements ItemDataProviderInterface
35+
class ItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
3636
{
3737
use IdentifierManagerTrait;
3838

@@ -53,6 +53,11 @@ public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollec
5353
$this->itemExtensions = $itemExtensions;
5454
}
5555

56+
public function supports(string $resourceClass, string $operationName = null): bool
57+
{
58+
return null !== $this->managerRegistry->getManagerForClass($resourceClass);
59+
}
60+
5661
/**
5762
* {@inheritdoc}
5863
*
@@ -63,9 +68,6 @@ public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollec
6368
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])
6469
{
6570
$manager = $this->managerRegistry->getManagerForClass($resourceClass);
66-
if (null === $manager) {
67-
throw new ResourceClassNotSupportedException();
68-
}
6971

7072
$identifiers = $this->normalizeIdentifiers($id, $manager, $resourceClass);
7173

src/DataProvider/ChainCollectionDataProvider.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,14 @@ public function getCollection(string $resourceClass, string $operationName = nul
3939
{
4040
foreach ($this->dataProviders as $dataProvider) {
4141
try {
42+
if ($dataProvider instanceof RestrictedDataProviderInterface
43+
&& !$dataProvider->supports($resourceClass, $operationName)) {
44+
continue;
45+
}
46+
4247
return $dataProvider->getCollection($resourceClass, $operationName);
4348
} catch (ResourceClassNotSupportedException $e) {
49+
@trigger_error(sprintf('Throwing a "%s" in a data provider is deprecated in favor of implementing "%s"', get_class($e), ResourceClassNotSupportedException::class), E_USER_DEPRECATED);
4450
continue;
4551
}
4652
}

src/DataProvider/ChainItemDataProvider.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,16 @@ public function __construct(array $dataProviders)
3737
*/
3838
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])
3939
{
40-
foreach ($this->dataProviders as $dataProviders) {
40+
foreach ($this->dataProviders as $dataProvider) {
4141
try {
42-
return $dataProviders->getItem($resourceClass, $id, $operationName, $context);
42+
if ($dataProvider instanceof RestrictedDataProviderInterface
43+
&& !$dataProvider->supports($resourceClass, $operationName)) {
44+
continue;
45+
}
46+
47+
return $dataProvider->getItem($resourceClass, $id, $operationName, $context);
4348
} catch (ResourceClassNotSupportedException $e) {
49+
@trigger_error(sprintf('Throwing a "%s" is deprecated in favor of implementing "%s"', get_class($e), RestrictedDataProviderInterface::class), E_USER_DEPRECATED);
4450
continue;
4551
}
4652
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\DataProvider;
15+
16+
/**
17+
* Restricts a data provider based on a condition.
18+
*/
19+
interface RestrictedDataProviderInterface
20+
{
21+
public function supports(string $resourceClass, string $operationName = null): bool;
22+
}

tests/Bridge/Doctrine/Orm/CollectionDataProviderTest.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,14 @@ public function testCannotCreateQueryBuilder()
9797
$this->assertEquals([], $dataProvider->getCollection(Dummy::class, 'foo'));
9898
}
9999

100-
/**
101-
* @expectedException \ApiPlatform\Core\Exception\ResourceClassNotSupportedException
102-
*/
103-
public function testThrowResourceClassNotSupportedException()
100+
public function testUnsupportedClass()
104101
{
105102
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
106103
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled();
107104

108105
$extensionProphecy = $this->prophesize(QueryResultCollectionExtensionInterface::class);
109106

110107
$dataProvider = new CollectionDataProvider($managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]);
111-
$dataProvider->getCollection(Dummy::class, 'foo');
108+
$this->assertFalse($dataProvider->supports(Dummy::class, 'foo'));
112109
}
113110
}

tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,7 @@ public function testQueryResultExtension()
177177
$this->assertEquals([], $dataProvider->getItem(Dummy::class, 1, 'foo'));
178178
}
179179

180-
/**
181-
* @expectedException \ApiPlatform\Core\Exception\ResourceClassNotSupportedException
182-
*/
183-
public function testThrowResourceClassNotSupportedException()
180+
public function testUnsupportedClass()
184181
{
185182
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
186183
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled();
@@ -192,7 +189,7 @@ public function testThrowResourceClassNotSupportedException()
192189
]);
193190

194191
$dataProvider = new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]);
195-
$dataProvider->getItem(Dummy::class, 'foo');
192+
$this->assertFalse($dataProvider->supports(Dummy::class, 'foo'));
196193
}
197194

198195
/**

tests/DataProvider/ChainCollectionDataProviderTest.php

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Core\DataProvider\ChainCollectionDataProvider;
1717
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
18+
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
1819
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
1920
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
2021

@@ -32,6 +33,60 @@ public function testGetCollection()
3233
$dummy2 = new Dummy();
3334
$dummy2->setName('Parks');
3435

36+
$firstDataProvider = $this->prophesize(CollectionDataProviderInterface::class);
37+
$firstDataProvider->willImplement(RestrictedDataProviderInterface::class);
38+
$firstDataProvider->supports(Dummy::class, null)->willReturn(false);
39+
40+
$secondDataProvider = $this->prophesize(CollectionDataProviderInterface::class);
41+
$secondDataProvider->willImplement(RestrictedDataProviderInterface::class);
42+
$secondDataProvider->supports(Dummy::class, null)->willReturn(true);
43+
$secondDataProvider->getCollection(Dummy::class, null)
44+
->willReturn([$dummy, $dummy2]);
45+
46+
$thirdDataProvider = $this->prophesize(CollectionDataProviderInterface::class);
47+
$thirdDataProvider->willImplement(RestrictedDataProviderInterface::class);
48+
$thirdDataProvider->supports(Dummy::class, null)->willReturn(true);
49+
$thirdDataProvider->getCollection(Dummy::class, null)->willReturn([$dummy]);
50+
51+
$chainItemDataProvider = new ChainCollectionDataProvider([
52+
$firstDataProvider->reveal(),
53+
$secondDataProvider->reveal(),
54+
$thirdDataProvider->reveal(),
55+
]);
56+
57+
$this->assertEquals(
58+
[$dummy, $dummy2],
59+
$chainItemDataProvider->getCollection(Dummy::class)
60+
);
61+
}
62+
63+
public function testGetCollectionNotSupported()
64+
{
65+
$firstDataProvider = $this->prophesize(CollectionDataProviderInterface::class);
66+
$firstDataProvider->willImplement(RestrictedDataProviderInterface::class);
67+
$firstDataProvider->supports('notfound', 'op')->willReturn(false);
68+
69+
$chainItemDataProvider = new ChainCollectionDataProvider(
70+
[$firstDataProvider->reveal()]
71+
);
72+
73+
$this->assertEquals(
74+
'',
75+
$chainItemDataProvider->getCollection('notfound', 'op')
76+
);
77+
}
78+
79+
/**
80+
* @group legacy
81+
* @expectedDeprecation Throwing a "ApiPlatform\Core\Exception\ResourceClassNotSupportedException" is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface"
82+
*/
83+
public function testLegacyGetCollection()
84+
{
85+
$dummy = new Dummy();
86+
$dummy->setName('Rosa');
87+
$dummy2 = new Dummy();
88+
$dummy2->setName('Parks');
89+
3590
$firstDataProvider = $this->prophesize(CollectionDataProviderInterface::class);
3691
$firstDataProvider->getCollection(Dummy::class, null)->willThrow(ResourceClassNotSupportedException::class);
3792

@@ -46,7 +101,11 @@ public function testGetCollection()
46101
$this->assertEquals([$dummy, $dummy2], $chainItemDataProvider->getCollection(Dummy::class));
47102
}
48103

49-
public function testGetCollectionExceptions()
104+
/**
105+
* @group legacy
106+
* @expectedDeprecation Throwing a "ApiPlatform\Core\Exception\ResourceClassNotSupportedException" is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface"
107+
*/
108+
public function testLegacyGetCollectionExceptions()
50109
{
51110
$firstDataProvider = $this->prophesize(CollectionDataProviderInterface::class);
52111
$firstDataProvider->getCollection('notfound', 'op')->willThrow(ResourceClassNotSupportedException::class);

tests/DataProvider/ChainItemDataProviderTest.php

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Core\DataProvider\ChainItemDataProvider;
1717
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
18+
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
1819
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
1920
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
2021

@@ -30,6 +31,49 @@ public function testGetItem()
3031
$dummy = new Dummy();
3132
$dummy->setName('Lucie');
3233

34+
$firstDataProvider = $this->prophesize(ItemDataProviderInterface::class);
35+
$firstDataProvider->willImplement(RestrictedDataProviderInterface::class);
36+
$firstDataProvider->supports(Dummy::class, null)->willReturn(false);
37+
38+
$secondDataProvider = $this->prophesize(ItemDataProviderInterface::class);
39+
$secondDataProvider->willImplement(RestrictedDataProviderInterface::class);
40+
$secondDataProvider->supports(Dummy::class, null)->willReturn(true);
41+
$secondDataProvider->getItem(Dummy::class, 1, null, [])->willReturn($dummy);
42+
43+
$thirdDataProvider = $this->prophesize(ItemDataProviderInterface::class);
44+
$thirdDataProvider->willImplement(RestrictedDataProviderInterface::class);
45+
$thirdDataProvider->supports(Dummy::class, null)->willReturn(true);
46+
$thirdDataProvider->getItem(Dummy::class, 1, null, [])->willReturn(new \stdClass());
47+
48+
$chainItemDataProvider = new ChainItemDataProvider([
49+
$firstDataProvider->reveal(),
50+
$secondDataProvider->reveal(),
51+
$thirdDataProvider->reveal(),
52+
]);
53+
54+
$this->assertEquals($dummy, $chainItemDataProvider->getItem(Dummy::class, 1));
55+
}
56+
57+
public function testGetItemExceptions()
58+
{
59+
$firstDataProvider = $this->prophesize(ItemDataProviderInterface::class);
60+
$firstDataProvider->willImplement(RestrictedDataProviderInterface::class);
61+
$firstDataProvider->supports('notfound', null)->willReturn(false);
62+
63+
$chainItemDataProvider = new ChainItemDataProvider([$firstDataProvider->reveal()]);
64+
65+
$this->assertEquals('', $chainItemDataProvider->getItem('notfound', 1));
66+
}
67+
68+
/**
69+
* @group legacy
70+
* @expectedDeprecation Throwing a "ApiPlatform\Core\Exception\ResourceClassNotSupportedException" is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface"
71+
*/
72+
public function testLegacyGetItem()
73+
{
74+
$dummy = new Dummy();
75+
$dummy->setName('Lucie');
76+
3377
$firstDataProvider = $this->prophesize(ItemDataProviderInterface::class);
3478
$firstDataProvider->getItem(Dummy::class, 1, null, [])->willThrow(ResourceClassNotSupportedException::class);
3579

@@ -44,7 +88,11 @@ public function testGetItem()
4488
$this->assertEquals($dummy, $chainItemDataProvider->getItem(Dummy::class, 1));
4589
}
4690

47-
public function testGetItemExeptions()
91+
/**
92+
* @group legacy
93+
* @expectedDeprecation Throwing a "ApiPlatform\Core\Exception\ResourceClassNotSupportedException" is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface"
94+
*/
95+
public function testLegacyGetItemExceptions()
4896
{
4997
$firstDataProvider = $this->prophesize(ItemDataProviderInterface::class);
5098
$firstDataProvider->getItem('notfound', 1, null, [])->willThrow(ResourceClassNotSupportedException::class);

tests/Fixtures/TestBundle/DataProvider/ContainNonResourceItemDataProvider.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,25 @@
1414
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\DataProvider;
1515

1616
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
17-
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
17+
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
1818
use ApiPlatform\Core\Tests\Fixtures\NotAResource;
1919
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ContainNonResource;
2020

2121
/**
2222
* @author Kévin Dunglas <dunglas@gmail.com>
2323
*/
24-
class ContainNonResourceItemDataProvider implements ItemDataProviderInterface
24+
class ContainNonResourceItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
2525
{
26+
public function supports(string $resourceClass, string $operationName = null): bool
27+
{
28+
return ContainNonResource::class === $resourceClass;
29+
}
30+
2631
/**
2732
* {@inheritdoc}
2833
*/
2934
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])
3035
{
31-
if (ContainNonResource::class !== $resourceClass) {
32-
throw new ResourceClassNotSupportedException();
33-
}
34-
3536
// Retrieve the blog post item from somewhere
3637
$cnr = new ContainNonResource();
3738
$cnr->id = $id;

0 commit comments

Comments
 (0)