Skip to content

Commit

Permalink
[Form] Added form debug collector
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalkaoz authored and webmozart committed Sep 25, 2013
1 parent 98c0d38 commit 1972a91
Show file tree
Hide file tree
Showing 12 changed files with 542 additions and 0 deletions.
Expand Up @@ -218,6 +218,7 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
return;
}

$loader->load('form_debug.xml');

This comment has been minimized.

Copy link
@tyx

tyx Sep 26, 2013

Contributor

If form config is not enabled, this line will break as it use %form.resolved_type_factory.class% parameter, defined in form.xml.

So I guess we should load it, only if form config is neabled.

edit : Here is a PR : #9137

$loader->load('profiling.xml');
$loader->load('collectors.xml');

Expand Down
Expand Up @@ -13,6 +13,7 @@
<parameter key="data_collector.time.class">Symfony\Component\HttpKernel\DataCollector\TimeDataCollector</parameter>
<parameter key="data_collector.memory.class">Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector</parameter>
<parameter key="data_collector.router.class">Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector</parameter>
<parameter key="data_collector.form.class">Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector</parameter>
</parameters>

<services>
Expand Down Expand Up @@ -53,5 +54,10 @@
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController"/>
<tag name="data_collector" template="@WebProfiler/Collector/router.html.twig" id="router" priority="255" />
</service>

<service id="data_collector.form" class="%data_collector.form.class%" >
<tag name="data_collector" template="@WebProfiler/Collector/form.html.twig" id="form" priority="255" />
</service>

</services>
</container>
22 changes: 22 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml
@@ -0,0 +1,22 @@
<?xml version="1.0" ?>

<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">

<parameters>
<parameter key="form.type_extension.form.data_collector.class">Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension</parameter>
<parameter key="form.type_extension.form.data_collector.event_subscriber.class">Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorSubscriber</parameter>
</parameters>

<services>
<!-- DataCollectorExtension -->
<service id="form.type_extension.form.data_collector" class="%form.type_extension.form.data_collector.class%">
<tag name="form.type_extension" alias="form" />
<argument type="service" id="form.type_extension.form.data_collector.event_subscriber" />
</service>
<service id="form.type_extension.form.data_collector.event_subscriber" class="%form.type_extension.form.data_collector.event_subscriber.class%" public="false">
<argument type="service" id="data_collector.form" />
</service>
</services>
</container>
@@ -0,0 +1,56 @@
{% extends app.request.isXmlHttpRequest ? 'WebProfilerBundle:Profiler:ajax_layout.html.twig' : 'WebProfilerBundle:Profiler:layout.html.twig' %}

{% block toolbar %}
{% if collector.dataCount %}
{% set icon %}
<img width="20" height="28" alt="Forms" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAYAAAB836/YAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDNkE1RUQ2RjBFMzcxMUUzOEIyQkVBRkQ5QzNENTIxMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDNkE1RUQ3MDBFMzcxMUUzOEIyQkVBRkQ5QzNENTIxMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkM2QTVFRDZEMEUzNzExRTM4QjJCRUFGRDlDM0Q1MjEzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkM2QTVFRDZFMEUzNzExRTM4QjJCRUFGRDlDM0Q1MjEzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+fkaAkgAAAQtJREFUeNpi/P//PwM1ARMDlQGLg4MDgsPCAqb//PnDwMzMDGJ+//v3L4oGqDgnSBxZPdwMLJa4ggioQRzoklBxVySh3SguBOICGOffv38TgNQuIny2C6oeRDEim8Fob28PjxVgBIEk/zMyMhIVXqAIZYQo/o81UlhZWcGYWIBNLQta+PACKS1iDYSGJy9OA4Fe+EBKUsKWhlmwpMtZZCS/NHzJ5iFFCRuN7wXEEtQ0sJDirIfGrwBiLiL0HSbWwLPoWQkH2I0tu+KKlOVk+BSngclALEnNMHSldqTMJtOcCFwGNlDbhZ1Y1LwhwpwTuAz0waI4gxIXPqC2lzuoYeAPalajjIO+ogcIMAAaCkonoOPIhQAAAABJRU5ErkJggg=="/>
<span class="sf-toolbar-status{% if collector.dataCount %} sf-toolbar-status-red{% endif %}">{{ collector.dataCount }}</span>
{% endset %}

