8000 [Serializer] Add serializer profiler · symfony/symfony@0e20ad6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0e20ad6

Browse files
committed
[Serializer] Add serializer profiler
1 parent 34cebec commit 0e20ad6

File tree

17 files changed

+1748
-7
lines changed

17 files changed

+1748
-7
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ class FrameworkExtension extends Extension
237237
private bool $mailerConfigEnabled = false;
238238
private bool $httpClientConfigEnabled = false;
239239
private bool $notifierConfigEnabled = false;
240+
private bool $serializerConfigEnabled = false;
240241
private bool $propertyAccessConfigEnabled = false;
241242
private static bool $lockConfigEnabled = false;
242243

@@ -386,7 +387,7 @@ public function load(array $configs, ContainerBuilder $container)
386387

387388
$container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']);
388389

389-
if ($this->isConfigEnabled($container, $config['serializer'])) {
390+
if ($this->serializerConfigEnabled = $this->isConfigEnabled($container, $config['serializer'])) {
390391
if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) {
391392
throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".');
392393
}
@@ -516,7 +517,7 @@ public function load(array $configs, ContainerBuilder $container)
516517
$this->registerNotifierConfiguration($config['notifier'], $container, $loader);
517518
}
518519

519-
// profiler depends on form, validation, translation, messenger, mailer, http-client, notifier being registered
520+
// profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered
520521
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
521< 9E81 /code>522

