如何创建自定义的数据收集器

3.4 版本
维护中的版本

Symfony Profiler(分析器)把数据收集(data collection)委托给了一些特殊的被称为data collector的类。Symfony对其中的一些打了包,但你可以轻松创建自己的收集器。

创建自定义的数据收集器 

创建自定义的数据收集器简单到只需实现DataCollectorInterface 接口:

1
2
3
4
5
interface DataCollectorInterface
{
    function collect(Request $request, Response $response, \Exception $exception = null);
    function getName();
}

getName() 返回data collector的名字,此名称在程序中必须唯一。这个值在后面也用于访问信息 (参考 如何在功能测试中使用分析器 for instance)。

collect() 负责在本地属性中存储收集来的数据。

多数时候,继承 DataCollector 和装载 $this->data 属性 (它掌控 $this->data 属性的序列化) 是很方便的。假设你要创建一个全新的数据收集器,用于从请求中收集method和可接受的content types:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/AppBundle/DataCollector/RequestCollector.php
namespace AppBundle\DataCollector;
 
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
 
class RequestCollector extends DataCollector
{
    public function collect(Request $request, Response $response, \Exception $exception = null)
    {
        $this->data = array(
            'method' => $request->getMethod(),
            'acceptable_content_types' => $request->getAcceptableContentTypes(),
        );
    }
 
    public function getMethod()
    {
        return $this->data['method'];
    }
 
    public function getAcceptableContentTypes()
    {
        return $this->data['acceptable_content_types'];
    }
 
    public function getName()
    {
        return 'app.request_collector';
    }
}

添加getters是为了让模板访问到所收集的信息。

由于分析器对data collecotor实例进行了序列化,你不应该存储那些不能被序列化的对象(如,PDO objects) 否则你需要提供自己的 serialize() 方法。

开启自定义数据收集器 

要开启data collector,将其定义为常规服务并打上 data_collector标签:

1
2
3
4
5
6
7
# app/config/services.yml
services:
    app.request_collector:
        class: AppBundle\DataCollector\RequestCollector
        public: false
        tags:
            - { name: data_collector }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 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.request_collector"
            class="AppBundle\DataCollector\RequestCollector"
            public="false"
        >
            <tag name="data_collector" />
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
// app/config/services.php
use AppBundle\DataCollector\RequestCollector;
 
$container
    ->register('app.request_collector', RequestCollector::class)
    ->setPublic(false)
    ->addTag('data_collector')
;

添加Web Profiler模板 

通过你的数据收集器所收集来的信息,可以被显示在web debug toolbar和web profiler中。要实现之,需要创建一个Twig模板,令其包容一些特定的block。

在最简单的例子中,你只需在工具条中显示信息,而不用再提供一个分析器面板。这需要定义 toolbar 块儿,并且设置其中的两个变量,分别是 icontext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
 
