[Form] Improved form debugger

This commit is contained in:
Bernhard Schussek 2013-09-19 18:16:39 +02:00
parent f56c5774a8
commit 89509d9847
19 changed files with 2023 additions and 242 deletions

View File

@ -13,7 +13,8 @@
<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>
<parameter key="data_collector.form.class">Symfony\Component\Form\Extension\DataCollector\FormDataCollector</parameter>
<parameter key="data_collector.form.extractor.class">Symfony\Component\Form\Extension\DataCollector\FormDataExtractor</parameter>
</parameters>
<services>
@ -55,8 +56,11 @@
<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%" >
<service id="data_collector.form.extractor" class="%data_collector.form.extractor.class%" />
<service id="data_collector.form" class="%data_collector.form.class%">
<tag name="data_collector" template="@WebProfiler/Collector/form.html.twig" id="form" priority="255" />
<argument type="service" id="data_collector.form.extractor" />
</service>
</services>

View File

@ -5,11 +5,19 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="form.resolved_type_factory.data_collector_proxy.class">Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy</parameter>
<parameter key="form.type_extension.form.data_collector.class">Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension</parameter>
</parameters>
<services>
<!-- DataCollectorExtension -->
<service id="form.resolved_type_factory" class="%form.resolved_type_factory.data_collector_proxy.class%">
<argument type="service">
<service class="%form.resolved_type_factory.class%" />
</argument>
<argument type="service" id="data_collector.form" />
</service>
<!-- DataCollectorTypeExtension -->
<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="data_collector.form" />

View File

@ -1,10 +1,12 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% from _self import form_tree_entry, form_tree_details %}
{% block toolbar %}
{% if collector.data|length %}
{% 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 sf-toolbar-status-{% if collector.errorCount %}red{% else %}green{% endif %}">{% if collector.errorCount %}{{ collector.errorCount }}{% else %}{{ collector.data|length }}{% endif %}</span>
<span class="sf-toolbar-status sf-toolbar-status-{% if collector.data.nb_errors %}red{% else %}green{% endif %}">{% if collector.data.nb_errors %}{{ collector.data.nb_errors }}{% else %}{{ collector.data.forms|length }}{% endif %}</span>
{% endset %}
{% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %}
@ -13,48 +15,331 @@
{% block menu %}
<span class="label">
<span class="icon"><img src="data:image/png;base64,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" alt=""/></span>
<span class="icon"><img src="data:image/png;base64,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" alt=""/></span>
<strong>Forms</strong>
{% if collector.data|length %}
<span class="count"><span>{{ collector.data|length }}</span></span>
{% if collector.data.forms|length %}
<span class="count"><span>{{ collector.data.forms|length }}</span></span>
{% endif %}
</span>
{% endblock %}
{% block panel %}
<h2>Form{% if collector.data|length > 1 %}s{% endif %}</h2>
<style type="text/css">
.window {
/*background: #F6F6F6;*/
margin: -30px -40px -40px;
}
.tree {
width: 230px;
padding: 10px;
font-size: 12px;
float: left;
}
#content .tree h2 {
font-size: 13px;
padding: 5px 7px;
margin: 0;
}
.tree li {
margin: 0;
padding: 0;
width: 100%;
}
.tree a {
text-decoration: none;
display: block;
padding: 5px 7px;
border-radius: 6px;
color: #313131;
}
.tree ul ul a {
padding-left: 22px;
}
.tree ul ul ul a {
padding-left: 37px;
}
.tree ul ul ul ul a {
padding-left: 52px;
}
.tree ul ul ul ul ul a {
padding-left: 67px;
}
.tree a:hover {
background: #dfdfdf;
}
.tree a.active, a.active:hover {
background: #dfdfdf;
font-weight: bold;
color: #313131;
}
.tree-details {
border-left: 1px solid #dfdfdf;
background: white;
margin-left: 250px;
padding: 30px 40px 40px;
}
.form-type {
color: #999999;
}
.hidden {
display: none;
}
</style>
{% if collector.data.forms|length %}
<div class="window">
<div class="tree">
<h2>Forms</h2>
<ul>
{% for formName, formData in collector.data.forms %}
{{ form_tree_entry(formName, formData) }}
{% endfor %}
</ul>
</div>
{% for formName, formData in collector.data.forms %}
{{ form_tree_details(formName, formData) }}
{% endfor %}
</div>
{% else %}
<p><em>No forms were submitted for this request.</em></p>
{% endif %}
<script>
function TabView() {
var _activeLink = null,
_activeView = null;
this.init = function () {
var links = document.querySelectorAll('.tree a'),
views = document.querySelectorAll('.tree-details'),
i,
l;
for (i = 0, l = links.length; i < l; ++i) {
(function () {
var link = links[i];
link.addEventListener('click', function (e) {
var href = link.getAttribute('href'),
targetId = href.substr(href.indexOf('#') + 1),
view = document.getElementById(targetId);
if (view) {
if (null !== _activeLink) {
Sfjs.removeClass(_activeLink, 'active');
}
if (null !== _activeView) {
Sfjs.addClass(_activeView, 'hidden');
}
Sfjs.addClass(link, 'active');
Sfjs.removeClass(view, 'hidden');
_activeLink = link;
_activeView = view;
}
e.preventDefault();
return false;
})
}());
}
for (i = 0, l = views.length; i < l; ++i) {
Sfjs.addClass(views[i], 'hidden');
}
if (links.length > 0) {
Sfjs.addClass(links[0], 'active');
_activeLink = links[0];
if (views.length > 0) {
Sfjs.removeClass(views[0], 'hidden');
_activeView = views[0];
}
}
}
}
var tabView = new TabView();
tabView.init();
</script>
{% endblock %}
{% macro form_tree_entry(name, data) %}
<li>
<a href="#details_{{ data.view_vars.id|default("") }}">{{ name }}</a>
{% if data.children|length > 1 %}
<ul>
{% for childName, childData in data.children %}
{{ _self.form_tree_entry(childName, childData) }}
{% endfor %}
</ul>
{% endif %}
</li>
{% endmacro %}
{% macro form_tree_details(name, data) %}
<div class="tree-details" id="details_{{ data.view_vars.id|default("") }}">
<h2>
{{ name }}
{% if data.type_class is defined %}
<span class="form-type">[<abbr title="{{ data.type_class }}">{{ data.type }}</abbr>]</span>
{% endif %}
</h2>
{% if data.errors is defined and data.errors|length > 0 %}
<h3>Errors</h3>
{% for formName, fields in collector.data %}
<h3>{{ formName }}</h3>
{% if fields %}
<table>
<tr>
<th>Field</th>
<th>Type</th>
<th>Value</th>
<th>Messages</th>
<th width="50%">Message</th>
<th>Cause</th>
</tr>
{% for fieldName, field in fields %}
{% for error in data.errors %}
<tr>
<td>{{ error.message }}</td>
<td><em>Unknown.</em></td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if data.default_data is defined %}
<h3>Default Data</h3>
<table>
<tr>
<th width="180">Model Format</th>
<td>
{% if data.default_data.model is defined %}
<pre>{{ data.default_data.model }}</pre>
{% else %}
<em>same as normalized format</em>
{% endif %}
</td>
</tr>
<tr>
<th>Normalized Format</th>
<td><pre>{{ data.default_data.norm }}</pre></td>
</tr>
<tr>
<th>View Format</th>
<td>
{% if data.default_data.view is defined %}
<pre>{{ data.default_data.view }}</pre>
{% else %}
<em>same as normalized format</em>
{% endif %}
</td>
</tr>
</table>
{% endif %}
{% if data.submitted_data is defined %}
<h3>Submitted Data</h3>
{% if data.submitted_data.norm is defined %}
<table>
<tr>
<td><b>{{ fieldName }}</b></td>
<td>{{ field.type }}</td>
<td>{{ field.value }}</td>
<th width="180">View Format</th>
<td>
<ul>
{% for errorMessage in field.errors %}
<li>
{{ errorMessage.message }}
</li>
{% endfor %}
</ul>
{% if data.submitted_data.view is defined %}
<pre>{{ data.submitted_data.view }}</pre>
{% else %}
<em>same as normalized format</em>
{% endif %}
</td>
</tr>
<tr>
<th>Normalized Format</th>
<td><pre>{{ data.submitted_data.norm }}</pre></td>
</tr>
<tr>
<th>Model Format</th>
<td>
{% if data.submitted_data.model is defined %}
<pre>{{ data.submitted_data.model }}</pre>
{% else %}
<em>same as normalized format</em>
{% endif %}
</td>
</tr>
</table>
{% else %}
<p><em>This form was not submitted.</em></p>
{% endif %}
{% endif %}
{% if data.passed_options is defined %}
<h3>Passed Options</h3>
{% if data.passed_options|length %}
<table>
<tr>
<th width="180">Option</th>
<th>Passed Value</th>
<th>Resolved Value</th>
</tr>
{% for option, value in data.passed_options %}
<tr>
<th>{{ option }}</th>
<td><pre>{{ value }}</pre></td>
<td>
{% if data.resolved_options[option] is sameas(value) %}
<em>same as passed value</em>
{% else %}
<pre>{{ data.resolved_options[option] }}</pre>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<em>This form is valid.</em>
<p><em>No options where passed when constructing this form.</em></p>
{% endif %}
{% else %}
<em>No forms were submitted for this request.</em>
{% endif %}
{% if data.resolved_options is defined %}
<h3>Resolved Options</h3>
<table>
<tr>
<th width="180">Option</th>
<th>Value</th>
</tr>
{% for option, value in data.resolved_options %}
<tr>
<th>{{ option }}</th>
<td><pre>{{ value }}</pre></td>
</tr>
{% endfor %}
</table>
{% endif %}
<h3>View Variables</h3>
<table>
<tr>
<th width="180">Variable</th>
<th>Value</th>
</tr>
{% for variable, value in data.view_vars %}
<tr>
<th>{{ variable }}</th>
<td><pre>{{ value }}</pre></td>
</tr>
{% endfor %}
</table>
</div>
{% for childName, childData in data.children %}
{{ _self.form_tree_details(childName, childData) }}
{% endfor %}
{% endblock %}
{% endmacro %}

