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