{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
{% endif %}
{% endblock %}

{% block menu %}
<span class="label">
<span class="icon"><img src="data:image/png;base64,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" alt=""/></span>
<strong>Forms</strong>
{% if collector.dataCount %}
<span class="count"><span>{{ collector.dataCount }}</span></span>
{% endif %}
</span>
{% endblock %}

{% block panel %}
<h2>Invalid Forms</h2>

{% for formName, fields in collector.data %}
<h3>{{ formName }}</h3>
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Value</th>
<th>Messages</th>
</tr>
{% for fieldName, field in fields %}
<tr>
<td><b>{{ fieldName }}</b></td>
<td>{{ field.type }}</td>
<td>{{ field.value }}</td>
<td>
<ul>
{% for errorMessage in field.errors %}
<li>
{{ errorMessage.message }}
</li>
{% endfor %}
</ul>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<em>No invalid form{% if collector.dataCount > 1 %}s{% endif %} detected for this request.</em>
{% endfor %}
{% endblock %}
@@ -0,0 +1,79 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Form\Extension\DataCollector\Collector;

use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector as BaseCollector;

/**
* DataCollector for Form Validation Failures.
*
* @author Robert Schönthal <robert.schoenthal@gmail.com>
*/
class FormCollector extends BaseCollector
{
/**
* {@inheritDoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
//nothing to do, everything is added with addError()
}

/**
* Adds a Form-Error to the Collector.
*
* @param FormInterface $form
*/
public function addError(FormInterface $form)
{
$storeData = array(
'root' => $form->getRoot()->getName(),
'name' => (string)$form->getPropertyPath(),
'type' => $form->getConfig()->getType()->getName(),
'errors' => $form->getErrors(),
'value' => $this->varToString($form->getViewData())
);

$this->data[$storeData['root']][$storeData['name']] = $storeData;
}

/**
* {@inheritDoc}
*/
public function getName()
{
return 'form';
}

/**
* Returns all collected Data.
*
* @return array
*/
public function getData()
{
return $this->data;
}

/**
* Returns the number of invalid Forms.
*
* @return integer
*/
public function getDataCount()
{
return count($this->data);
}
}
@@ -0,0 +1,43 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Form\Extension\DataCollector;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\AbstractExtension;

/**
* DataCollectorExtension for collecting Form Validation Failures.
*
* @author Robert Schönthal <robert.schoenthal@gmail.com>
*/
class DataCollectorExtension extends AbstractExtension
{
/**
* @var EventSubscriberInterface
*/
private $eventSubscriber;

public function __construct(EventSubscriberInterface $eventSubscriber)
{
$this->eventSubscriber = $eventSubscriber;
}

/**
* {@inheritDoc}
*/
protected function loadTypeExtensions()
{
return array(
new Type\DataCollectorTypeExtension($this->eventSubscriber)
);
}
}
@@ -0,0 +1,75 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Form\Extension\DataCollector\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;

/**
* EventSubscriber for adding Form Validation Failures to the DataCollector.
*
* @author Robert Schönthal <robert.schoenthal@gmail.com>
*/
class DataCollectorSubscriber implements EventSubscriberInterface
{
/**
* @var FormCollector
*/
private $collector;

public function __construct(FormCollector $collector)
{
$this->collector = $collector;
}

/**
* {@inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(FormEvents::POST_SUBMIT => array('addToProfiler', -255));
}

/**
* Searches for invalid Form-Data and adds them to the Collector.
*
* @param FormEvent $event The event object
*/
public function addToProfiler(FormEvent $event)
{
$form = $event->getForm();

if ($form->isRoot()) {
$this->addErrors($form);
}
}

/**
* Adds an invalid Form-Element to the Collector.
*
* @param FormInterface $form
*/
private function addErrors(FormInterface $form)
{
if ($form->getErrors()) {
$this->collector->addError($form);
}

//recursively add all child errors
foreach ($form->all() as $field) {
$this->addErrors($field);
}
}
}
@@ -0,0 +1,50 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Form\Extension\DataCollector\Type;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;

/**
* DataCollector Type Extension for collecting invalid Forms.
*
* @author Robert Schönthal <robert.schoenthal@gmail.com>
*/
class DataCollectorTypeExtension extends AbstractTypeExtension
{
/**
* @var EventSubscriberInterface
*/
private $eventSubscriber;

public function __construct(EventSubscriberInterface $eventSubscriber)
{
$this->eventSubscriber = $eventSubscriber;
}

/**
* {@inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber($this->eventSubscriber);
}

/**
* {@inheritDoc}
*/
public function getExtendedType()
{
return 'form';
}
}

0 comments on commit 1972a91

Please sign in to comment.