Skip to content

Commit d5c9d0b

Browse files
antograssiotdunglas
authored andcommitted
Add data providers and data persisters matches to the debug panel (#2262)
* Add data providers and data persisters matches to the debug panel * Add tests on traceable providers and persisters * Use internal public properties * Merge config file and add tests on Extension * Avoid extra loop in data persister/providers * remove duplicated code * Fix after rebasing
1 parent d3e3733 commit d5c9d0b

19 files changed

+1271
-117
lines changed

src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DataCollector;
1515

16+
use ApiPlatform\Core\Bridge\Symfony\Bundle\DataPersister\TraceableChainDataPersister;
17+
use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainCollectionDataProvider;
18+
use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainItemDataProvider;
19+
use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainSubresourceDataProvider;
20+
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
21+
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
22+
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
23+
use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface;
1624
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
1725
use ApiPlatform\Core\Util\RequestAttributesExtractor;
1826
use Psr\Container\ContainerInterface;
@@ -22,16 +30,25 @@
2230

2331
/**
2432
* @author Julien DENIAU <[email protected]>
33+
* @author Anthony GRASSIOT <[email protected]>
2534
*/
2635
final class RequestDataCollector extends DataCollector
2736
{
2837
private $metadataFactory;
2938
private $filterLocator;
39+
private $collectionDataProvider;
40+
private $itemDataProvider;
41+
private $subresourceDataProvider;
42+
private $dataPersister;
3043

31-
public function __construct(ResourceMetadataFactoryInterface $metadataFactory, ContainerInterface $filterLocator)
44+
public function __construct(ResourceMetadataFactoryInterface $metadataFactory, ContainerInterface $filterLocator, CollectionDataProviderInterface $collectionDataProvider = null, ItemDataProviderInterface $itemDataProvider = null, SubresourceDataProviderInterface $subresourceDataProvider = null, DataPersisterInterface $dataPersister = null)
3245
{
3346
$this->metadataFactory = $metadataFactory;
3447
$this->filterLocator = $filterLocator;
48+
$this->collectionDataProvider = $collectionDataProvider;
49+
$this->itemDataProvider = $itemDataProvider;
50+
$this->subresourceDataProvider = $subresourceDataProvider;
51+
$this->dataPersister = $dataPersister;
3552
}
3653

3754
/**
@@ -61,7 +78,34 @@ public function collect(Request $request, Response $response, \Exception $except
6178
'request_attributes' => RequestAttributesExtractor::extractAttributes($request),
6279
'filters' => $filters,
6380
'counters' => $counters,
81+
'dataProviders' => [],
82+
'dataPersisters' => ['responses' => []],
6483
];
84+
85+
if ($this->collectionDataProvider instanceof TraceableChainCollectionDataProvider) {
86+
$this->data['dataProviders']['collection'] = [
87+
'context' => $this->collectionDataProvider->getContext(),
88+
'responses' => $this->collectionDataProvider->getProvidersResponse(),
89+
];
90+
}
91+
92+
if ($this->itemDataProvider instanceof TraceableChainItemDataProvider) {
93+
$this->data['dataProviders']['item'] = [
94+
'context' => $this->itemDataProvider->getContext(),
95+
'responses' => $this->itemDataProvider->getProvidersResponse(),
96+
];
97+
}
98+
99+
if ($this->subresourceDataProvider instanceof TraceableChainSubresourceDataProvider) {
100+
$this->data['dataProviders']['subresource'] = [
101+
'context' => $this->subresourceDataProvider->getContext(),
102+
'responses' => $this->subresourceDataProvider->getProvidersResponse(),
103+
];
104+
}
105+
106+
if ($this->dataPersister instanceof TraceableChainDataPersister) {
107+
$this->data['dataPersisters']['responses'] = $this->dataPersister->getPersistersResponse();
108+
}
65109
}
66110

67111
public function getAcceptableContentTypes(): array
@@ -94,6 +138,26 @@ public function getCounters(): array
94138
return $this->data['counters'] ?? [];
95139
}
96140

141+
public function getCollectionDataProviders(): array
142+
{
143+
return $this->data['dataProviders']['collection'] ?? ['context' => [], 'responses' => []];
144+
}
145+
146+
public function getItemDataProviders(): array
147+
{
148+
return $this->data['dataProviders']['item'] ?? ['context' => [], 'responses' => []];
149+
}
150+
151+
public function getSubresourceDataProviders(): array
152+
{
153+
return $this->data['dataProviders']['subresource'] ?? ['context' => [], 'responses' => []];
154+
}
155+
156+
public function getDataPersisters(): array
157+
{
158+
return $this->data['dataPersisters'] ?? ['responses' => []];
159+
}
160+
97161
/**
98162
* {@inheritdoc}
99163
*/
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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\Bridge\Symfony\Bundle\DataPersister;
15+
16+
use ApiPlatform\Core\DataPersister\ChainDataPersister;
17+
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
18+
19+
/**
20+
* @author Anthony GRASSIOT <[email protected]>
21+
*/
22+
final class TraceableChainDataPersister implements DataPersisterInterface
23+
{
24+
private $persisters = [];
25+
private $persistersResponse = [];
26+
private $decorated;
27+
28+
public function __construct(DataPersisterInterface $dataPersister)
29+
{
30+
if ($dataPersister instanceof ChainDataPersister) {
31+
$this->decorated = $dataPersister;
32+
$this->persisters = $dataPersister->persisters;
33+
}
34+
}
35+
36+
public function getPersistersResponse(): array
37+
{
38+
return $this->persistersResponse;
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function supports($data): bool
45+
{
46+
return $this->decorated->supports($data);
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function persist($data)
53+
{
54+
if ($match = $this->tracePersisters($data)) {
55+
return $match->persist($data) ?? $data;
56+
}
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function remove($data)
63+
{
64+
if ($match = $this->tracePersisters($data)) {
65+
return $match->remove($data);
66+
}
67+
}
68+
69+
private function tracePersisters($data)
70+
{
71+
$match = null;
72+
foreach ($this->persisters as $persister) {
73+
$this->persistersResponse[\get_class($persister)] = $match ? null : false;
74+
if (!$match && $persister->supports($data)) {
75+
$match = $persister;
76+
$this->persistersResponse[\get_class($persister)] = true;
77+
}
78+
}
79+
80+
return $match;
81+
}
82+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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\Bridge\Symfony\Bundle\DataProvider;
15+
16+
use ApiPlatform\Core\DataProvider\ChainCollectionDataProvider;
17+
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
18+
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
19+
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
20+
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
21+
22+
/**
23+
* @author Anthony GRASSIOT <[email protected]>
24+
*/
25+
final class TraceableChainCollectionDataProvider implements ContextAwareCollectionDataProviderInterface
26+
{
27+
private $dataProviders = [];
28+
private $context = [];
29+
private $providersResponse = [];
30+
31+
public function __construct(CollectionDataProviderInterface $collectionDataProvider)
32+
{
33+
if ($collectionDataProvider instanceof ChainCollectionDataProvider) {
34+
$this->dataProviders = $collectionDataProvider->dataProviders;
35+
}
36+
}
37+
38+
public function getProvidersResponse(): array
39+
{
40+
return $this->providersResponse;
41+
}
42+
43+
public function getContext(): array
44+
{
45+
return $this->context;
46+
}
47+
48+
public function getCollection(string $resourceClass, string $operationName = null, array $context = [])
49+
{
50+
$this->context = $context;
51+
$results = null;
52+
$match = false;
53+
54+
foreach ($this->dataProviders as $dataProvider) {
55+
$this->providersResponse[\get_class($dataProvider)] = $match ? null : false;
56+
if ($match) {
57+
continue;
58+
}
59+
try {
60+
if ($dataProvider instanceof RestrictedDataProviderInterface
61+
&& !$dataProvider->supports($resourceClass, $operationName, $context)) {
62+
continue;
63+
}
64+
65+
$results = $dataProvider->getCollection($resourceClass, $operationName, $context);
66+
$this->providersResponse[\get_class($dataProvider)] = $match = true;
67+
} catch (ResourceClassNotSupportedException $e) {
68+
@trigger_error(sprintf('Throwing a "%s" in a data provider is deprecated in favor of implementing "%s"', ResourceClassNotSupportedException::class, RestrictedDataProviderInterface::class), E_USER_DEPRECATED);
69+
continue;
70+
}
71+
}
72+
73+
return $results ?? [];
74+
}
75+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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\Bridge\Symfony\Bundle\DataProvider;
15+
16+
use ApiPlatform\Core\DataProvider\ChainItemDataProvider;
17+
use ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface;
18+
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
19+
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
20+
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
21+
22+
/**
23+
* @author Anthony GRASSIOT <[email protected]>
24+
*/
25+
final class TraceableChainItemDataProvider implements ItemDataProviderInterface
26+
{
27+
private $dataProviders = [];
28+
private $context = [];
29+
private $providersResponse = [];
30+
31+
public function __construct(ItemDataProviderInterface $itemDataProvider)
32+
{
33+
if ($itemDataProvider instanceof ChainItemDataProvider) {
34+
$this->dataProviders = $itemDataProvider->dataProviders;
35+
}
36+
}
37+
38+
public function getProvidersResponse(): array
39+
{
40+
return $this->providersResponse;
41+
}
42+
43+
public function getContext(): array
44+
{
45+
return $this->context;
46+
}
47+
48+
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])
49+
{
50+
$this->context = $context;
51+
$match = false;
52+
$result = null;
53+
54+
foreach ($this->dataProviders as $dataProvider) {
55+
$this->providersResponse[\get_class($dataProvider)] = $match ? null : false;
56+
if ($match) {
57+
continue;
58+
}
59+
try {
60+
if ($dataProvider instanceof RestrictedDataProviderInterface
61+
&& !$dataProvider->supports($resourceClass, $operationName, $context)) {
62+
continue;
63+
}
64+
65+
$identifier = $id;
66+
if (!$dataProvider instanceof DenormalizedIdentifiersAwareItemDataProviderInterface && $identifier && \is_array($identifier)) {
67+
if (\count($identifier) > 1) {
68+
@trigger_error(sprintf('Receiving "$id" as non-array in an item data provider is deprecated in 2.3 in favor of implementing "%s".', DenormalizedIdentifiersAwareItemDataProviderInterface::class), E_USER_DEPRECATED);
69+
$identifier = http_build_query($identifier, '', ';');
70+
} else {
71+
$identifier = current($identifier);
72+
}
73+
}
74+
75+
$result = $dataProvider->getItem($resourceClass, $identifier, $operationName, $context);
76+
$this->providersResponse[\get_class($dataProvider)] = $match = true;
77+
} catch (ResourceClassNotSupportedException $e) {
78+
@trigger_error(sprintf('Throwing a "%s" is deprecated in favor of implementing "%s"', \get_class($e), RestrictedDataProviderInterface::class), E_USER_DEPRECATED);
79+
continue;
80+
}
81+
}
82+
83+
return $result;
84+
}
85+
}

0 commit comments

Comments
 (0)