Skip to content

Commit 9d62eba

Browse files
jdeniaudunglas
authored andcommitted
Add DataCollector for Symfony WebProfiler toolbar. (#1707)
* Add DataCollector for Symfony WebProfiler toolbar. Fixes #1701 * clean SVG, prettier rendering * strict typing, remove docblock * fix issue when no filter * some template improvements + PHP7.1 rollback * add option to dibable profiler * linting * unit test for RequestDataCollector
1 parent 4727a87 commit 9d62eba

File tree

10 files changed

+452
-0
lines changed

10 files changed

+452
-0
lines changed

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"symfony/security-bundle": "^3.0 || ^4.0",
6464
"symfony/twig-bundle": "^3.1 || ^4.0",
6565
"symfony/validator": "^3.3 || ^4.0",
66+
"symfony/web-profiler-bundle": "^3.3 || ^4.0",
6667
"symfony/yaml": "^3.3 || ^4.0",
6768
"webonyx/graphql-php": "^0.10.2"
6869
},
@@ -79,6 +80,7 @@
7980
"symfony/expression-language": "To use authorization features.",
8081
"symfony/security": "To use authorization features.",
8182
"symfony/twig-bundle": "To use the Swagger UI integration.",
83+
"symfony/web-profiler-bundle": "To use the data collector.",
8284
"webonyx/graphql-php": "To support GraphQL."
8385
},
8486
"autoload": {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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\DataCollector;
15+
16+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
17+
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpFoundation\Response;
19+
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
20+
21+
/**
22+
* @author Julien DENIAU <[email protected]>
23+
*/
24+
final class RequestDataCollector extends DataCollector
25+
{
26+
private $metadataFactory;
27+
28+
public function __construct(ResourceMetadataFactoryInterface $metadataFactory)
29+
{
30+
$this->metadataFactory = $metadataFactory;
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function collect(Request $request, Response $response, \Exception $exception = null)
37+
{
38+
$resourceClass = $request->attributes->get('_api_resource_class');
39+
$resourceMetadata = $resourceClass ? $this->metadataFactory->create($resourceClass) : null;
40+
41+
$this->data = [
42+
'resource_class' => $resourceClass,
43+
'resource_metadata' => $resourceMetadata,
44+
'method' => $request->getMethod(),
45+
'acceptable_content_types' => $request->getAcceptableContentTypes(),
46+
];
47+
}
48+
49+
public function getMethod(): string
50+
{
51+
return $this->data['method'] ?? '';
52+
}
53+
54+
public function getAcceptableContentTypes(): array
55+
{
56+
return $this->data['acceptable_content_types'] ?? [];
57+
}
58+
59+
public function getResourceClass()
60+
{
61+
return $this->data['resource_class'] ?? null;
62+
}
63+
64+
public function getResourceMetadata()
65+
{
66+
return $this->data['resource_metadata'] ?? null;
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function getName(): string
73+
{
74+
return 'api_platform.data_collector.request';
75+
}
76+
77+
/**
78+
* {@inheritdoc}
79+
*/
80+
public function reset()
81+
{
82+
$this->data = [];
83+
}
84+
}

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public function load(array $configs, ContainerBuilder $container)
138138
$this->registerDoctrineExtensionConfiguration($container, $config, $useDoctrine);
139139
$this->registerHttpCache($container, $config, $loader, $useDoctrine);
140140
$this->registerValidatorConfiguration($container, $config, $loader);
141+
$this->registerDataCollector($container, $config, $loader);
141142
}
142143

143144
/**
@@ -552,4 +553,16 @@ private function registerValidatorConfiguration(ContainerBuilder $container, arr
552553

553554
$container->setParameter('api_platform.validator.serialize_payload_fields', $config['validator']['serialize_payload_fields']);
554555
}
556+
557+
/**
558+
* Registers the DataCollector configuration.
559+
*/
560+
private function registerDataCollector(ContainerBuilder $container, array $config, XmlFileLoader $loader)
561+
{
562+
if (!$config['enable_profiler']) {
563+
return;
564+
}
565+
566+
$loader->load('data_collector.xml');
567+
}
555568
}

src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public function getConfigTreeBuilder()
101101
->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger ui.')->end()
102102
->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end()
103103
->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end()
104+
->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end()
104105

105106
->arrayNode('oauth')
106107
->canBeEnabled()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:twig="http://symfony.com/schema/dic/twig"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
7+
8+
<services>
9+
<service id="api_platform.data_collector.request" class="ApiPlatform\Core\Bridge\Symfony\Bundle\DataCollector\RequestDataCollector" public="false">
10+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
11+
12+
<tag
13+
name="data_collector"
14+
template="@ApiPlatform/DataCollector/request.html.twig"
15+
id="api_platform.data_collector.request"
16+
priority="334"
17+
/>
18+
<!-- Priority 334 → Just after the "Request / Response" tab -->
19+
</service>
20+
</services>
21+
</container>
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
2+
3+
{% macro operationLine(itemOrCollection, key, operation) %}
4+
<tr>
5+
<td>
6+
{{ key }}
7+
</td>
8+
<td>
9+
{% if operation['method'] is defined %}
10+
<em>Method:</em> {{ operation['method'] }}
11+
{% elseif operation['route_name'] is defined %}
12+
<em>Route name:</em> {{ operation['route_name'] }}
13+
{% endif %}
14+
</td>
15+
<td>
16+
<a
17+
href="#"
18+
class="sf-toggle link-inverse sf-toggle-on"
19+
data-toggle-selector="#api-platform-line-{{ itemOrCollection }}-{{ key }}"
20+
data-toggle-alt-content="Hide"
21+
data-toggle-original-content="Show"
22+
>
23+
Show
24+
</a>
25+
</td>
26+
</tr>
27+
<tr id="api-platform-line-{{ itemOrCollection }}-{{ key }}">
28+
<td colspan="3">
29+
{{- dump(operation) -}}
30+
</td>
31+
</tr>
32+
{% endmacro %}
33+
34+
{% import _self as apiPlatform %}
35+
36+
{% block toolbar %}
37+
{% set icon %}
38+
{{ include('@ApiPlatform/DataCollector/api-platform.svg') }}
39+
{% endset %}
40+
41+
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }}
42+
{% endblock %}
43+
44+
{% block head %}
45+
{# Optional. Here you can link to or define your own CSS and JS contents. #}
46+
{# Use {{ parent() }} to extend the default styles instead of overriding them. #}
47+
{{ parent() }}
48+
49+
<style>
50+
tr.sf-toggle-content.sf-toggle-visible {
51+
display: table-row;
52+
}
53+
</style>
54+
{% endblock %}
55+
56+
{% block menu %}
57+
{# This left-hand menu appears when using the full-screen profiler. #}
58+
<span class="label{{ collector.resourceClass ? '' : ' disabled' }}">
59+
<span class="icon">
60+
{{ include('@ApiPlatform/DataCollector/api-platform.svg') }}
61+
</span>
62+
<strong>API Platform</strong>
63+
</span>
64+
{% endblock %}
65+
66+
{% block panel %}
67+
{# Optional, for showing the most details. #}
68+
<h2>Acceptable Content Types</h2>
69+
<table>
70+
<thead>
71+
<tr>
72+
<th>Content Type</th>
73+
</tr>
74+
</thead>
75+
76+
<tbody>
77+
{% for type in collector.acceptableContentTypes %}
78+
<tr>
79+
<td>{{ type }}</td>
80+
</tr>
81+
{% endfor %}
82+
</tbody>
83+
</table>
84+
<table>
85+
<thead>
86+
<tr>
87+
<th>
88+
Resource class
89+
</th>
90+
</tr>
91+
</thead>
92+
93+
<tbody>
94+
<tr>
95+
<td>
96+
{{ collector.resourceClass|default('Not an API Platform resource') }}
97+
</td>
98+
<tr>
99+
</tbody>
100+
</table>
101+
102+
{% if collector.resourceMetadata %}
103+
<h2>Metadata</h2>
104+
<h3>Short name: "{{ collector.resourceMetadata.shortName }}"</h3>
105+
<table>
106+
<thead>
107+
<tr>
108+
<th>
109+
Item operations
110+
</th>
111+
<th>
112+
Method / Route name
113+
</th>
114+
<th>
115+
Attributes
116+
</th>
117+
</tr>
118+
</thead>
119+
120+
<tbody>
121+
{% for key, itemOperation in collector.resourceMetadata.itemOperations %}
122+
{{ apiPlatform.operationLine('item', key, itemOperation) }}
123+
{% endfor %}
124+
</tbody>
125+
</table>
126+
127+
<table>
128+
<thead>
129+
<tr>
130+
<th>
131+
Collection operations
132+
</th>
133+
<th>
134+
Method / Route name
135+
</th>
136+
<th>
137+
Attributes
138+
</th>
139+
</tr>
140+
</thead>
141+
142+
<tbody>
143+
{% for key, collectionOperation in collector.resourceMetadata.collectionOperations %}
144+
{{ apiPlatform.operationLine('collection', key, collectionOperation) }}
145+
{% endfor %}
146+
</tbody>
147+
</table>
148+
149+
<table>
150+
<thead>
151+
<tr>
152+
<th>
153+
Filters
154+
</th>
155+
</tr>
156+
</thead>
157+
158+
<tbody>
159+
{% if collector.resourceMetadata.attributes.filters is defined %}
160+
{% for filter in collector.resourceMetadata.attributes.filters %}
161+
<tr>
162+
<td>
163+
{{ filter }}
164+
</td>
165+
</tr>
166+
{% endfor %}
167+
{% endif %}
168+
</tbody>
169+
</table>
170+
171+
<table>
172+
<thead>
173+
<tr>
174+
<th colspan="100%">
175+
Attributes
176+
</th>
177+
</tr>
178+
</thead>
179+
180+
<tbody>
181+
{% for key, value in collector.resourceMetadata.attributes if key != 'filters' %}
182+
<tr>
183+
<td>
184+
{{ key }}
185+
</td>
186+
<td>
187+
<pre>
188+
{{- dump(value) -}}
189+
</pre>
190+
</td>
191+
</tr>
192+
{% endfor %}
193+
</tbody>
194+
</table>
195+
{% endif %}
196+
{% endblock %}

0 commit comments

Comments
 (0)