View File

@ -63,6 +63,9 @@ table th, table td {
font-size: 12px;
padding: 8px 10px;
}
table td em {
color: #aaa;
}
fieldset {
border: none;
}
@ -70,6 +73,9 @@ abbr {
border-bottom: 1px dotted #000;
cursor: help;
}
pre, code {
font-size: 0.9em;
}
.clear {
clear: both;
height: 0;

View File

@ -1,130 +0,0 @@
<?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\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
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.
*
* @author Robert Schönthal <robert.schoenthal@gmail.com>
*/
class FormCollector extends BaseCollector implements EventSubscriberInterface
{
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(FormEvents::POST_SUBMIT => array('collectForm', -255));
}
/**
* {@inheritDoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
//nothing to do, everything is added with addError()
}
/**
* Collects Form-Validation-Data and adds them to the Collector.
*
* @param FormEvent $event The event object
*/
public function collectForm(FormEvent $event)
{
$form = $event->getForm();
if ($form->isRoot()) {
$this->data[$form->getName()] = array();
$this->addForm($form);
}
}
/**
* Adds an Form-Element to the Collector.
*
* @param FormInterface $form
*/
private function addForm(FormInterface $form)
{
if ($form->getErrors()) {
$this->addError($form);
}
// recursively add all child-errors
foreach ($form->all() as $field) {
$this->addForm($field);
}
}
/**
* Adds a Form-Error to the Collector.
*
* @param FormInterface $form
*/
private 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 Forms with Errors.
*
* @return integer
*/
public function getErrorCount()
{
$errorCount = 0;
foreach ($this->data as $form) {
if (count($form)) {
$errorCount++;
}
}
return $errorCount;
}
}

View File