{% block toolbar %}
    {% set icon %}
        {# this is the content displayed as a panel in the toolbar #}
        {# 这是在工具条中作为面板来显示的内容 #}
        <span class="icon"><img src="..." alt=""/></span>
        <span class="sf-toolbar-status">Request</span>
    {% endset %}
 
    {% set text %}
        {# this is the content displayed when hovering the mouse over
           the toolbar panel #}
        {# 这是当鼠标悬停在工具条面板上时所显示的内容 #}
        <div class="sf-toolbar-info-piece">
            <b>Method</b>
            <span>{{ collector.method }}</span>
        </div>
 
        <div class="sf-toolbar-info-piece">
            <b>Accepted content type</b>
            <span>{{ collector.acceptableContentTypes|join(', ') }}</span>
        </div>
    {% endset %}
 
    {# the 'link' value set to 'false' means that this panel doesn't
       show a section in the web profiler #}
    {# 'link' 值设为 'false',表示在此面板中,不显示web profiler部分 #}
    {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: false }) }}
{% endblock %}

内置的collector模板,把所需之全部图片定义成基于base64加密的图片。这可令它们在任何地方工作,而不会和web资源的链接混为一谈:

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

另一个方案是把图片定义成SVG文件。除了分辨率是独立的之外,这些图片还可以轻易嵌入到Twig模板或从外部文件中包容,以实现在多个模板中的复用:

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

推荐在你自己的工具条面板中使用后一个技巧。

如果工具条面板继承了web profiler信息,Twig模板中必须同时定义附加blocks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
 
{% block toolbar %}
    {% set icon %}
        <span class="icon"><img src="..." alt=""/></span>
        <span class="sf-toolbar-status">Request</span>
    {% endset %}
 
    {% set text %}
        <div class="sf-toolbar-info-piece">
            {# ... #}
        </div>
    {% endset %}
 
    {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }}
{% endblock %}
 
{% block head %}
    {# Optional. Here you can link to or define your own CSS and JS contents. #}
    {# 可选。此处你可以链入自己的 CSS 和 JS 内容。 #}
    {# Use {{ parent() }} to extend the default styles instead of overriding them. #}
    {# 使用 {{ parent() }} 来继承默认样式而不是全盘覆写。 #}
{% endblock %}
 
{% block menu %}
    {# This left-hand menu appears when using the full-screen profiler. #}
    {# 当使用全屏profiler时,这个左侧菜单就会显示 #}
    <span class="label">
        <span class="icon"><img src="..." alt=""/></span>
        <strong>Request</strong>
    </span>
{% endblock %}
 
{% block panel %}
    {# Optional, for showing the most details. #}
    {# 可选,用于显示更多细节。 #}
    <h2>Acceptable Content Types</h2>
    <table>
        <tr>
            <th>Content Type</th>
        </tr>
 
        {% for type in collector.acceptableContentTypes %}
        <tr>
            <td>{{ type }}</td>
        </tr>
        {% endfor %}
    </table>
{% endblock %}

menupanel 块儿,是唯一需要定义内容以显示在“和这个data collector相关联”的分析器面板中的blocks。所有的block都可以访问到 collector 对象。

最后,要开启数据收集器模板,添加一个 template 属性到你的服务配置的 data_collector 标签中:

1
2
3
4
5
6
7
8
9
10
# app/config/services.yml
services:
    app.request_collector:
        class: AppBundle\DataCollector\RequestCollector
        tags:
            -
                name:     data_collector
                template: 'data_collector/template.html.twig'
                id:       'app.request_collector'
        public: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 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.request_collector"
            class="AppBundle\DataCollector\RequestCollector"
            public="false"
        >
            <tag name="data_collector"
                template="data_collector/template.html.twig"
                id="app.request_collector"
            />
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
10
11
// app/config/services.php
use AppBundle\DataCollector\RequestCollector;
 
$container
    ->register('app.request_collector', RequestCollector::class)
    ->setPublic(false)
    ->addTag('data_collector', array(
        'template' => 'data_collector/template.html.twig',
        'id'       => 'app.request_collector',
    ))
;

id 属性必须匹配由 getName() 方法返回的值。

工具条中的每一个面板的位置,是由定义在每个collector中的优先级所决定的。多数内置的收集器使用 255 作为其优先级。如果你希望自己的收集器可以显示在它们的前面,使用更高的值:

1
2
3
4
5
6
# app/config/services.yml
services:
    app.request_collector:
        class: AppBundle\DataCollector\RequestCollector
        tags:
            - { name: data_collector, template: '...', id: '...', priority: 300 }
1
2
3
4
<!-- app/config/services.xml -->
<service id="app.request_collector" class="AppBundle\DataCollector\RequestCollector">
    <tag name="data_collector" template="..." id="..." priority="300" />
</service>
1
2
3
4
5
6
7
8
9
10
11
// app/config/services.php
use AppBundle\DataCollector\RequestCollector;
 
$container
    ->register('app.request_collector', RequestCollector::class)
    ->addTag('data_collector', array(
        'template' => '...',
        'id'       => '...',
        'priority' => 300,
    ))
;

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)