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