@ -15,20 +15,22 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\AbstractExtension;
/**
* DataCollectorExtension for collecting Form Validation Failures.
* Extension for collecting data of the forms on a page.
*
* @since 2.4
* @author Robert Schönthal <robert.schoenthal@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DataCollectorExtension extends AbstractExtension
{
/**
* @var EventSubscriberInterface
*/
private $eventSubscriber;
private $dataCollector;
public function __construct(EventSubscriberInterface $eventSubscriber)
public function __construct(FormDataCollectorInterface $dataCollector)
{
$this->eventSubscriber = $eventSubscriber;
$this->dataCollector = $dataCollector;
}
/**
@ -37,7 +39,7 @@ class DataCollectorExtension extends AbstractExtension
protected function loadTypeExtensions()
{
return array(
new Type\DataCollectorTypeExtension($this->eventSubscriber)
new Type\DataCollectorTypeExtension($this->dataCollector)
);
}
}

View File

@ -0,0 +1,85 @@
<?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\FormDataCollectorInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
/**
* Listener that invokes a data collector for the {@link FormEvents::POST_SET_DATA}
* and {@link FormEvents::POST_SUBMIT} events.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DataCollectorListener implements EventSubscriberInterface
{
/**
* @var FormDataCollectorInterface
*/
private $dataCollector;
public function __construct(FormDataCollectorInterface $dataCollector)
{
$this->dataCollector = $dataCollector;
}
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
// High priority in order to be called as soon as possible
FormEvents::POST_SET_DATA => array('postSetData', 255),
// Low priority in order to be called as late as possible
FormEvents::POST_SUBMIT => array('postSubmit', -255),
);
}
/**
* Listener for the {@link FormEvents::POST_SET_DATA} event.
*
* @param FormEvent $event The event object
*/
public function postSetData(FormEvent $event)
{
if ($event->getForm()->isRoot()) {
// Collect basic information about each form
$this->dataCollector->collectConfiguration($event->getForm());
// Collect the default data
$this->dataCollector->collectDefaultData($event->getForm());
}
}
/**
* Listener for the {@link FormEvents::POST_SUBMIT} event.
*
* @param FormEvent $event The event object
*/
public function postSubmit(FormEvent $event)
{
if ($event->getForm()->isRoot()) {
// Collect the submitted data of each form
$this->dataCollector->collectSubmittedData($event->getForm());
// Assemble a form tree
// This is done again in collectViewVariables(), but that method
// is not guaranteed to be called (i.e. when no view is created)
$this->dataCollector->buildPreliminaryFormTree($event->getForm());
}
}
}

View File

@ -0,0 +1,274 @@
<?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\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Data collector for {@link \Symfony\Component\Form\FormInterface} instances.
*
* @since 2.4
* @author Robert Schönthal <robert.schoenthal@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormDataCollector extends DataCollector implements FormDataCollectorInterface
{
/**
* @var FormDataExtractor
*/
private $dataExtractor;
/**
* Stores the collected data per {@link FormInterface} instance.
*
* Uses the hashes of the forms as keys. This is preferrable over using
* {@link \SplObjectStorage}, because in this way no references are kept
* to the {@link FormInterface} instances.
*
* @var array
*/
private $dataByForm;
/**
* Stores the collected data per {@link FormView} instance.
*
* Uses the hashes of the views as keys. This is preferrable over using
* {@link \SplObjectStorage}, because in this way no references are kept
* to the {@link FormView} instances.
*
* @var array
*/
private $dataByView;
/**
* Connects {@link FormView} with {@link FormInterface} instances.
*
* Uses the hashes of the views as keys and the hashes of the forms as
* values. This is preferrable over storing the objects directly, because
* this way they can safely be discarded by the GC.
*
* @var array
*/
private $formsByView;
public function __construct(FormDataExtractorInterface $dataExtractor)
{
$this->dataExtractor = $dataExtractor;
$this->data = array(
'forms' => array(),
'nb_errors' => 0,
);
}
/**
* Does nothing. The data is collected during the form event listeners.
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
}
/**
* {@inheritdoc}
*/
public function associateFormWithView(FormInterface $form, FormView $view)
{
$this->formsByView[spl_object_hash($view)] = spl_object_hash($form);
}
/**
* {@inheritdoc}
*/
public function collectConfiguration(FormInterface $form)
{
$hash = spl_object_hash($form);
if (!isset($this->dataByForm[$hash])) {
$this->dataByForm[$hash] = array();
}
$this->dataByForm[$hash] = array_replace(
$this->dataByForm[$hash],
$this->dataExtractor->extractConfiguration($form)
);
foreach ($form as $child) {
$this->collectConfiguration($child);
}
}
/**
* {@inheritdoc}
*/
public function collectDefaultData(FormInterface $form)
{
$hash = spl_object_hash($form);
if (!isset($this->dataByForm[$hash])) {
$this->dataByForm[$hash] = array();
}
$this->dataByForm[$hash] = array_replace(
$this->dataByForm[$hash],
$this->dataExtractor->extractDefaultData($form)
);
foreach ($form as $child) {
$this->collectDefaultData($child);
}
}
/**
* {@inheritdoc}
*/
public function collectSubmittedData(FormInterface $form)
{
$hash = spl_object_hash($form);
if (!isset($this->dataByForm[$hash])) {
$this->dataByForm[$hash] = array();
}
$this->dataByForm[$hash] = array_replace(
$this->dataByForm[$hash],
$this->dataExtractor->extractSubmittedData($form)
);
// Count errors
if (isset($this->dataByForm[$hash]['errors'])) {
$this->data['nb_errors'] += count($this->dataByForm[$hash]['errors']);
}
foreach ($form as $child) {
$this->collectSubmittedData($child);
}
}
/**
* {@inheritdoc}
*/
public function collectViewVariables(FormView $view)
{
$hash = spl_object_hash($view);
if (!isset($this->dataByView[$hash])) {
$this->dataByView[$hash] = array();
}
$this->dataByView[$hash] = array_replace(
$this->dataByView[$hash],
$this->dataExtractor->extractViewVariables($view)
);
foreach ($view->children as $child) {
$this->collectViewVariables($child);
}
}
/**
* {@inheritdoc}
*/
public function buildPreliminaryFormTree(FormInterface $form)
{
$this->data['forms'][$form->getName()] = array();
$this->recursiveBuildPreliminaryFormTree($form, $this->data['forms'][$form->getName()]);
}
/**
* {@inheritdoc}
*/
public function buildFinalFormTree(FormInterface $form, FormView $view)
{
$this->data['forms'][$form->getName()] = array();
$this->recursiveBuildFinalFormTree($form, $view, $this->data['forms'][$form->getName()]);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return 'form';
}
/**
* {@inheritdoc}
*/
public function getData()
{
return $this->data;
}
private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output = null)
{
$hash = spl_object_hash($form);
$output = isset($this->dataByForm[$hash])
? $this->dataByForm[$hash]
: array();
$output['children'] = array();
foreach ($form as $name => $child) {
$output['children'][$name] = array();
$this->recursiveBuildPreliminaryFormTree($child, $output['children'][$name]);
}
}
private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output = null)
{
$viewHash = spl_object_hash($view);
$formHash = null;
if (null !== $form) {
$formHash = spl_object_hash($form);
} elseif (isset($this->formsByView[$viewHash])) {
// The FormInterface instance of the CSRF token is never contained in
// the FormInterface tree of the form, so we need to get the
// corresponding FormInterface instance for its view in a different way
$formHash = $this->formsByView[$viewHash];
}
$output = isset($this->dataByView[$viewHash])
? $this->dataByView[$viewHash]
: array();
if (null !== $formHash) {
$output = array_replace(
$output,
isset($this->dataByForm[$formHash])
? $this->dataByForm[$formHash]
: array()
);
}
$output['children'] = array();
foreach ($view->children as $name => $childView) {
// The CSRF token, for example, is never added to the form tree.
// It is only present in the view.
$childForm = null !== $form && $form->has($name)
? $form->get($name)
: null;
$output['children'][$name] = array();
$this->recursiveBuildFinalFormTree($childForm, $childView, $output['children'][$name]);
}
}
}

