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