@@ -211,11 +211,309 @@ event::
211211 $response = $event->getResponse();
212212 $response->headers->set('Symfony-Debug-Toolbar-Replace', 1);
213213 }
214+ .. index ::
215+ single: Profiling; Data collector
214216
215- .. toctree ::
216- :hidden:
217+ .. _profiler-data-collector :
217218
218- profiler/data_collector
219+ Creating a Data Collector
220+ =========================
221+
222+ The Symfony Profiler obtains its profiling and debug information using some
223+ special classes called data collectors. Symfony comes bundled with a few of
224+ them, but you can also create your own.
225+
226+ A data collector is a PHP class that implements the
227+ :class: `Symfony\\ Component\\ HttpKernel\\ DataCollector\\ DataCollectorInterface `.
228+ For convenience, your data collectors can also extend from the
229+ :class: `Symfony\\ Bundle\\ FrameworkBundle\\ DataCollector\\ AbstractDataCollector `
230+ class, which implements the interface and provides some utilities and the
231+ ``$this->data `` property to store the collected information.
232+
233+ .. versionadded :: 5.2
234+
235+ The ``AbstractDataCollector `` class was introduced in Symfony 5.2.
236+
237+ The following example shows a custom collector that stores information about the
238+ request::
239+
240+ // src/DataCollector/RequestCollector.php
241+ namespace App\DataCollector;
242+
243+ use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector;
244+ use Symfony\Component\HttpFoundation\Request;
245+ use Symfony\Component\HttpFoundation\Response;
246+
247+ class RequestCollector extends AbstractDataCollector
248+ {
249+ public function collect(Request $request, Response $response, \Throwable $exception = null)
250+ {
251+ $this->data = [
252+ 'method' => $request->getMethod(),
253+ 'acceptable_content_types' => $request->getAcceptableContentTypes(),
254+ ];
255+ }
256+ }
257+
258+ These are the method that you can define in the data collector class:
259+
260+ :method: `Symfony\\ Component\\ HttpKernel\\ DataCollector\\ DataCollectorInterface::collect ` method:
261+ Stores the collected data in local properties (``$this->data `` if you extend
262+ from ``AbstractDataCollector ``). If you need some services to collect the
263+ data, inject those services in the data collector constructor.
264+
265+ .. caution ::
266+
267+ The ``collect() `` method is only called once. It is not used to "gather"
268+ data but is there to "pick up" the data that has been stored by your
269+ service.
270+
271+ .. caution ::
272+
273+ As the profiler serializes data collector instances, you should not
274+ store objects that cannot be serialized (like PDO objects) or you need
275+ to provide your own ``serialize() `` method.
276+
277+ :method: `Symfony\\ Component\\ HttpKernel\\ DataCollector\\ DataCollectorInterface::reset ` method:
278+ It's called between requests to reset the state of the profiler. By default
279+ it only empties the ``$this->data `` contents, but you can override this method
280+ to do additional cleaning.
281+
282+ :method: `Symfony\\ Component\\ HttpKernel\\ DataCollector\\ DataCollectorInterface::getName ` method:
283+ Returns the collector identifier, which must be unique in the application.
284+ By default it returns the FQCN of the data collector class, but you can
285+ override this method to return a custom name (e.g. ``app.request_collector ``).
286+ This value is used later to access the collector information (see
287+ :doc: `/testing/profiling `) so you may prefer using short strings instead of FQCN strings.
288+
289+ The ``collect() `` method is called during the :ref: `kernel.response <component-http-kernel-kernel-response >`
290+ event. If you need to collect data that is only available later, implement
291+ :class: `Symfony\\ Component\\ HttpKernel\\ DataCollector\\ LateDataCollectorInterface `
292+ and define the ``lateCollect() `` method, which is invoked right before the profiler
293+ data serialization (during :ref: `kernel.terminate <component-http-kernel-kernel-terminate >` event).
294+
295+ .. note ::
296+
297+ If you're using the :ref: `default services.yaml configuration <service-container-services-load-example >`
298+ with ``autoconfigure ``, then Symfony will start using your data collector after the
299+ next page refresh. Otherwise, :ref: `enable the data collector by hand <data_collector_tag >`.
300+
301+ Adding Web Profiler Templates
302+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
303+
304+ The information collected by your data collector can be displayed both in the
305+ web debug toolbar and in the web profiler. To do so, you need to create a Twig
306+ template that includes some specific blocks.
307+
308+ First, add the ``getTemplate() `` method in your data collector class to return
309+ the path of the Twig template to use. Then, add some *getters * to give the
310+ template access to the collected information::
311+
312+ // src/DataCollector/RequestCollector.php
313+ namespace App\DataCollector;
314+
315+ use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector;
316+
317+ class RequestCollector extends AbstractDataCollector
318+ {
319+ // ...
320+
321+ public static function getTemplate(): ?string
322+ {
323+ return 'data_collector/template.html.twig';
324+ }
325+
326+ public function getMethod()
327+ {
328+ return $this->data['method'];
329+ }
330+
331+ public function getAcceptableContentTypes()
332+ {
333+ return $this->data['acceptable_content_types'];
334+ }
335+ }
336+
337+ In the simplest case, you want to display the information in the toolbar
338+ without providing a profiler panel. This requires to define the ``toolbar ``
339+ block and set the value of two variables called ``icon `` and ``text ``:
340+
341+ .. code-block :: html+twig
342+
343+ {# templates/data_collector/template.html.twig #}
344+ {% extends '@WebProfiler/Profiler/layout.html.twig' %}
345+
346+ {% block toolbar %}
347+ {% set icon %}
348+ {# this is the content displayed as a panel in the toolbar #}
349+ <svg xmlns="http://www.w3.org/2000/svg"> ... </svg>
350+ <span class="sf-toolbar-value">Request</span>
351+ {% endset %}
352+
353+ {% set text %}
354+ {# this is the content displayed when hovering the mouse over
355+ the toolbar panel #}
356+ <div class="sf-toolbar-info-piece">
357+ <b>Method</b>
358+ <span>{{ collector.method }}</span>
359+ </div>
360+
361+ <div class="sf-toolbar-info-piece">
362+ <b>Accepted content type</b>
363+ <span>{{ collector.acceptableContentTypes|join(', ') }}</span>
364+ </div>
365+ {% endset %}
366+
367+ {# the 'link' value set to 'false' means that this panel doesn't
368+ show a section in the web profiler #}
369+ {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: false }) }}
370+ {% endblock %}
371+
372+ .. tip ::
373+
374+ Built-in collector templates define all their images as embedded SVG files.
375+ This makes them work everywhere without having to mess with web assets links:
376+
377+ .. code-block :: twig
378+
379+ {% set icon %}
380+ {{ include('data_collector/icon.svg') }}
381+ {# ... #}
382+ {% endset %}
383+
384+ If the toolbar panel includes extended web profiler information, the Twig template
385+ must also define additional blocks:
386+
387+ .. code-block :: html+twig
388+
389+ {# templates/data_collector/template.html.twig #}
390+ {% extends '@WebProfiler/Profiler/layout.html.twig' %}
391+
392+ {% block toolbar %}
393+ {% set icon %}
394+ {# ... #}
395+ {% endset %}
396+
397+ {% set text %}
398+ <div class="sf-toolbar-info-piece">
399+ {# ... #}
400+ </div>
401+ {% endset %}
402+
403+ {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }}
404+ {% endblock %}
405+
406+ {% block head %}
407+ {# Optional. Here you can link to or define your own CSS and JS contents. #}
408+ {# Use {{ parent() }} to extend the default styles instead of overriding them. #}
409+ {% endblock %}
410+
411+ {% block menu %}
412+ {# This left-hand menu appears when using the full-screen profiler. #}
413+ <span class="label">
414+ <span class="icon"><img src="..." alt=""/></span>
415+ <strong>Request</strong>
416+ </span>
417+ {% endblock %}
418+
419+ {% block panel %}
420+ {# Optional, for showing the most details. #}
421+ <h2>Acceptable Content Types</h2>
422+ <table>
423+ <tr>
424+ <th>Content Type</th>
425+ </tr>
426+
427+ {% for type in collector.acceptableContentTypes %}
428+ <tr>
429+ <td>{{ type }}</td>
430+ </tr>
431+ {% endfor %}
432+ </table>
433+ {% endblock %}
434+
435+ The ``menu `` and ``panel `` blocks are the only required blocks to define the
436+ contents displayed in the web profiler panel associated with this data collector.
437+ All blocks have access to the ``collector `` object.
438+
439+ .. note ::
440+
441+ The position of each panel in the toolbar is determined by the collector
442+ priority, which can only be defined when :ref: `configuring the data collector by hand <data_collector_tag >`.
443+
444+ .. note ::
445+
446+ If you're using the :ref: `default services.yaml configuration <service-container-services-load-example >`
447+ with ``autoconfigure ``, then Symfony will start displaying your collector data
448+ in the toolbar after the next page refresh. Otherwise, :ref: `enable the data collector by hand <data_collector_tag >`.
449+
450+ .. _data_collector_tag :
451+
452+ Enabling Custom Data Collectors
453+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
454+
455+ If you don't use Symfony's default configuration with
456+ :ref: `autowire and autoconfigure <service-container-services-load-example >`
457+ you'll need to configure the data collector explicitly:
458+
459+ .. configuration-block ::
460+
461+ .. code-block :: yaml
462+
463+ # config/services.yaml
464+ services :
465+ App\DataCollector\RequestCollector :
466+ tags :
467+ -
468+ name : data_collector
469+ # must match the value returned by the getName() method
470+ id : ' App\DataCollector\RequestCollector'
471+ # optional template (it has more priority than the value returned by getTemplate())
472+ template : ' data_collector/template.html.twig'
473+ # optional priority (positive or negative integer; default = 0)
474+ # priority: 300
475+
476+ .. code-block :: xml
477+
478+ <!-- config/services.xml -->
479+ <?xml version =" 1.0" encoding =" UTF-8" ?>
480+ <container xmlns =" http://symfony.com/schema/dic/services"
481+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
482+ xsi : schemaLocation =" http://symfony.com/schema/dic/services
483+ https://symfony.com/schema/dic/services/services-1.0.xsd" >
484+
485+ <services >
486+ <service id =" App\DataCollector\RequestCollector" >
487+ <!-- the 'template' attribute has more priority than the value returned by getTemplate() -->
488+ <tag name =" data_collector"
489+ id =" App\DataCollector\RequestCollector"
490+ template =" data_collector/template.html.twig"
491+ />
492+ <!-- optional 'priority' attribute (positive or negative integer; default = 0) -->
493+ <!-- priority="300" -->
494+ </service >
495+ </services >
496+ </container >
497+
498+ .. code-block :: php
499+
500+ // config/services.php
501+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
502+
503+ use App\DataCollector\RequestCollector;
504+
505+ return function(ContainerConfigurator $containerConfigurator) {
506+ $services = $containerConfigurator->services();
507+
508+ $services->set(RequestCollector::class)
509+ ->tag('data_collector', [
510+ 'id' => RequestCollector::class,
511+ // optional template (it has more priority than the value returned by getTemplate())
512+ 'template' => 'data_collector/template.html.twig',
513+ // optional priority (positive or negative integer; default = 0)
514+ // 'priority' => 300,
515+ ]);
516+ };
219517
220518 .. _`Single-page applications` : https://en.wikipedia.org/wiki/Single-page_application
221519.. _`Blackfire` : https://blackfire.io/docs/introduction?utm_source=symfony&utm_medium=symfonycom_docs&utm_campaign=profiler
0 commit comments