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