Skip to content

Updated the article about data collectors #5592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 10, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 172 additions & 71 deletions cookbook/profiler/data_collector.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
How to Create a custom Data Collector
=====================================

:doc:`The Symfony Profiler </cookbook/profiler/index>` delegates data collecting to
data collectors. Symfony comes bundled with a few of them, but you can easily
create your own.
:doc:`The Symfony Profiler </cookbook/profiler/index>` delegates data collection
to some special classes called data collectors. Symfony comes bundled with a few
of them, but you can easily create your own.

Creating a custom Data Collector
--------------------------------
Expand All @@ -33,12 +33,12 @@ Creating a custom data collector is as simple as implementing the
function getName();
}

The ``getName()`` method must return a unique name. This is used to access the
information later on (see :doc:`/cookbook/testing/profiling` for
instance).
The value returned by ``getName()`` must be unique in the application. This value
is also used to access the information later on (see :doc:`/cookbook/testing/profiling`
for instance).

The ``collect()`` method is responsible for storing the data it wants to give
access to in local properties.
The ``collect()`` method is responsible for storing the collected data in local
properties.

.. caution::

Expand All @@ -51,101 +51,169 @@ Most of the time, it is convenient to extend
populate the ``$this->data`` property (it takes care of serializing the
``$this->data`` property)::

class MemoryDataCollector extends DataCollector
// src/AppBundle/DataCollector/MyCollector.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class namespace is missing.

use Symfony\Component\HttpKernel\DataCollector\DataCollector;

class MyCollector extends DataCollector
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing use statement for the DataCollector class.

{
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(
'memory' => memory_get_peak_usage(true),
'variable' => 'value',
);
}

public function getMemory()
public function getVariable()
{
return $this->data['memory'];
return $this->data['variable'];
}

public function getName()
{
return 'memory';
return 'app.my_collector';
}
}

.. _data_collector_tag:

Enabling custom Data Collectors
Enabling Custom Data Collectors
-------------------------------

To enable a data collector, add it as a regular service in one of your
configuration, and tag it with ``data_collector``:
To enable a data collector, define it as a regular service and tag it with
``data_collector``:

.. configuration-block::

.. code-block:: yaml

# app/config/services.yml
services:
data_collector.your_collector_name:
class: Fully\Qualified\Collector\Class\Name
app.my_collector:
class: AppBundle\DataCollector\MyCollector
public: false
tags:
- { name: data_collector }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service should be private.


.. code-block:: xml

<service id="data_collector.your_collector_name" class="Fully\Qualified\Collector\Class\Name">
<tag name="data_collector" />
</service>
<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd"
>
<services>
<service id="app.my_collector" class="AppBundle\DataCollector\MyCollector"
public="false">
<tag name="data_collector" />
</service>
</services>
</container>

.. code-block:: php

// app/config/services.php
$container
->register('data_collector.your_collector_name', 'Fully\Qualified\Collector\Class\Name')
->register('app.my_collector', 'AppBundle\DataCollector\MyCollector')
->setPublic(false)
->addTag('data_collector')
;

Adding Web Profiler Templates
-----------------------------

When you want to display the data collected by your data collector in the web
debug toolbar or the web profiler, you will need to create a Twig template. The
following example can help you get started:
The information collected by your data collector can be displayed both in the
web debug toolbar and in the web profiler. To do so, you need to create a Twig
template that includes some specific blocks.

In the simplest case, you just want to display the information in the toolbar
without providing a profiler panel. This requires to define the ``toolbar``
block and set the value of two variables called ``icon`` and ``text``:

.. code-block:: jinja
.. code-block:: html+jinja

{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}

