Skip to content

Commit 20f2f55

Browse files
feat(symfony): debug profiler state provider and processor (#4677)
* feat(metadata): Show state providers in profiler * feat(metadata): test state provider tab in profiler * test: fix cs + requires php 8 Co-authored-by: emmanuel <[email protected]>
1 parent 3877b50 commit 20f2f55

File tree

6 files changed

+150
-14
lines changed

6 files changed

+150
-14
lines changed

src/Symfony/Bundle/DataCollector/RequestDataCollector.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
use ApiPlatform\Metadata\ApiResource;
2727
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2828
use ApiPlatform\State\ProcessorInterface;
29+
use ApiPlatform\State\ProviderInterface;
2930
use ApiPlatform\Symfony\Bundle\Processor\TraceableChainProcessor;
31+
use ApiPlatform\Symfony\Bundle\Provider\TraceableChainProvider;
3032
use ApiPlatform\Util\RequestAttributesExtractor;
3133
use PackageVersions\Versions;
3234
use Psr\Container\ContainerInterface;
@@ -51,16 +53,26 @@ final class RequestDataCollector extends DataCollector
5153
private $itemDataProvider;
5254
private $subresourceDataProvider;
5355
private $dataPersister;
56+
private $provider;
5457
private $processor;
5558

56-
public function __construct($metadataFactory, ContainerInterface $filterLocator, CollectionDataProviderInterface $collectionDataProvider = null, ItemDataProviderInterface $itemDataProvider = null, SubresourceDataProviderInterface $subresourceDataProvider = null, DataPersisterInterface $dataPersister = null, ProcessorInterface $processor = null)
57-
{
59+
public function __construct(
60+
$metadataFactory,
61+
ContainerInterface $filterLocator,
62+
CollectionDataProviderInterface $collectionDataProvider = null,
63+
ItemDataProviderInterface $itemDataProvider = null,
64+
SubresourceDataProviderInterface $subresourceDataProvider = null,
65+
DataPersisterInterface $dataPersister = null,
66+
ProviderInterface $provider = null,
67+
ProcessorInterface $processor = null
68+
) {
5869
$this->metadataFactory = $metadataFactory;
5970
$this->filterLocator = $filterLocator;
6071
$this->collectionDataProvider = $collectionDataProvider;
6172
$this->itemDataProvider = $itemDataProvider;
6273
$this->subresourceDataProvider = $subresourceDataProvider;
6374
$this->dataPersister = $dataPersister;
75+
$this->provider = $provider;
6476
$this->processor = $processor;
6577

6678
if (!$metadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
@@ -103,6 +115,7 @@ public function collect(Request $request, Response $response, \Throwable $except
103115
'dataProviders' => [],
104116
'dataPersisters' => ['responses' => []],
105117
'request_attributes' => $requestAttributes,
118+
'providers' => ['responses' => []],
106119
'processors' => ['responses' => []],
107120
];
108121

@@ -131,6 +144,13 @@ public function collect(Request $request, Response $response, \Throwable $except
131144
$this->data['dataPersisters']['responses'] = $this->dataPersister->getPersistersResponse();
132145
}
133146

147+
if ($this->provider instanceof TraceableChainProvider) {
148+
$this->data['providers'] = [
149+
'context' => $this->cloneVar($this->provider->getContext()),
150+
'responses' => $this->provider->getProvidersResponse(),
151+
];
152+
}
153+
134154
if ($this->processor instanceof TraceableChainProcessor) {
135155
$this->data['processors']['responses'] = $this->processor->getProcessorsResponse();
136156
}
@@ -199,6 +219,11 @@ public function getDataPersisters(): array
199219
return $this->data['dataPersisters'] ?? ['responses' => []];
200220
}
201221

222+
public function getProviders(): array
223+
{
224+
return $this->data['providers'] ?? ['responses' => []];
225+
}
226+
202227
public function getProcessors(): array
203228
{
204229
return $this->data['processors'] ?? ['responses' => []];
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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\Symfony\Bundle\Provider;
15+
16+
use ApiPlatform\State\ChainProvider;
17+
use ApiPlatform\State\ProviderInterface;
18+
19+
final class TraceableChainProvider implements ProviderInterface
20+
{
21+
private $providers;
22+
private $context;
23+
private $providersResponse = [];
24+
private $decorated;
25+
26+
public function __construct(ProviderInterface $provider)
27+
{
28+
if ($provider instanceof ChainProvider) {
29+
$this->decorated = $provider;
30+
$this->providers = $provider->providers;
31+
}
32+
}
33+
34+
public function getProvidersResponse()
35+
{
36+
return $this->providersResponse;
37+
}
38+
39+
public function getContext()
40+
{
41+
return $this->context;
42+
}
43+
44+
private function traceProviders(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = [])
45+
{
46+
foreach ($this->providers as $provider) {
47+
$this->providersResponse[\get_class($provider)] = $provider->supports($resourceClass, $identifiers, $operationName, $context);
48+
}
49+
}
50+
51+
private function traceContext(array $context)
52+
{
53+
$this->context = $context;
54+
}
55+
56+
public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = [])
57+
{
58+
$this->traceProviders($resourceClass, $identifiers, $operationName, $context);
59+
$this->traceContext($context);
60+
61+
return $this->decorated->provide($resourceClass, $identifiers, $operationName, $context);
62+
}
63+
64+
public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool
65+
{
66+
return $this->decorated->supports($resourceClass, $identifiers, $operationName, $context);
67+
}
68+
}

src/Symfony/Bundle/Resources/config/v3/data_collector.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<argument type="service" id="api_platform.item_data_provider" />
1313
<argument type="service" id="api_platform.subresource_data_provider" />
1414
<argument type="service" id="api_platform.data_persister" />
15+
<argument type="service" id="api_platform.state_provider" />
1516
<argument type="service" id="api_platform.state_processor" />
1617
<tag
1718
name="data_collector"

src/Symfony/Bundle/Resources/config/v3/debug.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
<argument type="service" id="debug.var_dumper.cli_dumper" />
1313
</service>
1414

15+
<service id="debug.api_platform.provider" class="ApiPlatform\Symfony\Bundle\Provider\TraceableChainProvider" decorates="api_platform.state_provider">
16+
<argument type="service" id="debug.api_platform.provider.inner" />
17+
</service>
18+
1519
<service id="debug.api_platform.processor" class="ApiPlatform\Symfony\Bundle\Processor\TraceableChainProcessor" decorates="api_platform.state_processor">
1620
<argument type="service" id="debug.api_platform.processor.inner" />
1721
</service>

src/Symfony/Bundle/Resources/views/DataCollector/request.html.twig

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@
4141
<thead>
4242
<tr>
4343
<th>#</th>
44-
<th>Answer</th>
44+
<th>Used</th>
4545
<th>{{ name|capitalize }}</th>
4646
</tr>
4747
</thead>
4848
<tbody>
49-
{% for class, answer in object.responses %}
49+
{% for class, used in object.responses %}
5050
<tr>
5151
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
5252
<td class="font-normal">
53-
{% if answer is same as(true) %}
53+
{% if used is same as(true) %}
5454
<span class="label status-success same-width">TRUE</span>
55-
{% elseif answer is same as(false) %}
55+
{% elseif used is same as(false) %}
5656
<span class="label status-error same-width">FALSE</span>
5757
{% else %}
5858
<span class="label status-info same-width">NOT USED</span>
@@ -202,8 +202,15 @@
202202
</div>
203203

204204
<div class="tab">
205-
<h3 class="tab-title data-processor-tab-title">Processors</h3>
206-
<div class="tab-content data-processor-tab-content">
205+
<h3 class="tab-title provider-tab-title">Providers</h3>
206+
<div class="tab-content provider-tab-content">
207+
{{ apiPlatform.providerTable(collector.providers, 'provider') }}
208+
</div>
209+
</div>
210+
211+
<div class="tab">
212+
<h3 class="tab-title processor-tab-title">Processors</h3>
213+
<div class="tab-content processor-tab-content">
207214
{{ apiPlatform.providerTable(collector.processors, 'processors') }}
208215
</div>
209216
</div>

tests/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public function testProfilerGeneralLayout()
162162
$this->assertCount(1, $metrics->filter('.metric'), 'The should be one metric displayed (resource class).');
163163
$this->assertSame('mongodb' === $this->env ? DocumentDummy::class : Dummy::class, $metrics->filter('span.value')->html());
164164

165-
$this->assertCount(7, $crawler->filter('.sf-tabs .tab-content'), 'Tabs must be presents on the panel.');
165+
$this->assertCount(8, $crawler->filter('.sf-tabs .tab-content'), 'Tabs must be presents on the panel.');
166166

167167
// Metadata tab
168168
$this->assertSame('Metadata', $crawler->filter('.tab:nth-of-type(1) .tab-title')->html());
@@ -178,9 +178,17 @@ public function testProfilerGeneralLayout()
178178
$this->assertSame('Data Providers', $crawler->filter('.data-provider-tab-title')->html());
179179
$this->assertNotEmpty($crawler->filter('.data-provider-tab-content'));
180180

181-
// Data processors tab
182-
$this->assertSame('Processors', $crawler->filter('.data-processor-tab-title')->html());
183-
$this->assertNotEmpty($crawler->filter('.data-processor-tab-content .empty'));
181+
// Data persisters tab
182+
$this->assertSame('Data Persisters', $crawler->filter('.data-persister-tab-title')->html());
183+
$this->assertNotEmpty($crawler->filter('.data-persister-tab-content'));
184+
185+
// Providers tab
186+
$this->assertSame('Providers', $crawler->filter('.provider-tab-title')->html());
187+
$this->assertNotEmpty($crawler->filter('.provider-tab-content .empty'));
188+
189+
// Processors tab
190+
$this->assertSame('Processors', $crawler->filter('.processor-tab-title')->html());
191+
$this->assertNotEmpty($crawler->filter('.processor-tab-content .empty'));
184192
}
185193

186194
/**
@@ -211,9 +219,32 @@ public function testPostCollectionProcessorProfiler()
211219
$this->assertStringContainsString('No calls to item data provider have been recorded.', $tabContent->html());
212220
$this->assertStringContainsString('No calls to subresource data provider have been recorded.', $tabContent->html());
213221

214-
// Data processors tab
215-
$tabContent = $crawler->filter('.data-processor-tab-content');
222+
// Processors tab
223+
$tabContent = $crawler->filter('.processor-tab-content');
216224
$this->assertSame('TRUE', $tabContent->filter('table tbody .status-success')->html());
217225
$this->assertStringContainsString(Processor::class, $tabContent->html());
218226
}
227+
228+
/**
229+
* @requires PHP 8.0
230+
*/
231+
public function testStateProvidersProfiler()
232+
{
233+
if ($this->legacy) {
234+
$this->markTestSkipped('Legacy test.');
235+
236+
return;
237+
}
238+
239+
$client = static::createClient();
240+
$client->enableProfiler();
241+
// request to resource that uses state provider
242+
$client->request('GET', '/attribute_resources/2', [], [], ['HTTP_ACCEPT' => 'application/ld+json']);
243+
$this->assertEquals(200, $client->getResponse()->getStatusCode());
244+
$crawler = $client->request('GET', '/_profiler/latest?panel=api_platform.data_collector.request', [], [], []);
245+
$this->assertEquals(200, $client->getResponse()->getStatusCode());
246+
247+
// Providers tab
248+
$this->assertNotEmpty($crawler->filter('.provider-tab-content table'));
249+
}
219250
}

0 commit comments

Comments
 (0)