View File

@ -0,0 +1,102 @@
<?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\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
/**
* Collects and structures information about forms.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface FormDataCollectorInterface extends DataCollectorInterface
{
/**
* Stores configuration data of the given form and its children.
*
* @param FormInterface $form A root form
*/
public function collectConfiguration(FormInterface $form);
/**
* Stores the default data of the given form and its children.
*
* @param FormInterface $form A root form
*/
public function collectDefaultData(FormInterface $form);
/**
* Stores the submitted data of the given form and its children.
*
* @param FormInterface $form A root form
*/
public function collectSubmittedData(FormInterface $form);
/**
* Stores the view variables of the given form view and its children.
*
* @param FormView $view A root form view
*/
public function collectViewVariables(FormView $view);
/**
* Specifies that the given objects represent the same conceptual form.
*
* @param FormInterface $form A form object
* @param FormView $view A view object
*/
public function associateFormWithView(FormInterface $form, FormView $view);
/**
* Assembles the data collected about the given form and its children as
* a tree-like data structure.
*
* The result can be queried using {@link getData()}.
*
* @param FormInterface $form A root form
*/
public function buildPreliminaryFormTree(FormInterface $form);
/**
* Assembles the data collected about the given form and its children as
* a tree-like data structure.
*
* The result can be queried using {@link getData()}.
*
* Contrary to {@link buildPreliminaryFormTree()}, a {@link FormView}
* object has to be passed. The tree structure of this view object will be
* used for structuring the resulting data. That means, if a child is
* present in the view, but not in the form, it will be present in the final
* data array anyway.
*
* When {@link FormView} instances are present in the view tree, for which
* no corresponding {@link FormInterface} objects can be found in the form
* tree, only the view data will be included in the result. If a
* corresponding {@link FormInterface} exists otherwise, call
* {@link associateFormWithView()} before calling this method.
*
* @param FormInterface $form A root form
* @param FormView $view A root view
*/
public function buildFinalFormTree(FormInterface $form, FormView $view);
/**
* Returns all collected data.
*
* @return array
*/
public function getData();
}

View File

@ -0,0 +1,135 @@
<?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\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
/**
* Default implementation of {@link FormDataExtractorInterface}.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormDataExtractor implements FormDataExtractorInterface
{
/**
* @var ValueExporter
*/
private $valueExporter;
/**
* Constructs a new data extractor.
*/
public function __construct(ValueExporter $valueExporter = null)
{
$this->valueExporter = $valueExporter ?: new ValueExporter();
}
/**
* {@inheritdoc}
*/
public function extractConfiguration(FormInterface $form)
{
$data = array(
'type' => $form->getConfig()->getType()->getName(),
'type_class' => get_class($form->getConfig()->getType()->getInnerType()),
'synchronized' => $this->valueExporter->exportValue($form->isSynchronized()),
'passed_options' => array(),
'resolved_options' => array(),
);
foreach ($form->getConfig()->getAttribute('data_collector/passed_options', array()) as $option => $value) {
$data['passed_options'][$option] = $this->valueExporter->exportValue($value);
}
foreach ($form->getConfig()->getOptions() as $option => $value) {
$data['resolved_options'][$option] = $this->valueExporter->exportValue($value);
}
ksort($data['passed_options']);
ksort($data['resolved_options']);
return $data;
}
/**
* {@inheritdoc}
*/
public function extractDefaultData(FormInterface $form)
{
$data = array(
'default_data' => array(
'norm' => $this->valueExporter->exportValue($form->getNormData()),
),
'submitted_data' => array(),
);
if ($form->getData() !== $form->getNormData()) {
$data['default_data']['model'] = $this->valueExporter->exportValue($form->getData());
}
if ($form->getViewData() !== $form->getNormData()) {
$data['default_data']['view'] = $this->valueExporter->exportValue($form->getViewData());
}
return $data;
}
/**
* {@inheritdoc}
*/
public function extractSubmittedData(FormInterface $form)
{
$data = array(
'submitted_data' => array(
'norm' => $this->valueExporter->exportValue($form->getNormData()),
),
'errors' => array(),
);
if ($form->getViewData() !== $form->getNormData()) {
$data['submitted_data']['view'] = $this->valueExporter->exportValue($form->getViewData());
}
if ($form->getData() !== $form->getNormData()) {
$data['submitted_data']['model'] = $this->valueExporter->exportValue($form->getData());
}
foreach ($form->getErrors() as $error) {
$data['errors'][] = array(
'message' => $error->getMessage(),
);
}
$data['synchronized'] = $this->valueExporter->exportValue($form->isSynchronized());
return $data;
}
/**
* {@inheritdoc}
*/
public function extractViewVariables(FormView $view)
{
$data = array();
foreach ($view->vars as $varName => $value) {
$data['view_vars'][$varName] = $this->valueExporter->exportValue($value);
}
ksort($data['view_vars']);
return $data;
}
}