522523
$this->addAnnotatedClassesToCompile([
@@ -798,6 +799,10 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
798799
$loader->load('notifier_debug.php');
799800
}
800801

802+
if ($this->serializerConfigEnabled) {
803+
$loader->load('serializer_debug.php');
804+
}
805+
801806
$container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']);
802807
$container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests']);
803808

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\Serializer\DataCollector\SerializerDataCollector;
15+
use Symfony\Component\Serializer\Debug\TraceableSerializer;
16+
17+
return static function (ContainerConfigurator $container) {
18+
$container->services()
19+
->set('debug.serializer', TraceableSerializer::class)
20+
->decorate('serializer', null, 255)
21+
->args([
22+
service('debug.serializer.inner'),
23+
service('serializer.data_collector'),
24+
])
25+
26+
->set('serializer.data_collector', SerializerDataCollector::class)
27+
->tag('data_collector', [
28+
'template' => '@WebProfiler/Collector/serializer.html.twig',
29+
'id' => 'serializer',
30+
])
31+
;
32+
};

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
"symfony/mime": "<5.4",
8686
"symfony/property-info": "<5.4",
8787
"symfony/property-access": "<5.4",
88-
"symfony/serializer": "<5.4",
88+
"symfony/serializer": "<6.1",
8989
"symfony/security-csrf": "<5.4",
9090
"symfony/security-core": "<5.4",
9191
"symfony/stopwatch": "<5.4",
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
2+
3+
{% import _self as helper %}
4+
5+
{% block menu %}
6+
<span class="label {{ not collector.handledCount ? 'disabled' }}">
7+
<span class="icon">{{ include('@WebProfiler/Icon/validator.svg') }}</span>
8+
<strong>Serializer</strong>
9+
</span>
10+
{% endblock %}
11+
12+
{% block panel %}
13+
<h2>Serializer</h2>
14+
{% if not collector.handledCount %}
15+
<div class="empty">
16+
<p>Nothing was handled by the serializer for this request.</p>
17+
</div>
18+
{% else %}
19+
<div class="metrics">
20+
<div class="metric">
21+
<span class="value">{{ collector.handledCount }}</span>
22+
<span class="label">Handled</span>
23+
</div>
24+
25+
<div class="metric">
26+
<span class="value">{{ '%.2f'|format(collector.totalTime * 1000) }} <span class="unit">ms</span></span>
27+
<span class="label">Total time</span>
28+
</div>
29+
</div>
30+
31+
<div class="sf-tabs">
32+
{{ helper.render_serialize_tab(collector.data, true) }}
33+
{{ helper.render_serialize_tab(collector.data, false) }}
34+
35+
{{ helper.render_normalize_tab(collector.data, true) }}
36+
{{ helper.render_normalize_tab(collector.data, false) }}
37+
38+
{{ helper.render_encode_tab(collector.data, true) }}
39+
{{ helper.render_encode_tab(collector.data, false) }}
40+
</div>
41+
{% endif %}
42+
{% endblock %}
43+
44+
{% macro render_serialize_tab(collectorData, serialize) %}
45+
{% set data = serialize ? collectorData.serialize : collectorData.deserialize %}
46+
{% set cellPrefix = serialize ? 'serialize' : 'deserialize' %}
47+
48+
<div class="tab {{ not data ? 'disabled' }}">
49+
<h3 class="tab-title">{{ serialize ? 'serialize' : 'deserialize' }} <span class="badge">{{ data|length }}</h3>
50+
<div class="tab-content">
51+
{% if not data|length %}
52+
<div class="empty">
53+
<p>Nothing was {{ serialize ? 'serialized' : 'deserialized' }}.</p>
54+
</div>
55+
{% else %}
56+
<table>
57+
<thead>
58+
<tr>
59+
<th>Data</th>
60+
<th>Context</th>
61+
<th>Normalizer</th>
62+
<th>Encoder</th>
63+
<th>Time</th>
64+
</tr>
65+
</thead>
66+
<tbody>
67+
{% for item in data %}
68+
<tr>
69+
<td>{{ helper.render_data_cell(item, loop.index, cellPrefix) }}</td>
70+
<td>{{ helper.render_context_cell(item, loop.index, cellPrefix) }}</td>
71+
<td>{{ helper.render_normalizer_cell(item, loop.index, cellPrefix) }}</td>
72+
<td>{{ helper.render_encoder_cell(item, loop.index, cellPrefix) }}</td>
73+
<td>{{ helper.render_time_cell(item) }}</td>
74+
</tr>
75+
{% endfor %}
76+
</tbody>
77+
</table>
78+
{% endif %}
79+
</div>
80+
</div>
81+
{% endmacro %}
82+
83+
{% macro render_normalize_tab(collectorData, normalize) %}
84+
{% set data = normalize ? collectorData.normalize : collectorData.denormalize %}
85+
{% set cellPrefix = normalize ? 'normalize' : 'denormalize' %}
86+
87+
<div class="tab {{ not data ? 'disabled' }}">
88+
<h3 class="tab-title">{{ normalize ? 'normalize' : 'denormalize' }} <span class="badge">{{ data|length }}</h3>
89+
<div class="tab-content">
90+
{% if not data|length %}
91+
<div class="empty">
92+
<p>Nothing was {{ normalize ? 'normalized' : 'denormalized' }}.</p>
93+
</div>
94+
{% else %}
95+
<table>
96+
<thead>
97+
<tr>
98+
<th>Data</th>
99+
<th>Context</th>
100+
<th>Normalizer</th>
101+
<th>Time</th>
102+
</tr>
103+
</thead>
104+
<tbody>
105+
{% for item in data %}
106+
<tr>
107+
<td>{{ helper.render_data_cell(item, loop.index, cellPrefix) }}</td>
108+
<td>{{ helper.render_context_cell(item, loop.index, cellPrefix) }}</td>
109+
<td>{{ helper.render_normalizer_cell(item, loop.index, cellPrefix) }}</td>
110+
<td>{{ helper.render_time_cell(item) }}</td>
111+
</tr>
112+
{% endfor %}
113+
</tbody>
114+
</table>
115+
{% endif %}
116+
</div>
117+
</div>
118+
{% endmacro %}
119+
120+
{% macro render_encode_tab(collectorData, encode) %}
121+
{% set data = encode ? collectorData.encode : collectorData.decode %}
122+
{% set cellPrefix = encode ? 'encode' : 'decode' %}
123+
124+
<div class="tab {{ not data ? 'disabled' }}">
125+
<h3 class="tab-title">{{ encode ? 'encode' : 'decode' }} <span class="badge">{{ data|length }}</h3>
126+
<div class="tab-content">
127+
{% if not data|length %}
128+
<div class="empty">
129+
<p>Nothing was {{ encode ? 'encoded' : 'decoded' }}.</p>
130+
</div>
131+
{% else %}
132+
<table>
133+
<thead>
134+
<tr>
135+
<th>Data</th>
136+
<th>Context</th>
137+
<th>Encoder</th>
138+
<th>Time</th>
139+
</tr>
140+
</thead>
141+
<tbody>
142+
{% for item in data %}
143+
<tr>
144+
<td>{{ helper.render_data_cell(item, loop.index, cellPrefix) }}</td>
145+
<td>{{ helper.render_context_cell(item, loop.index, cellPrefix) }}</td>
146+
<td>{{ helper.render_encoder_cell(item, loop.index, cellPrefix) }}</td>
147+
<td>{{ helper.render_time_cell(item) }}</td>
148+
</tr>
149+
{% endfor %}
150+
</tbody>
151+
</table>
152+
{% endif %}
153+
</div>
154+
</div>
155+
{% endmacro %}
156+
157+
{% macro render_data_cell(item, index, method) %}
158+
{% set data_id = 'data-' ~ method ~ '-' ~ index %}
159+
160+
<span class="nowrap">{{ item.dataType }}</span>
161+
162+
<div>
163+
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ data_id }}" data-toggle-alt-content="Hide contents">Show contents</a>
164+
<div id="{{ data_id }}" class="context sf-toggle-content sf-toggle-hidden">
165+
{{ profiler_dump(item.data) }}
166+
</div>
167+
</div>
168+
{% endmacro %}
169+
170+
{% macro render_context_cell(item, index, method) %}
171+
{% set context_id = 'context-' ~ method ~ '-' ~ index %}
172+
173+
{% if item.type %}
174+
<span class="nowrap">Type: {{ item.type }}</span>
175+
<div class="nowrap">Format: {{ item.format ? item.format : 'none' }}</div>
176+
{% else %}
177+
<span class="nowrap">Format: {{ item.format ? item.format : 'none' }}</span>
178+
{% endif %}
179+
180+
<div>
181+
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ context_id }}" data-toggle-alt-content="Hide context">Show context</a>
182+
<div id="{{ context_id }}" class="context sf-toggle-content sf-toggle-hidden">
183+
{{ profiler_dump(item.context) }}
184+
</div>
185+
</div>
186+
{% endmacro %}
187+
188+
{% macro render_normalizer_cell(item, index, method) %}
189+
{% set nested_normalizers_id = 'nested-normalizers-' ~ method ~ '-' ~ index %}
190+
191+
<span class="nowrap"><a href="{{ item.normalizer.file|file_link(item.normalizer.line) }}" title="{{ item.normalizer.file }}">{{ item.normalizer.class }}</a> ({{ '%.2f'|format(item.normalizer.time * 1000) }} ms)</span>
192+
193+
{% if item.normalization|length > 1 %}
194+
<div>
195+
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ nested_normalizers_id }}" data-toggle-alt-content="Hide nested normalizers">Show nested normalizers</a>
196+
<div id="{{ nested_normalizers_id }}" class="context sf-toggle-content sf-toggle-hidden">
197+
<ul class="text-small" style="line-height:80%;margin-top:10px">
198+
{% for normalizer in item.normalization %}
199+
<li><span class="nowrap">x{{ normalizer.calls }} <a href="{{ normalizer.file|file_link(normalizer.line) }}" title="{{ normalizer.file }}">{{ normalizer.class }}</a> ({{ '%.2f'|format(normalizer.time * 1000) }} ms)</span></li>
200+
{% endfor %}
201+
</ul>
202+
</div>
203+
</div>
204+
{% endif %}
205+
{% endmacro %}
206+
207+
{% macro render_encoder_cell(item, index, method) %}
208+
{% set nested_encoders_id = 'nested-encoders-' ~ method ~ '-' ~ index %}
209+
210+
<span class="nowrap"><a href="{{ item.encoder.file|file_link(item.encoder.line) }}" title="{{ item.encoder.file }}">{{ item.encoder.class }}</a> ({{ '%.2f'|format(item.encoder.time * 1000) }} ms)</span>
211+
212+
{% if item.encoding|length > 1 %}
213+
<div>
214+
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ nested_encoders_id }}" data-toggle-alt-content="Hide nested encoders">Show nested encoders</a>
215+
<div id="{{ nested_encoders_id }}" class="context sf-toggle-content sf-toggle-hidden">
216+
<ul class="text-small" style="line-height:80%;margin-top:10px">
217+
{% for encoder in item.encoding %}
218+
<li><span class="nowrap">x{{ encoder.calls }} <a href="{{ encoder.file|file_link(encoder.line) }}" title="{{ encoder.file }}">{{ encoder.class }}</a> ({{ '%.2f'|format(encoder.time * 1000) }} ms)</span></li>
219+
{% endfor %}
220+
</ul>
221+
</div>
222+
</div>
223+
{% endif %}
224+
{% endmacro %}
225+
226+
{% macro render_time_cell(item) %}
227+
<span class="nowrap">{{ '%.2f'|format(item.time * 1000) }} ms</span>
228+
{% endmacro %}

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
6.1
55
---
66

7+
* Add `TraceableSerializer`, `TraceableNormalizer`, `TraceableEncoder` and `SerializerDataCollector` to integrate with the web profiler
78
* Add the ability to create contexts using context builders
89
* Set `Context` annotation as not final
910
* Deprecate `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead

0 commit comments

Comments
 (0)
0