@@ -211,11 +211,309 @@ event::
211
211
$response = $event->getResponse();
212
212
$response->headers->set('Symfony-Debug-Toolbar-Replace', 1);
213
213
}
214
+ .. index ::
215
+ single: Profiling; Data collector
214
216
215
- .. toctree ::
216
- :hidden:
217
+ .. _profiler-data-collector :
217
218
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
+ };
219
517
220
518
.. _`Single-page applications` : https://en.wikipedia.org/wiki/Single-page_application
221
519
.. _`Blackfire` : https://blackfire.io/docs/introduction?utm_source=symfony&utm_medium=symfonycom_docs&utm_campaign=profiler
0 commit comments