View File

@ -0,0 +1,60 @@
<?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\Form\FormInterface;
use Symfony\Component\Form\FormView;
/**
* Extracts arrays of information out of forms.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface FormDataExtractorInterface
{
/**
* Extracts the configuration data of a form.
*
* @param FormInterface $form The form
*
* @return array Information about the form's configuration
*/
public function extractConfiguration(FormInterface $form);
/**
* Extracts the default data of a form.
*
* @param FormInterface $form The form
*
* @return array Information about the form's default data
*/
public function extractDefaultData(FormInterface $form);
/**
* Extracts the submitted data of a form.
*
* @param FormInterface $form The form
*
* @return array Information about the form's submitted data
*/
public function extractSubmittedData(FormInterface $form);
/**
* Extracts the view variables of a form.
*
* @param FormView $view The form view
*
* @return array Information about the view's variables
*/
public function extractViewVariables(FormView $view);
}

View File

@ -0,0 +1,148 @@
<?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\Proxy;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\ResolvedFormTypeInterface;
/**
* Proxy that invokes a data collector when creating a form and its view.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ResolvedTypeDataCollectorProxy implements ResolvedFormTypeInterface
{
/**
* @var ResolvedFormTypeInterface
*/
private $proxiedType;
/**
* @var FormDataCollectorInterface
*/
private $dataCollector;
public function __construct(ResolvedFormTypeInterface $proxiedType, FormDataCollectorInterface $dataCollector)
{
$this->proxiedType = $proxiedType;
$this->dataCollector = $dataCollector;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->proxiedType->getName();
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return $this->proxiedType->getParent();
}
/**
* {@inheritdoc}
*/
public function getInnerType()
{
return $this->proxiedType->getInnerType();
}
/**
* {@inheritdoc}
*/
public function getTypeExtensions()
{
return $this->proxiedType->getTypeExtensions();
}
/**
* {@inheritdoc}
*/
public function createBuilder(FormFactoryInterface $factory, $name, array $options = array())
{
$builder = $this->proxiedType->createBuilder($factory, $name, $options);
$builder->setAttribute('data_collector/passed_options', $options);
$builder->setType($this);
return $builder;
}
/**
* {@inheritdoc}
*/
public function createView(FormInterface $form, FormView $parent = null)
{
return $this->proxiedType->createView($form, $parent);
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->proxiedType->buildForm($builder, $options);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$this->proxiedType->buildView($view, $form, $options);
}
/**
* {@inheritdoc}
*/
public function finishView(FormView $view, FormInterface $form, array $options)
{
$this->proxiedType->finishView($view, $form, $options);
// Remember which view belongs to which form instance, so that we can
// get the collected data for a view when its form instance is not
// available (e.g. CSRF token)
$this->dataCollector->associateFormWithView($form, $view);
// Since the CSRF token is only present in the FormView tree, we also
// need to check the FormView tree instead of calling isRoot() on the
// FormInterface tree
if (null === $view->parent) {
$this->dataCollector->collectViewVariables($view);
// Re-assemble data, in case FormView instances were added, for
// which no FormInterface instances were present (e.g. CSRF token).
// Since finishView() is called after finishing the views of all
// children, we can safely assume that information has been
// collected about the complete form tree.
$this->dataCollector->buildFinalFormTree($form, $view);
}
}
/**
* {@inheritdoc}
*/
public function getOptionsResolver()
{
return $this->proxiedType->getOptionsResolver();
}
}

View File

@ -0,0 +1,55 @@
<?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\Proxy;
use Symfony\Component\Form\Exception;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\Form\ResolvedFormTypeFactoryInterface;
use Symfony\Component\Form\ResolvedFormTypeInterface;
/**
* Proxy that wraps resolved types into {@link ResolvedTypeDataCollectorProxy}
* instances.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ResolvedTypeFactoryDataCollectorProxy implements ResolvedFormTypeFactoryInterface
{
/**
* @var ResolvedFormTypeFactoryInterface
*/
private $proxiedFactory;
/**
* @var FormDataCollectorInterface
*/
private $dataCollector;
public function __construct(ResolvedFormTypeFactoryInterface $proxiedFactory, FormDataCollectorInterface $dataCollector)
{
$this->proxiedFactory = $proxiedFactory;
$this->dataCollector = $dataCollector;
}
/**
* {@inheritdoc}
*/
public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ResolvedFormTypeInterface $parent = null)
{
return new ResolvedTypeDataCollectorProxy(
$this->proxiedFactory->createResolvedType($type, $typeExtensions, $parent),
$this->dataCollector
);
}
}

View File