{% block toolbar %}
{# This toolbar item may appear along the top or bottom of the screen.#}
{% set icon %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add comments before the icon and text variable to explain what they are

<span class="icon"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAcCAQAAADVGmdYAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffAxkBCDStonIVAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAHpJREFUOMtj3PWfgXRAuqZd/5nIsIdhVBPFmgqIjCuYOrJsYtz1fxuUOYER2TQID8afwIiQ8YIkI4TzCv5D2AgaWSuExJKMIDbA7EEVhQEWXJ6FKUY4D48m7HYU/EcWZ8JlE6qfMELPDcUJuEMPxvYazYTDWRMjOcUyAEswO+VjeQQaAAAAAElFTkSuQmCC" alt=""/></span>
<span class="sf-toolbar-status">Example</span>
{# this is the content displayed as a panel in the toolbar #}
<span class="icon"><img src="..." alt=""/></span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we show SVG + include, as that's the standard?

<span class="sf-toolbar-status">Information</span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be lost

{% endset %}

{% set text %}
<div class="sf-toolbar-info-piece">
<b>Quick piece of data</b>
<span>100 units</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Another quick thing</b>
<span>300 units</span>
</div>
{# this is the content displayed when hovering the mouse over
the toolbar panel #}
<div class="sf-toolbar-info-piece">
<b>Quick piece of data</b>
<span>100 units</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Another piece of data</b>
<span>300 units</span>
</div>
{% endset %}

{# Set the "link" value to false if you do not have a big "panel"
section that you want to direct the user to. #}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }}
{# the 'link' value set to 'false' means that this panel doesn't
show a section in the web profiler. #}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: false }) }}
{% endblock %}

.. tip::

Built-in collector templates define all their images as embedded base64-encoded
images. This makes them work everywhere without having to mess with web assets
links:

.. code-block:: html

<img src="data:image/png;base64,..." />

Another solution is to define the images as SVG files. In addition to being
resolution-independent, these images can be easily embedded in the Twig
template or included from an external file to reuse them in several templates:

.. code-block:: jinja

{{ include('@App/data_collector/icon.svg') }}

You are encouraged to use the latter technique for your own toolbar panels.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

always put the recommended method before other methods. Can you please move this one before the data URL?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, but this article is for Symfony 2.3. In that version, the recommendation is to use base64 and <img>. Once this article is merged, I'll send a new PR for 2.7 with lots of changes and modern recommendations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think even 2.3 docs should advocate the modern recommendations. We still maintain that version, so it's not frozen back in the time.


If the toolbar panel includes extended web profiler information, the Twig template
must also define additional blocks:

.. code-block:: html+jinja

{% extends '@WebProfiler/Profiler/layout.html.twig' %}

{% block toolbar %}
{% set icon %}
<span class="icon"><img src="..." alt=""/></span>
<span class="sf-toolbar-status">Information</span>
{% endset %}

{% set text %}
<div class="sf-toolbar-info-piece">
{# ... #}
</div>
{% endset %}

{# the 'link' value is now set to 'true', which allows the user to click
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should addd a note sayign it is the default, and so you don't need to change the value of the variable here

on it to access the web profiler panel. Since 'true' is the default
value, you can omit the 'link' parameter entirely #}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: true }) }}
{% endblock %}

{% block head %}
{# Optional, if you need your own JS or CSS files. #}
{{ parent() }} {# Use parent() to keep the default styles #}
{# Optional, you can here link to or define your own CSS and JS contents #}
{# {{ parent() }} to keep the default styles #}
{% endblock %}

{% block menu %}
{# This left-hand menu appears when using the full-screen profiler. #}
<span class="label">
<span class="icon"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAcCAQAAADVGmdYAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffAxkBCDStonIVAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAHpJREFUOMtj3PWfgXRAuqZd/5nIsIdhVBPFmgqIjCuYOrJsYtz1fxuUOYER2TQID8afwIiQ8YIkI4TzCv5D2AgaWSuExJKMIDbA7EEVhQEWXJ6FKUY4D48m7HYU/EcWZ8JlE6qfMELPDcUJuEMPxvYazYTDWRMjOcUyAEswO+VjeQQaAAAAAElFTkSuQmCC" alt=""/></span>
<span class="icon"><img src="..." alt=""/></span>
<strong>Example Collector</strong>
</span>
{% endblock %}
Expand All @@ -158,57 +226,90 @@ following example can help you get started:
</p>
{% endblock %}

Each block is optional. The ``toolbar`` block is used for the web debug
toolbar and ``menu`` and ``panel`` are used to add a panel to the web
profiler.

The ``menu`` and ``panel`` blocks are the only required blocks to define the
contents displayed in the web profiler panel associated with this data collector.
All blocks have access to the ``collector`` object.

.. tip::
Finally, to enable the data collector template, add a ``template`` attribute to
the ``data_collector`` tag in your service configuration:

Built-in templates use a base64 encoded image for the toolbar:
.. configuration-block::

.. code-block:: html
.. code-block:: yaml

<img src="data:image/png;base64,..." />
# app/config/services.yml
services:
app.my_collector:
class: AppBundle\DataCollector\MyCollector
tags:
-
name: data_collector
template: 'data_collector/template.html.twig'
id: 'app.my_collector'
public: false

You can easily calculate the base64 value for an image with this
little script::
.. code-block:: xml

#!/usr/bin/env php
<?php
echo base64_encode(file_get_contents($_SERVER['argv'][1]));
<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd"
>
<services>
<service id="app.my_collector" class="AppBundle\DataCollector\MyCollector" public="false">
<tag name="data_collector" template="data_collector/template.html.twig" id="app.my_collector" />
</service>
</services>
</container>

To enable the template, add a ``template`` attribute to the ``data_collector``
tag in your configuration. For example, assuming your template is in AppBundle:
.. code-block:: php

// app/config/services.php
$container
->register('app.my_collector', 'AppBundle\DataCollector\MyCollector')
->setPublic(false)
->addTag('data_collector', array(
'template' => 'data_collector/template.html.twig',
'id' => 'app.my_collector',
))
;

.. caution::

The ``id`` attribute must match the value returned by the ``getName()`` method.

The position of each panel in the toolbar is determined by the priority defined
by each collector. Most built-in collectors use ``255`` as their priority. If you
want your collector to be displayed before them, use a higher value:

.. configuration-block::

.. code-block:: yaml

# app/config/services.yml
services:
data_collector.your_collector_name:
class: AppBundle\Collector\Class\Name
app.my_collector:
class: AppBundle\DataCollector\MyCollector
tags:
- { name: data_collector, template: "AppBundle:Collector:templatename", id: "your_collector_name" }
- { name: data_collector, template: '...', id: '...', priority: 300 }

.. code-block:: xml

<service id="data_collector.your_collector_name" class="AppBundle\Collector\Class\Name">
<tag name="data_collector" template="AppBundle:Collector:templatename" id="your_collector_name" />
<!-- app/config/services.xml -->
<service id="app.my_collector" class="AppBundle\DataCollector\MyCollector">
<tag name="data_collector" template="..." id="..." priority="300" />
</service>

.. code-block:: php

// app/config/services.php
$container
->register('data_collector.your_collector_name', 'AppBundle\Collector\Class\Name')
->register('app.my_collector', 'AppBundle\DataCollector\MyCollector')
->addTag('data_collector', array(
'template' => 'AppBundle:Collector:templatename',
'id' => 'your_collector_name',
'template' => '...',
'id' => '...',
'priority' => 300,
))
;

.. caution::

Make sure the ``id`` attribute is the same string you used for the
``getName()`` method.