@ -11,25 +11,28 @@
namespace Symfony\Component\Form\Extension\DataCollector\Type;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface;
use Symfony\Component\Form\FormBuilderInterface;
/**
* DataCollector Type Extension for collecting invalid Forms.
* Type extension for collecting data of a form with this type.
*
* @since 2.4
* @author Robert Schönthal <robert.schoenthal@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DataCollectorTypeExtension extends AbstractTypeExtension
{
/**
* @var EventSubscriberInterface
* @var \Symfony\Component\EventDispatcher\EventSubscriberInterface
*/
private $eventSubscriber;
private $listener;
public function __construct(EventSubscriberInterface $eventSubscriber)
public function __construct(FormDataCollectorInterface $dataCollector)
{
$this->eventSubscriber = $eventSubscriber;
$this->listener = new DataCollectorListener($dataCollector);
}
/**
@ -37,7 +40,7 @@ class DataCollectorTypeExtension extends AbstractTypeExtension
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber($this->eventSubscriber);
$builder->addEventSubscriber($this->listener);
}
/**

View File

@ -1,58 +0,0 @@
<?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\Tests\Extension\DataCollector\Collector;
use Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class FormCollectorTest extends \PHPUnit_Framework_TestCase
{
public function testSubscribedEvents()
{
$events = FormCollector::getSubscribedEvents();
$this->assertInternalType('array', $events);
$this->assertEquals(array(FormEvents::POST_SUBMIT => array('collectForm', -255)), $events);
}
public function testCollect()
{
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
$subForm = $this->getMock('Symfony\Component\Form\Test\FormInterface');
$type = $this->getMock('Symfony\Component\Form\FormTypeInterface');
$type->expects($this->atLeastOnce())->method('getName')->will($this->returnValue('fizz'));
$config = $this->getMock('Symfony\Component\Form\FormConfigInterface');
$config->expects($this->atLeastOnce())->method('getType')->will($this->returnValue($type));
$form->expects($this->atLeastOnce())->method('all')->will($this->returnValue(array($subForm)));
$form->expects($this->atLeastOnce())->method('isRoot')->will($this->returnValue(true));
$form->expects($this->atLeastOnce())->method('getName')->will($this->returnValue('foo'));
$subForm->expects($this->atLeastOnce())->method('all')->will($this->returnValue(array()));
$subForm->expects($this->atLeastOnce())->method('getErrors')->will($this->returnValue(array('foo')));
$subForm->expects($this->atLeastOnce())->method('getRoot')->will($this->returnValue($form));
$subForm->expects($this->atLeastOnce())->method('getConfig')->will($this->returnValue($config));
$subForm->expects($this->atLeastOnce())->method('getPropertyPath')->will($this->returnValue('bar'));
$subForm->expects($this->atLeastOnce())->method('getViewData')->will($this->returnValue('bazz'));
$event = new FormEvent($form, array());
$c = new FormCollector();
$c->collectForm($event);
$this->assertInternalType('array', $c->getData());
$this->assertEquals(1, $c->getErrorCount());
$this->assertEquals(array('foo' => array('bar' => array('value' => 'bazz', 'root' => 'foo', 'type' => 'fizz', 'name' => 'bar', 'errors' => array('foo')))), $c->getData());
}
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Form\Tests\Extension\DataCollector;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\DataCollector\DataCollectorExtension;
/**
@ -25,14 +24,14 @@ class DataCollectorExtensionTest extends \PHPUnit_Framework_TestCase
private $extension;
/**
* @var EventSubscriberInterface
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $eventSubscriber;
private $dataCollector;
public function setUp()
{
$this->eventSubscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface');
$this->extension = new DataCollectorExtension($this->eventSubscriber);
$this->dataCollector = $this->getMock('Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface');
$this->extension = new DataCollectorExtension($this->dataCollector);
}
public function testLoadTypeExtensions()
@ -44,4 +43,3 @@ class DataCollectorExtensionTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension', array_shift($typeExtensions));
}
}

View File

@ -0,0 +1,465 @@
<?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\Tests\Extension\DataCollector;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollector;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormView;
class FormDataCollectorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dataExtractor;
/**
* @var FormDataCollector
*/
private $dataCollector;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dispatcher;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $factory;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dataMapper;
/**
* @var Form
*/
private $form;
/**
* @var Form
*/
private $childForm;
/**
* @var FormView
*/
private $view;
/**
* @var FormView
*/
private $childView;
protected function setUp()
{
$this->dataExtractor = $this->getMock('Symfony\Component\Form\Extension\DataCollector\FormDataExtractorInterface');
$this->dataCollector = new FormDataCollector($this->dataExtractor);
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
$this->dataMapper = $this->getMock('Symfony\Component\Form\DataMapperInterface');
$this->form = $this->createForm('name');
$this->childForm = $this->createForm('child');
$this->view = new FormView();
$this->childView = new FormView();
}
public function testBuildPreliminaryFormTree()
{
$this->form->add($this->childForm);
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($this->childForm)
->will($this->returnValue(array('config' => 'bar')));
$this->dataExtractor->expects($this->at(2))
->method('extractDefaultData')
->with($this->form)
->will($this->returnValue(array('default_data' => 'foo')));
$this->dataExtractor->expects($this->at(3))
->method('extractDefaultData')
->with($this->childForm)
->will($this->returnValue(array('default_data' => 'bar')));
$this->dataExtractor->expects($this->at(4))
->method('extractSubmittedData')
->with($this->form)
->will($this->returnValue(array('submitted_data' => 'foo')));
$this->dataExtractor->expects($this->at(5))
->method('extractSubmittedData')
->with($this->childForm)
->will($this->returnValue(array('submitted_data' => 'bar')));
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->collectDefaultData($this->form);
$this->dataCollector->collectSubmittedData($this->form);
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'default_data' => 'foo',
'submitted_data' => 'foo',
'children' => array(
'child' => array(
'config' => 'bar',
'default_data' => 'bar',
'submitted_data' => 'bar',
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testBuildMultiplePreliminaryFormTrees()
{
$form1 = $this->createForm('form1');
$form2 = $this->createForm('form2');
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($form1)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($form2)
->will($this->returnValue(array('config' => 'bar')));
$this->dataCollector->collectConfiguration($form1);
$this->dataCollector->collectConfiguration($form2);
$this->dataCollector->buildPreliminaryFormTree($form1);
$this->assertSame(array(
'forms' => array(
'form1' => array(
'config' => 'foo',
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
$this->dataCollector->buildPreliminaryFormTree($form2);
$this->assertSame(array(
'forms' => array(
'form1' => array(
'config' => 'foo',
'children' => array(),
),
'form2' => array(
'config' => 'bar',
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testBuildSamePreliminaryFormTreeMultipleTimes()
{
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractDefaultData')
->with($this->form)
->will($this->returnValue(array('default_data' => 'foo')));
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
$this->dataCollector->collectDefaultData($this->form);
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'default_data' => 'foo',
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testBuildPreliminaryFormTreeWithoutCollectingAnyData()
{
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testBuildFinalFormTree()
{
$this->form->add($this->childForm);
$this->view->children['child'] = $this->childView;
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($this->childForm)
->will($this->returnValue(array('config' => 'bar')));
$this->dataExtractor->expects($this->at(2))
->method('extractDefaultData')
->with($this->form)
->will($this->returnValue(array('default_data' => 'foo')));
$this->dataExtractor->expects($this->at(3))
->method('extractDefaultData')
->with($this->childForm)
->will($this->returnValue(array('default_data' => 'bar')));
$this->dataExtractor->expects($this->at(4))
->method('extractSubmittedData')
->with($this->form)
->will($this->returnValue(array('submitted_data' => 'foo')));
$this->dataExtractor->expects($this->at(5))
->method('extractSubmittedData')
->with($this->childForm)
->will($this->returnValue(array('submitted_data' => 'bar')));
$this->dataExtractor->expects($this->at(6))
->method('extractViewVariables')
->with($this->view)
->will($this->returnValue(array('view_vars' => 'foo')));
$this->dataExtractor->expects($this->at(7))
->method('extractViewVariables')
->with($this->childView)
->will($this->returnValue(array('view_vars' => 'bar')));
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->collectDefaultData($this->form);
$this->dataCollector->collectSubmittedData($this->form);
$this->dataCollector->collectViewVariables($this->view);
$this->dataCollector->buildFinalFormTree($this->form, $this->view);
$this->assertSame(array(
'forms' => array(
'name' => array(
'view_vars' => 'foo',
'config' => 'foo',
'default_data' => 'foo',
'submitted_data' => 'foo',
'children' => array(
'child' => array(
'view_vars' => 'bar',
'config' => 'bar',
'default_data' => 'bar',
'submitted_data' => 'bar',
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testFinalFormReliesOnFormViewStructure()
{
$this->form->add($this->createForm('first'));
$this->form->add($this->createForm('second'));
$this->view->children['second'] = $this->childView;
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'children' => array(
'first' => array(
'children' => array(),
),
'second' => array(
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
$this->dataCollector->buildFinalFormTree($this->form, $this->view);
$this->assertSame(array(
'forms' => array(
'name' => array(
'children' => array(
// "first" not present in FormView
'second' => array(
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testChildViewsCanBeWithoutCorrespondingChildForms()
{
// don't add $this->childForm to $this->form!
$this->view->children['child'] = $this->childView;
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($this->childForm)
->will($this->returnValue(array('config' => 'bar')));
// explicitly call collectConfiguration(), since $this->childForm is not
// contained in the form tree
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->collectConfiguration($this->childForm);
$this->dataCollector->buildFinalFormTree($this->form, $this->view);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'children' => array(
'child' => array(
// no "config" key
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testChildViewsWithoutCorrespondingChildFormsMayBeExplicitlyAssociated()
{
// don't add $this->childForm to $this->form!
$this->view->children['child'] = $this->childView;
// but associate the two
$this->dataCollector->associateFormWithView($this->childForm, $this->childView);
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($this->childForm)
->will($this->returnValue(array('config' => 'bar')));
// explicitly call collectConfiguration(), since $this->childForm is not
// contained in the form tree
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->collectConfiguration($this->childForm);
$this->dataCollector->buildFinalFormTree($this->form, $this->view);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'children' => array(
'child' => array(
'config' => 'bar',
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testCollectSubmittedDataCountsErrors()
{
$form1 = $this->createForm('form1');
$childForm1 = $this->createForm('child1');
$form2 = $this->createForm('form2');
$form1->add($childForm1);
$this->dataExtractor->expects($this->at(0))
->method('extractSubmittedData')
->with($form1)
->will($this->returnValue(array('errors' => array('foo'))));
$this->dataExtractor->expects($this->at(1))
->method('extractSubmittedData')
->with($childForm1)
->will($this->returnValue(array('errors' => array('bar', 'bam'))));
$this->dataExtractor->expects($this->at(2))
->method('extractSubmittedData')
->with($form2)
->will($this->returnValue(array('errors' => array('baz'))));
$this->dataCollector->collectSubmittedData($form1);
$data = $this->dataCollector->getData();
$this->assertSame(3, $data['nb_errors']);
$this->dataCollector->collectSubmittedData($form2);
$data = $this->dataCollector->getData();
$this->assertSame(4, $data['nb_errors']);
}
private function createForm($name)
{
$builder = new FormBuilder($name, null, $this->dispatcher, $this->factory);
$builder->setCompound(true);
$builder->setDataMapper($this->dataMapper);
return $builder->getForm();
}
}

View File

@ -0,0 +1,338 @@
<?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\Tests\Extension\DataCollector;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\DataCollector\FormDataExtractor;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
class FormDataExtractorTest_SimpleValueExporter extends ValueExporter
{
/**
* {@inheritdoc}
*/
public function exportValue($value)
{
return var_export($value, true);
}
}
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var FormDataExtractorTest_SimpleValueExporter
*/
private $valueExporter;
/**
* @var FormDataExtractor
*/
private $dataExtractor;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dispatcher;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $factory;
protected function setUp()
{
$this->valueExporter = new FormDataExtractorTest_SimpleValueExporter();
$this->dataExtractor = new FormDataExtractor($this->valueExporter);
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
}
public function testExtractConfiguration()
{
$type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
$type->expects($this->any())
->method('getName')
->will($this->returnValue('type_name'));
$type->expects($this->any())
->method('getInnerType')
->will($this->returnValue(new \stdClass()));
$form = $this->createBuilder('name')
->setType($type)
->getForm();
$this->assertSame(array(
'type' => 'type_name',
'type_class' => 'stdClass',
'synchronized' => 'true',
'passed_options' => array(),
'resolved_options' => array(),
), $this->dataExtractor->extractConfiguration($form));
}
public function testExtractConfigurationSortsPassedOptions()
{
$type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
$type->expects($this->any())
->method('getName')
->will($this->returnValue('type_name'));
$type->expects($this->any())
->method('getInnerType')
->will($this->returnValue(new \stdClass()));
$options = array(
'b' => 'foo',
'a' => 'bar',
'c' => 'baz',
);
$form = $this->createBuilder('name')
->setType($type)
// passed options are stored in an attribute by
// ResolvedTypeDataCollectorProxy
->setAttribute('data_collector/passed_options', $options)
->getForm();
$this->assertSame(array(
'type' => 'type_name',
'type_class' => 'stdClass',
'synchronized' => 'true',
'passed_options' => array(
'a' => "'bar'",
'b' => "'foo'",
'c' => "'baz'",
),
'resolved_options' => array(),
), $this->dataExtractor->extractConfiguration($form));
}
public function testExtractConfigurationSortsResolvedOptions()
{
$type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
$type->expects($this->any())
->method('getName')
->will($this->returnValue('type_name'));
$type->expects($this->any())
->method('getInnerType')
->will($this->returnValue(new \stdClass()));
$options = array(
'b' => 'foo',
'a' => 'bar',
'c' => 'baz',
);
$form = $this->createBuilder('name', $options)
->setType($type)
->getForm();
$this->assertSame(array(
'type' => 'type_name',
'type_class' => 'stdClass',
'synchronized' => 'true',
'passed_options' => array(),
'resolved_options' => array(
'a' => "'bar'",
'b' => "'foo'",
'c' => "'baz'",
),
), $this->dataExtractor->extractConfiguration($form));
}
public function testExtractDefaultData()
{
$form = $this->createBuilder('name')->getForm();
$form->setData('Foobar');
$this->assertSame(array(
'default_data' => array(
'norm' => "'Foobar'",
),
'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form));
}
public function testExtractDefaultDataStoresModelDataIfDifferent()
{
$form = $this->createBuilder('name')
->addModelTransformer(new FixedDataTransformer(array(
'Foo' => 'Bar'
)))
->getForm();
$form->setData('Foo');
$this->assertSame(array(
'default_data' => array(
'norm' => "'Bar'",
'model' => "'Foo'",
),
'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form));
}
public function testExtractDefaultDataStoresViewDataIfDifferent()
{
$form = $this->createBuilder('name')
->addViewTransformer(new FixedDataTransformer(array(
'Foo' => 'Bar'
)))
->getForm();
$form->setData('Foo');
$this->assertSame(array(
'default_data' => array(
'norm' => "'Foo'",
'view' => "'Bar'",
),
'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form));
}
public function testExtractSubmittedData()
{
$form = $this->createBuilder('name')->getForm();
$form->submit('Foobar');
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Foobar'",
),
'errors' => array(),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractSubmittedDataStoresModelDataIfDifferent()
{
$form = $this->createBuilder('name')
->addModelTransformer(new FixedDataTransformer(array(
'Foo' => 'Bar',
'' => '',
)))
->getForm();
$form->submit('Bar');
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Bar'",
'model' => "'Foo'",
),
'errors' => array(),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractSubmittedDataStoresViewDataIfDifferent()
{
$form = $this->createBuilder('name')
->addViewTransformer(new FixedDataTransformer(array(
'Foo' => 'Bar',
'' => '',
)))
->getForm();
$form->submit('Bar');
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Foo'",
'view' => "'Bar'",
),
'errors' => array(),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractSubmittedDataStoresErrors()
{
$form = $this->createBuilder('name')->getForm();
$form->submit('Foobar');
$form->addError(new FormError('Invalid!'));
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Foobar'",
),
'errors' => array(
array('message' => 'Invalid!'),
),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractSubmittedDataRemembersIfNonSynchronized()
{
$form = $this->createBuilder('name')
->addModelTransformer(new CallbackTransformer(
function () {},
function () {
throw new TransformationFailedException('Fail!');
}
))
->getForm();
$form->submit('Foobar');
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Foobar'",
'model' => 'NULL',
),
'errors' => array(),
'synchronized' => 'false',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractViewVariables()
{
$view = new FormView();
$view->vars = array(
'b' => 'foo',
'a' => 'bar',
'c' => 'baz',
);
$this->assertSame(array(
'view_vars' => array(
'a' => "'bar'",
'b' => "'foo'",
'c' => "'baz'",
),
), $this->dataExtractor->extractViewVariables($view));
}
/**
* @param string $name
* @param array $options
*
* @return FormBuilder
*/
private function createBuilder($name, array $options = array())
{
return new FormBuilder($name, null, $this->dispatcher, $this->factory, $options);
}
}

View File

@ -10,7 +10,6 @@
namespace Symfony\Component\Form\Tests\Extension\DataCollector\Type;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension;
class DataCollectorTypeExtensionTest extends \PHPUnit_Framework_TestCase
@ -21,14 +20,14 @@ class DataCollectorTypeExtensionTest extends \PHPUnit_Framework_TestCase
private $extension;
/**
* @var EventSubscriberInterface
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $eventSubscriber;
private $dataCollector;
public function setUp()
{
$this->eventSubscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface');
$this->extension = new DataCollectorTypeExtension($this->eventSubscriber);
$this->dataCollector = $this->getMock('Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface');
$this->extension = new DataCollectorTypeExtension($this->dataCollector);
}
public function testGetExtendedType()
@ -39,7 +38,9 @@ class DataCollectorTypeExtensionTest extends \PHPUnit_Framework_TestCase
public function testBuildForm()
{
$builder = $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
$builder->expects($this->atLeastOnce())->method('addEventSubscriber')->with($this->eventSubscriber);
$builder->expects($this->atLeastOnce())
->method('addEventSubscriber')
->with($this->isInstanceOf('Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener'));
$this->extension->buildForm($builder, array());
}