diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 32487952c8..f00c240d2a 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -218,6 +218,7 @@ class FrameworkExtension extends Extension
return;
}
+ $loader->load('form_debug.xml');
$loader->load('profiling.xml');
$loader->load('collectors.xml');
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml
index 0e07cdb5d9..7eb472fd97 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml
@@ -13,6 +13,8 @@
Symfony\Component\HttpKernel\DataCollector\TimeDataCollector
Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector
Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector
+ Symfony\Component\Form\Extension\DataCollector\FormDataCollector
+ Symfony\Component\Form\Extension\DataCollector\FormDataExtractor
@@ -53,5 +55,13 @@
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml
new file mode 100644
index 0000000000..5d4faac4ac
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy
+ Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
new file mode 100644
index 0000000000..ecc0a42633
--- /dev/null
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
@@ -0,0 +1,345 @@
+{% extends '@WebProfiler/Profiler/layout.html.twig' %}
+
+{% from _self import form_tree_entry, form_tree_details %}
+
+{% block toolbar %}
+ {% if collector.data|length %}
+ {% set icon %}
+
+ {% if collector.data.nb_errors %}{{ collector.data.nb_errors }}{% else %}{{ collector.data.forms|length }}{% endif %}
+ {% endset %}
+
+ {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %}
+ {% endif %}
+{% endblock %}
+
+{% block menu %}
+
+
+ Forms
+ {% if collector.data.forms|length %}
+ {{ collector.data.forms|length }}
+ {% endif %}
+
+{% endblock %}
+
+{% block panel %}
+
+
+ {% if collector.data.forms|length %}
+
+
+
Forms
+
+
+ {% for formName, formData in collector.data.forms %}
+ {{ form_tree_entry(formName, formData) }}
+ {% endfor %}
+
+
+
+ {% for formName, formData in collector.data.forms %}
+ {{ form_tree_details(formName, formData) }}
+ {% endfor %}
+
+ {% else %}
+ No forms were submitted for this request.
+ {% endif %}
+
+
+{% endblock %}
+
+{% macro form_tree_entry(name, data) %}
+
+ {{ name }}
+
+ {% if data.children|length > 1 %}
+
+ {% for childName, childData in data.children %}
+ {{ _self.form_tree_entry(childName, childData) }}
+ {% endfor %}
+
+ {% endif %}
+
+{% endmacro %}
+
+{% macro form_tree_details(name, data) %}
+
+
+ {{ name }}
+ {% if data.type_class is defined %}
+ [{{ data.type }}]
+ {% endif %}
+
+
+ {% if data.errors is defined and data.errors|length > 0 %}
+
Errors
+
+
+
+ Message |
+ Cause |
+
+ {% for error in data.errors %}
+
+ {{ error.message }} |
+ Unknown. |
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% if data.default_data is defined %}
+
Default Data
+
+
+
+ Model Format |
+
+ {% if data.default_data.model is defined %}
+ {{ data.default_data.model }}
+ {% else %}
+ same as normalized format
+ {% endif %}
+ |
+
+
+ Normalized Format |
+ {{ data.default_data.norm }} |
+
+
+ View Format |
+
+ {% if data.default_data.view is defined %}
+ {{ data.default_data.view }}
+ {% else %}
+ same as normalized format
+ {% endif %}
+ |
+
+
+ {% endif %}
+
+ {% if data.submitted_data is defined %}
+
Submitted Data
+
+ {% if data.submitted_data.norm is defined %}
+
+
+ View Format |
+
+ {% if data.submitted_data.view is defined %}
+ {{ data.submitted_data.view }}
+ {% else %}
+ same as normalized format
+ {% endif %}
+ |
+
+
+ Normalized Format |
+ {{ data.submitted_data.norm }} |
+
+
+ Model Format |
+
+ {% if data.submitted_data.model is defined %}
+ {{ data.submitted_data.model }}
+ {% else %}
+ same as normalized format
+ {% endif %}
+ |
+
+
+ {% else %}
+
This form was not submitted.
+ {% endif %}
+ {% endif %}
+
+ {% if data.passed_options is defined %}
+
Passed Options
+
+ {% if data.passed_options|length %}
+
+
+ Option |
+ Passed Value |
+ Resolved Value |
+
+ {% for option, value in data.passed_options %}
+
+ {{ option }} |
+ {{ value }} |
+
+ {% if data.resolved_options[option] is sameas(value) %}
+ same as passed value
+ {% else %}
+ {{ data.resolved_options[option] }}
+ {% endif %}
+ |
+
+ {% endfor %}
+
+ {% else %}
+
No options where passed when constructing this form.
+ {% endif %}
+ {% endif %}
+
+ {% if data.resolved_options is defined %}
+
Resolved Options
+
+
+
+ Option |
+ Value |
+
+ {% for option, value in data.resolved_options %}
+
+ {{ option }} |
+ {{ value }} |
+
+ {% endfor %}
+
+ {% endif %}
+
+
View Variables
+
+
+
+ Variable |
+ Value |
+
+ {% for variable, value in data.view_vars %}
+
+ {{ variable }} |
+ {{ value }} |
+
+ {% endfor %}
+
+
+
+ {% for childName, childData in data.children %}
+ {{ _self.form_tree_details(childName, childData) }}
+ {% endfor %}
+{% endmacro %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
index 7f10150c72..3b45ca1116 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
@@ -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;
diff --git a/src/Symfony/Component/Form/Button.php b/src/Symfony/Component/Form/Button.php
index aecbabaebe..cecf27b696 100644
--- a/src/Symfony/Component/Form/Button.php
+++ b/src/Symfony/Component/Form/Button.php
@@ -412,7 +412,16 @@ class Button implements \IteratorAggregate, FormInterface
$parent = $this->parent->createView();
}
- return $this->config->getType()->createView($this, $parent);
+
+ $type = $this->config->getType();
+ $options = $this->config->getOptions();
+
+ $view = $type->createView($this, $parent);
+
+ $type->buildView($view, $this, $options);
+ $type->finishView($view, $this, $options);
+
+ return $view;
}
/**
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php b/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php
new file mode 100644
index 0000000000..941bd2102e
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php
@@ -0,0 +1,45 @@
+
+ *
+ * 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;
+
+/**
+ * Extension for collecting data of the forms on a page.
+ *
+ * @since 2.4
+ * @author Robert Schönthal
+ * @author Bernhard Schussek
+ */
+class DataCollectorExtension extends AbstractExtension
+{
+ /**
+ * @var EventSubscriberInterface
+ */
+ private $dataCollector;
+
+ public function __construct(FormDataCollectorInterface $dataCollector)
+ {
+ $this->dataCollector = $dataCollector;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadTypeExtensions()
+ {
+ return array(
+ new Type\DataCollectorTypeExtension($this->dataCollector)
+ );
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php
new file mode 100644
index 0000000000..4822989b58
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php
@@ -0,0 +1,85 @@
+
+ *
+ * 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
+ */
+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());
+ }
+ }
+
+}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php
new file mode 100644
index 0000000000..543d6c5b04
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php
@@ -0,0 +1,274 @@
+
+ *
+ * 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
+ * @author Bernhard Schussek
+ */
+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]);
+ }
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php
new file mode 100644
index 0000000000..9a805029f8
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php
@@ -0,0 +1,102 @@
+
+ *
+ * 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
+ */
+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();
+
+}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php
new file mode 100644
index 0000000000..ad401796bd
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php
@@ -0,0 +1,135 @@
+
+ *
+ * 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
+ */
+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;
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractorInterface.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractorInterface.php
new file mode 100644
index 0000000000..d47496552d
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractorInterface.php
@@ -0,0 +1,60 @@
+
+ *
+ * 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
+ */
+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);
+}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php
new file mode 100644
index 0000000000..960048a0c2
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php
@@ -0,0 +1,148 @@
+
+ *
+ * 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
+ */
+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();
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php
new file mode 100644
index 0000000000..f15b720585
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php
@@ -0,0 +1,55 @@
+
+ *
+ * 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
+ */
+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
+ );
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php
new file mode 100644
index 0000000000..1b5cfb695d
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php
@@ -0,0 +1,53 @@
+
+ *
+ * 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\Form\AbstractTypeExtension;
+use Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener;
+use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface;
+use Symfony\Component\Form\FormBuilderInterface;
+
+/**
+ * Type extension for collecting data of a form with this type.
+ *
+ * @since 2.4
+ * @author Robert Schönthal
+ * @author Bernhard Schussek
+ */
+class DataCollectorTypeExtension extends AbstractTypeExtension
+{
+ /**
+ * @var \Symfony\Component\EventDispatcher\EventSubscriberInterface
+ */
+ private $listener;
+
+ public function __construct(FormDataCollectorInterface $dataCollector)
+ {
+ $this->listener = new DataCollectorListener($dataCollector);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder->addEventSubscriber($this->listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getExtendedType()
+ {
+ return 'form';
+ }
+}
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index f3324986c8..e03e329ff8 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -969,7 +969,23 @@ class Form implements \IteratorAggregate, FormInterface
$parent = $this->parent->createView();
}
- return $this->config->getType()->createView($this, $parent);
+ $type = $this->config->getType();
+ $options = $this->config->getOptions();
+
+ // The methods createView(), buildView() and finishView() are called
+ // explicitly here in order to be able to override either of them
+ // in a custom resolved form type.
+ $view = $type->createView($this, $parent);
+
+ $type->buildView($view, $this, $options);
+
+ foreach ($this->children as $name => $child) {
+ $view->children[$name] = $child->createView($view);
+ }
+
+ $type->finishView($view, $this, $options);
+
+ return $view;
}
/**
diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php
index a5fd9cc0c6..d76e73101b 100644
--- a/src/Symfony/Component/Form/FormFactory.php
+++ b/src/Symfony/Component/Form/FormFactory.php
@@ -84,7 +84,13 @@ class FormFactory implements FormFactoryInterface
throw new UnexpectedTypeException($type, 'string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface');
}
- return $type->createBuilder($this, $name, $options);
+ $builder = $type->createBuilder($this, $name, $options);
+
+ // Explicitly call buildForm() in order to be able to override either
+ // createBuilder() or buildForm() in the resolved form type
+ $type->buildForm($builder, $builder->getOptions());
+
+ return $builder;
}
/**
diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php
index 47d43553cd..c2fed95859 100644
--- a/src/Symfony/Component/Form/ResolvedFormType.php
+++ b/src/Symfony/Component/Form/ResolvedFormType.php
@@ -114,8 +114,6 @@ class ResolvedFormType implements ResolvedFormTypeInterface
$builder = $this->newBuilder($name, $dataClass, $factory, $options);
$builder->setType($this);
- $this->buildForm($builder, $options);
-
return $builder;
}
@@ -124,28 +122,12 @@ class ResolvedFormType implements ResolvedFormTypeInterface
*/
public function createView(FormInterface $form, FormView $parent = null)
{
- $options = $form->getConfig()->getOptions();
-
- $view = $this->newView($parent);
-
- $this->buildView($view, $form, $options);
-
- foreach ($form as $name => $child) {
- /* @var FormInterface $child */
- $view->children[$name] = $child->createView($view);
- }
-
- $this->finishView($view, $form, $options);
-
- return $view;
+ return $this->newView($parent);
}
/**
* Configures a form builder for the type hierarchy.
*
- * This method is protected in order to allow implementing classes
- * to change or call it in re-implementations of {@link createBuilder()}.
- *
* @param FormBuilderInterface $builder The builder to configure.
* @param array $options The options used for the configuration.
*/
@@ -166,10 +148,7 @@ class ResolvedFormType implements ResolvedFormTypeInterface
/**
* Configures a form view for the type hierarchy.
*
- * This method is protected in order to allow implementing classes
- * to change or call it in re-implementations of {@link createView()}.
- *
- * It is called before the children of the view are built.
+ * This method is called before the children of the view are built.
*
* @param FormView $view The form view to configure.
* @param FormInterface $form The form corresponding to the view.
@@ -192,10 +171,7 @@ class ResolvedFormType implements ResolvedFormTypeInterface
/**
* Finishes a form view for the type hierarchy.
*
- * This method is protected in order to allow implementing classes
- * to change or call it in re-implementations of {@link createView()}.
- *
- * It is called after the children of the view have been built.
+ * This method is called after the children of the view have been built.
*
* @param FormView $view The form view to configure.
* @param FormInterface $form The form corresponding to the view.
@@ -218,9 +194,6 @@ class ResolvedFormType implements ResolvedFormTypeInterface
/**
* Returns the configured options resolver used for this type.
*
- * This method is protected in order to allow implementing classes
- * to change or call it in re-implementations of {@link createBuilder()}.
- *
* @return \Symfony\Component\OptionsResolver\OptionsResolverInterface The options resolver.
*/
public function getOptionsResolver()
diff --git a/src/Symfony/Component/Form/Tests/AbstractFormTest.php b/src/Symfony/Component/Form/Tests/AbstractFormTest.php
index 28f0e389e5..083176e670 100644
--- a/src/Symfony/Component/Form/Tests/AbstractFormTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractFormTest.php
@@ -60,9 +60,9 @@ abstract class AbstractFormTest extends \PHPUnit_Framework_TestCase
*
* @return FormBuilder
*/
- protected function getBuilder($name = 'name', EventDispatcherInterface $dispatcher = null, $dataClass = null)
+ protected function getBuilder($name = 'name', EventDispatcherInterface $dispatcher = null, $dataClass = null, array $options = array())
{
- return new FormBuilder($name, $dataClass, $dispatcher ?: $this->dispatcher, $this->factory);
+ return new FormBuilder($name, $dataClass, $dispatcher ?: $this->dispatcher, $this->factory, $options);
}
/**
diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php
index 7d9126a988..225cc93391 100644
--- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php
+++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php
@@ -15,6 +15,7 @@ use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Forms;
+use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
@@ -806,6 +807,65 @@ class CompoundFormTest extends AbstractFormTest
$this->assertEquals("name:\n ERROR: Error!\nfoo:\n No errors\n", $parent->getErrorsAsString());
}
+ // Basic cases are covered in SimpleFormTest
+ public function testCreateViewWithChildren()
+ {
+ $type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
+ $options = array('a' => 'Foo', 'b' => 'Bar');
+ $field1 = $this->getMockForm('foo');
+ $field2 = $this->getMockForm('bar');
+ $view = new FormView();
+ $field1View = new FormView();
+ $field2View = new FormView();
+
+ $this->form = $this->getBuilder('form', null, null, $options)
+ ->setCompound(true)
+ ->setDataMapper($this->getDataMapper())
+ ->setType($type)
+ ->getForm();
+ $this->form->add($field1);
+ $this->form->add($field2);
+
+ $test = $this;
+
+ $assertChildViewsEqual = function (array $childViews) use ($test) {
+ return function (FormView $view) use ($test, $childViews) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertSame($childViews, $view->children);
+ };
+ };
+
+ // First create the view
+ $type->expects($this->once())
+ ->method('createView')
+ ->will($this->returnValue($view));
+
+ // Then build it for the form itself
+ $type->expects($this->once())
+ ->method('buildView')
+ ->with($view, $this->form, $options)
+ ->will($this->returnCallback($assertChildViewsEqual(array())));
+
+ // Then add the first child form
+ $field1->expects($this->once())
+ ->method('createView')
+ ->will($this->returnValue($field1View));
+
+ // Then the second child form
+ $field2->expects($this->once())
+ ->method('createView')
+ ->will($this->returnValue($field2View));
+
+ // Again build the view for the form itself. This time the child views
+ // exist.
+ $type->expects($this->once())
+ ->method('finishView')
+ ->with($view, $this->form, $options)
+ ->will($this->returnCallback($assertChildViewsEqual(array('foo' => $field1View, 'bar' => $field2View))));
+
+ $this->assertSame($view, $this->form->createView());
+ }
+
protected function createForm()
{
return $this->getBuilder()
diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/DataCollectorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/DataCollectorExtensionTest.php
new file mode 100644
index 0000000000..9f5991c105
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/DataCollectorExtensionTest.php
@@ -0,0 +1,45 @@
+
+ *
+ * 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\DataCollectorExtension;
+
+/**
+ * @covers Symfony\Component\Form\Extension\DataCollector\DataCollectorExtension
+ */
+class DataCollectorExtensionTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var DataCollectorExtension
+ */
+ private $extension;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $dataCollector;
+
+ public function setUp()
+ {
+ $this->dataCollector = $this->getMock('Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface');
+ $this->extension = new DataCollectorExtension($this->dataCollector);
+ }
+
+ public function testLoadTypeExtensions()
+ {
+ $typeExtensions = $this->extension->getTypeExtensions('form');
+
+ $this->assertInternalType('array', $typeExtensions);
+ $this->assertCount(1, $typeExtensions);
+ $this->assertInstanceOf('Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension', array_shift($typeExtensions));
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php
new file mode 100644
index 0000000000..c31f62c7e0
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php
@@ -0,0 +1,465 @@
+
+ *
+ * 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();
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php
new file mode 100644
index 0000000000..d9c1bb3180
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php
@@ -0,0 +1,338 @@
+
+ *
+ * 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
+ */
+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);
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php
new file mode 100644
index 0000000000..cb77456984
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php
@@ -0,0 +1,47 @@
+
+ *
+ * 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\Type;
+
+use Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension;
+
+class DataCollectorTypeExtensionTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var DataCollectorTypeExtension
+ */
+ private $extension;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $dataCollector;
+
+ public function setUp()
+ {
+ $this->dataCollector = $this->getMock('Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface');
+ $this->extension = new DataCollectorTypeExtension($this->dataCollector);
+ }
+
+ public function testGetExtendedType()
+ {
+ $this->assertEquals('form', $this->extension->getExtendedType());
+ }
+
+ public function testBuildForm()
+ {
+ $builder = $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
+ $builder->expects($this->atLeastOnce())
+ ->method('addEventSubscriber')
+ ->with($this->isInstanceOf('Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener'));
+
+ $this->extension->buildForm($builder, array());
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php
index bb24079450..cdd06e1594 100644
--- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php
+++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php
@@ -46,6 +46,11 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
*/
private $resolvedTypeFactory;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $builder;
+
/**
* @var FormFactory
*/
@@ -57,6 +62,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->guesser1 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
$this->guesser2 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
$this->registry = $this->getMock('Symfony\Component\Form\FormRegistryInterface');
+ $this->builder = $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
$this->factory = new FormFactory($this->registry, $this->resolvedTypeFactory);
$this->registry->expects($this->any())
@@ -70,6 +76,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
public function testCreateNamedBuilderWithTypeName()
{
$options = array('a' => '1', 'b' => '2');
+ $resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
$this->registry->expects($this->once())
@@ -80,14 +87,23 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
- ->will($this->returnValue('BUILDER'));
+ ->will($this->returnValue($this->builder));
- $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', null, $options));
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->assertSame($this->builder, $this->factory->createNamedBuilder('name', 'type', null, $options));
}
public function testCreateNamedBuilderWithTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
+ $resolvedOptions = array('a' => '2', 'b' => '3');
$type = new FooType();
$resolvedType = $this->getMockResolvedType();
@@ -99,14 +115,23 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
- ->will($this->returnValue('BUILDER'));
+ ->will($this->returnValue($this->builder));
- $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->assertSame($this->builder, $this->factory->createNamedBuilder('name', $type, null, $options));
}
public function testCreateNamedBuilderWithTypeInstanceWithParentType()
{
$options = array('a' => '1', 'b' => '2');
+ $resolvedOptions = array('a' => '2', 'b' => '3');
$type = new FooSubType();
$resolvedType = $this->getMockResolvedType();
$parentResolvedType = $this->getMockResolvedType();
@@ -124,14 +149,23 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
- ->will($this->returnValue('BUILDER'));
+ ->will($this->returnValue($this->builder));
- $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->assertSame($this->builder, $this->factory->createNamedBuilder('name', $type, null, $options));
}
public function testCreateNamedBuilderWithTypeInstanceWithParentTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
+ $resolvedOptions = array('a' => '2', 'b' => '3');
$type = new FooSubTypeWithParentInstance();
$resolvedType = $this->getMockResolvedType();
$parentResolvedType = $this->getMockResolvedType();
@@ -149,28 +183,46 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
- ->will($this->returnValue('BUILDER'));
+ ->will($this->returnValue($this->builder));
- $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->assertSame($this->builder, $this->factory->createNamedBuilder('name', $type, null, $options));
}
public function testCreateNamedBuilderWithResolvedTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
+ $resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
- ->will($this->returnValue('BUILDER'));
+ ->will($this->returnValue($this->builder));
- $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $resolvedType, null, $options));
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->assertSame($this->builder, $this->factory->createNamedBuilder('name', $resolvedType, null, $options));
}
public function testCreateNamedBuilderFillsDataOption()
{
$givenOptions = array('a' => '1', 'b' => '2');
$expectedOptions = array_merge($givenOptions, array('data' => 'DATA'));
+ $resolvedOptions = array('a' => '2', 'b' => '3', 'data' => 'DATA');
$resolvedType = $this->getMockResolvedType();
$this->registry->expects($this->once())
@@ -181,14 +233,23 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $expectedOptions)
- ->will($this->returnValue('BUILDER'));
+ ->will($this->returnValue($this->builder));
- $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', 'DATA', $givenOptions));
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->assertSame($this->builder, $this->factory->createNamedBuilder('name', 'type', 'DATA', $givenOptions));
}
public function testCreateNamedBuilderDoesNotOverrideExistingDataOption()
{
$options = array('a' => '1', 'b' => '2', 'data' => 'CUSTOM');
+ $resolvedOptions = array('a' => '2', 'b' => '3', 'data' => 'CUSTOM');
$resolvedType = $this->getMockResolvedType();
$this->registry->expects($this->once())
@@ -199,9 +260,17 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
- ->will($this->returnValue('BUILDER'));
+ ->will($this->returnValue($this->builder));
- $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', 'DATA', $options));
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->assertSame($this->builder, $this->factory->createNamedBuilder('name', 'type', 'DATA', $options));
}
/**
@@ -216,8 +285,8 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
public function testCreateUsesTypeNameIfTypeGivenAsString()
{
$options = array('a' => '1', 'b' => '2');
+ $resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
- $builder = $this->getMockFormBuilder();
$this->registry->expects($this->once())
->method('getType')
@@ -227,9 +296,17 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'TYPE', $options)
- ->will($this->returnValue($builder));
+ ->will($this->returnValue($this->builder));
- $builder->expects($this->once())
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->builder->expects($this->once())
->method('getForm')
->will($this->returnValue('FORM'));
@@ -239,8 +316,8 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
public function testCreateUsesTypeNameIfTypeGivenAsObject()
{
$options = array('a' => '1', 'b' => '2');
+ $resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
- $builder = $this->getMockFormBuilder();
$resolvedType->expects($this->once())
->method('getName')
@@ -249,9 +326,17 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'TYPE', $options)
- ->will($this->returnValue($builder));
+ ->will($this->returnValue($this->builder));
- $builder->expects($this->once())
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->builder->expects($this->once())
->method('getForm')
->will($this->returnValue('FORM'));
@@ -261,8 +346,8 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
public function testCreateNamed()
{
$options = array('a' => '1', 'b' => '2');
+ $resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
- $builder = $this->getMockFormBuilder();
$this->registry->expects($this->once())
->method('getType')
@@ -272,9 +357,17 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
- ->will($this->returnValue($builder));
+ ->will($this->returnValue($this->builder));
- $builder->expects($this->once())
+ $this->builder->expects($this->any())
+ ->method('getOptions')
+ ->will($this->returnValue($resolvedOptions));
+
+ $resolvedType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->builder, $resolvedOptions);
+
+ $this->builder->expects($this->once())
->method('getForm')
->will($this->returnValue('FORM'));
@@ -294,9 +387,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array())
->will($this->returnValue('builderInstance'));
- $builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
+ $this->builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
- $this->assertEquals('builderInstance', $builder);
+ $this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence()
@@ -326,9 +419,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'password', null, array('max_length' => 7))
->will($this->returnValue('builderInstance'));
- $builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
+ $this->builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
- $this->assertEquals('builderInstance', $builder);
+ $this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderCreatesTextFormIfNoGuess()
@@ -345,9 +438,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text')
->will($this->returnValue('builderInstance'));
- $builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
+ $this->builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
- $this->assertEquals('builderInstance', $builder);
+ $this->assertEquals('builderInstance', $this->builder);
}
public function testOptionsCanBeOverridden()
@@ -368,14 +461,14 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array('max_length' => 11))
->will($this->returnValue('builderInstance'));
- $builder = $factory->createBuilderForProperty(
+ $this->builder = $factory->createBuilderForProperty(
'Application\Author',
'firstName',
null,
array('max_length' => 11)
);
- $this->assertEquals('builderInstance', $builder);
+ $this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderUsesMaxLengthIfFound()
@@ -403,12 +496,12 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array('max_length' => 20))
->will($this->returnValue('builderInstance'));
- $builder = $factory->createBuilderForProperty(
+ $this->builder = $factory->createBuilderForProperty(
'Application\Author',
'firstName'
);
- $this->assertEquals('builderInstance', $builder);
+ $this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderUsesRequiredSettingWithHighestConfidence()
@@ -436,12 +529,12 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array('required' => false))
->will($this->returnValue('builderInstance'));
- $builder = $factory->createBuilderForProperty(
+ $this->builder = $factory->createBuilderForProperty(
'Application\Author',
'firstName'
);
- $this->assertEquals('builderInstance', $builder);
+ $this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderUsesPatternIfFound()
@@ -469,12 +562,12 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array('pattern' => '[a-zA-Z]'))
->will($this->returnValue('builderInstance'));
- $builder = $factory->createBuilderForProperty(
+ $this->builder = $factory->createBuilderForProperty(
'Application\Author',
'firstName'
);
- $this->assertEquals('builderInstance', $builder);
+ $this->assertEquals('builderInstance', $this->builder);
}
private function getMockFactory(array $methods = array())
@@ -494,9 +587,4 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
{
return $this->getMock('Symfony\Component\Form\FormTypeInterface');
}
-
- private function getMockFormBuilder()
- {
- return $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
- }
}
diff --git a/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php b/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php
index de19e9783e..cf2275f8d4 100644
--- a/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php
@@ -36,12 +36,71 @@ class ResolvedFormTypeTest extends \PHPUnit_Framework_TestCase
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dataMapper;
+ private $parentType;
+ private $type;
+ private $extension1;
+ private $extension2;
+ private $parentResolvedType;
+ private $resolvedType;
protected function setUp()
{
$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->parentType = $this->getMockFormType();
+ $this->type = $this->getMockFormType();
+ $this->extension1 = $this->getMockFormTypeExtension();
+ $this->extension2 = $this->getMockFormTypeExtension();
+ $this->parentResolvedType = new ResolvedFormType($this->parentType);
+ $this->resolvedType = new ResolvedFormType($this->type, array($this->extension1, $this->extension2), $this->parentResolvedType);
+ }
+
+ public function testGetOptionsResolver()
+ {
+ if (version_compare(\PHPUnit_Runner_Version::id(), '3.7', '<')) {
+ $this->markTestSkipped('This test requires PHPUnit 3.7.');
+ }
+
+ $test = $this;
+ $i = 0;
+
+ $assertIndexAndAddOption = function ($index, $option, $default) use (&$i, $test) {
+ return function (OptionsResolverInterface $resolver) use (&$i, $test, $index, $option, $default) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertEquals($index, $i, 'Executed at index '.$index);
+
+ ++$i;
+
+ $resolver->setDefaults(array($option => $default));
+ };
+ };
+
+ // First the default options are generated for the super type
+ $this->parentType->expects($this->once())
+ ->method('setDefaultOptions')
+ ->will($this->returnCallback($assertIndexAndAddOption(0, 'a', 'a_default')));
+
+ // The form type itself
+ $this->type->expects($this->once())
+ ->method('setDefaultOptions')
+ ->will($this->returnCallback($assertIndexAndAddOption(1, 'b', 'b_default')));
+
+ // And its extensions
+ $this->extension1->expects($this->once())
+ ->method('setDefaultOptions')
+ ->will($this->returnCallback($assertIndexAndAddOption(2, 'c', 'c_default')));
+
+ $this->extension2->expects($this->once())
+ ->method('setDefaultOptions')
+ ->will($this->returnCallback($assertIndexAndAddOption(3, 'd', 'd_default')));
+
+ $givenOptions = array('a' => 'a_custom', 'c' => 'c_custom');
+ $resolvedOptions = array('a' => 'a_custom', 'b' => 'b_default', 'c' => 'c_custom', 'd' => 'd_default');
+
+ $resolver = $this->resolvedType->getOptionsResolver();
+
+ $this->assertEquals($resolvedOptions, $resolver->resolve($givenOptions));
}
public function testCreateBuilder()
@@ -50,13 +109,70 @@ class ResolvedFormTypeTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('This test requires PHPUnit 3.7.');
}
- $parentType = $this->getMockFormType();
- $type = $this->getMockFormType();
- $extension1 = $this->getMockFormTypeExtension();
- $extension2 = $this->getMockFormTypeExtension();
+ $givenOptions = array('a' => 'a_custom', 'c' => 'c_custom');
+ $resolvedOptions = array('a' => 'a_custom', 'b' => 'b_default', 'c' => 'c_custom', 'd' => 'd_default');
+ $optionsResolver = $this->getMock('Symfony\Component\OptionsResolver\OptionsResolverInterface');
- $parentResolvedType = new ResolvedFormType($parentType);
- $resolvedType = new ResolvedFormType($type, array($extension1, $extension2), $parentResolvedType);
+ $this->resolvedType = $this->getMockBuilder('Symfony\Component\Form\ResolvedFormType')
+ ->setConstructorArgs(array($this->type, array($this->extension1, $this->extension2), $this->parentResolvedType))
+ ->setMethods(array('getOptionsResolver'))
+ ->getMock();
+
+ $this->resolvedType->expects($this->once())
+ ->method('getOptionsResolver')
+ ->will($this->returnValue($optionsResolver));
+
+ $optionsResolver->expects($this->once())
+ ->method('resolve')
+ ->with($givenOptions)
+ ->will($this->returnValue($resolvedOptions));
+
+ $factory = $this->getMockFormFactory();
+ $builder = $this->resolvedType->createBuilder($factory, 'name', $givenOptions);
+
+ $this->assertSame($this->resolvedType, $builder->getType());
+ $this->assertSame($resolvedOptions, $builder->getOptions());
+ $this->assertNull($builder->getDataClass());
+ }
+
+ public function testCreateBuilderWithDataClassOption()
+ {
+ if (version_compare(\PHPUnit_Runner_Version::id(), '3.7', '<')) {
+ $this->markTestSkipped('This test requires PHPUnit 3.7.');
+ }
+
+ $givenOptions = array('data_class' => 'Foo');
+ $resolvedOptions = array('data_class' => '\stdClass');
+ $optionsResolver = $this->getMock('Symfony\Component\OptionsResolver\OptionsResolverInterface');
+
+ $this->resolvedType = $this->getMockBuilder('Symfony\Component\Form\ResolvedFormType')
+ ->setConstructorArgs(array($this->type, array($this->extension1, $this->extension2), $this->parentResolvedType))
+ ->setMethods(array('getOptionsResolver'))
+ ->getMock();
+
+ $this->resolvedType->expects($this->once())
+ ->method('getOptionsResolver')
+ ->will($this->returnValue($optionsResolver));
+
+ $optionsResolver->expects($this->once())
+ ->method('resolve')
+ ->with($givenOptions)
+ ->will($this->returnValue($resolvedOptions));
+
+ $factory = $this->getMockFormFactory();
+ $builder = $this->resolvedType->createBuilder($factory, 'name', $givenOptions);
+
+ $this->assertSame($this->resolvedType, $builder->getType());
+ $this->assertSame($resolvedOptions, $builder->getOptions());
+ $this->assertSame('\stdClass', $builder->getDataClass());
+ }
+
+
+ public function testBuildForm()
+ {
+ if (version_compare(\PHPUnit_Runner_Version::id(), '3.7', '<')) {
+ $this->markTestSkipped('This test requires PHPUnit 3.7.');
+ }
$test = $this;
$i = 0;
@@ -70,169 +186,142 @@ class ResolvedFormTypeTest extends \PHPUnit_Framework_TestCase
};
};
- $assertIndexAndAddOption = function ($index, $option, $default) use ($assertIndex) {
- $assertIndex = $assertIndex($index);
+ $options = array('a' => 'Foo', 'b' => 'Bar');
+ $builder = $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
- return function (OptionsResolverInterface $resolver) use ($assertIndex, $index, $option, $default) {
- $assertIndex();
-
- $resolver->setDefaults(array($option => $default));
- };
- };
-
- // First the default options are generated for the super type
- $parentType->expects($this->once())
- ->method('setDefaultOptions')
- ->will($this->returnCallback($assertIndexAndAddOption(0, 'a', 'a_default')));
-
- // The form type itself
- $type->expects($this->once())
- ->method('setDefaultOptions')
- ->will($this->returnCallback($assertIndexAndAddOption(1, 'b', 'b_default')));
-
- // And its extensions
- $extension1->expects($this->once())
- ->method('setDefaultOptions')
- ->will($this->returnCallback($assertIndexAndAddOption(2, 'c', 'c_default')));
-
- $extension2->expects($this->once())
- ->method('setDefaultOptions')
- ->will($this->returnCallback($assertIndexAndAddOption(3, 'd', 'd_default')));
-
- $givenOptions = array('a' => 'a_custom', 'c' => 'c_custom');
- $resolvedOptions = array('a' => 'a_custom', 'b' => 'b_default', 'c' => 'c_custom', 'd' => 'd_default');
-
- // Then the form is built for the super type
- $parentType->expects($this->once())
+ // First the form is built for the super type
+ $this->parentType->expects($this->once())
->method('buildForm')
- ->with($this->anything(), $resolvedOptions)
- ->will($this->returnCallback($assertIndex(4)));
+ ->with($builder, $options)
+ ->will($this->returnCallback($assertIndex(0)));
// Then the type itself
- $type->expects($this->once())
+ $this->type->expects($this->once())
->method('buildForm')
- ->with($this->anything(), $resolvedOptions)
- ->will($this->returnCallback($assertIndex(5)));
+ ->with($builder, $options)
+ ->will($this->returnCallback($assertIndex(1)));
// Then its extensions
- $extension1->expects($this->once())
+ $this->extension1->expects($this->once())
->method('buildForm')
- ->with($this->anything(), $resolvedOptions)
- ->will($this->returnCallback($assertIndex(6)));
+ ->with($builder, $options)
+ ->will($this->returnCallback($assertIndex(2)));
- $extension2->expects($this->once())
+ $this->extension2->expects($this->once())
->method('buildForm')
- ->with($this->anything(), $resolvedOptions)
- ->will($this->returnCallback($assertIndex(7)));
+ ->with($builder, $options)
+ ->will($this->returnCallback($assertIndex(3)));
- $factory = $this->getMockFormFactory();
- $builder = $resolvedType->createBuilder($factory, 'name', $givenOptions);
-
- $this->assertSame($resolvedType, $builder->getType());
+ $this->resolvedType->buildForm($builder, $options);
}
public function testCreateView()
{
- $parentType = $this->getMockFormType();
- $type = $this->getMockFormType();
- $field1Type = $this->getMockFormType();
- $field2Type = $this->getMockFormType();
- $extension1 = $this->getMockFormTypeExtension();
- $extension2 = $this->getMockFormTypeExtension();
+ $form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
- $parentResolvedType = new ResolvedFormType($parentType);
- $resolvedType = new ResolvedFormType($type, array($extension1, $extension2), $parentResolvedType);
- $field1ResolvedType = new ResolvedFormType($field1Type);
- $field2ResolvedType = new ResolvedFormType($field2Type);
+ $view = $this->resolvedType->createView($form);
+ $this->assertInstanceOf('Symfony\Component\Form\FormView', $view);
+ $this->assertNull($view->parent);
+ }
+
+ public function testCreateViewWithParent()
+ {
+ $form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
+ $parentView = $this->getMock('Symfony\Component\Form\FormView');
+
+ $view = $this->resolvedType->createView($form, $parentView);
+
+ $this->assertInstanceOf('Symfony\Component\Form\FormView', $view);
+ $this->assertSame($parentView, $view->parent);
+ }
+
+ public function testBuildView()
+ {
$options = array('a' => '1', 'b' => '2');
- $form = $this->getBuilder('name', $options)
- ->setCompound(true)
- ->setDataMapper($this->dataMapper)
- ->setType($resolvedType)
- ->add($this->getBuilder('foo')->setType($field1ResolvedType))
- ->add($this->getBuilder('bar')->setType($field2ResolvedType))
- ->getForm();
+ $form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
+ $view = $this->getMock('Symfony\Component\Form\FormView');
$test = $this;
$i = 0;
- $assertIndexAndNbOfChildViews = function ($index, $nbOfChildViews) use (&$i, $test) {
- return function (FormView $view) use (&$i, $test, $index, $nbOfChildViews) {
+ $assertIndex = function ($index) use (&$i, $test) {
+ return function () use (&$i, $test, $index) {
/* @var \PHPUnit_Framework_TestCase $test */
$test->assertEquals($index, $i, 'Executed at index '.$index);
- $test->assertCount($nbOfChildViews, $view);
++$i;
};
};
// First the super type
- $parentType->expects($this->once())
+ $this->parentType->expects($this->once())
->method('buildView')
- ->with($this->anything(), $form, $options)
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(0, 0)));
+ ->with($view, $form, $options)
+ ->will($this->returnCallback($assertIndex(0)));
// Then the type itself
- $type->expects($this->once())
+ $this->type->expects($this->once())
->method('buildView')
- ->with($this->anything(), $form, $options)
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(1, 0)));
+ ->with($view, $form, $options)
+ ->will($this->returnCallback($assertIndex(1)));
// Then its extensions
- $extension1->expects($this->once())
+ $this->extension1->expects($this->once())
->method('buildView')
- ->with($this->anything(), $form, $options)
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(2, 0)));
+ ->with($view, $form, $options)
+ ->will($this->returnCallback($assertIndex(2)));
- $extension2->expects($this->once())
+ $this->extension2->expects($this->once())
->method('buildView')
- ->with($this->anything(), $form, $options)
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(3, 0)));
+ ->with($view, $form, $options)
+ ->will($this->returnCallback($assertIndex(3)));
- // Now the first child form
- $field1Type->expects($this->once())
- ->method('buildView')
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(4, 0)));
- $field1Type->expects($this->once())
+ $this->resolvedType->buildView($view, $form, $options);
+ }
+
+ public function testFinishView()
+ {
+ $options = array('a' => '1', 'b' => '2');
+ $form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
+ $view = $this->getMock('Symfony\Component\Form\FormView');
+
+ $test = $this;
+ $i = 0;
+
+ $assertIndex = function ($index) use (&$i, $test) {
+ return function () use (&$i, $test, $index) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertEquals($index, $i, 'Executed at index '.$index);
+
+ ++$i;
+ };
+ };
+
+ // First the super type
+ $this->parentType->expects($this->once())
->method('finishView')
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(5, 0)));
-
- // And the second child form
- $field2Type->expects($this->once())
- ->method('buildView')
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(6, 0)));
- $field2Type->expects($this->once())
- ->method('finishView')
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(7, 0)));
-
- // Again first the parent
- $parentType->expects($this->once())
- ->method('finishView')
- ->with($this->anything(), $form, $options)
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(8, 2)));
+ ->with($view, $form, $options)
+ ->will($this->returnCallback($assertIndex(0)));
// Then the type itself
- $type->expects($this->once())
+ $this->type->expects($this->once())
->method('finishView')
- ->with($this->anything(), $form, $options)
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(9, 2)));
+ ->with($view, $form, $options)
+ ->will($this->returnCallback($assertIndex(1)));
// Then its extensions
- $extension1->expects($this->once())
+ $this->extension1->expects($this->once())
->method('finishView')
- ->with($this->anything(), $form, $options)
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(10, 2)));
+ ->with($view, $form, $options)
+ ->will($this->returnCallback($assertIndex(2)));
- $extension2->expects($this->once())
+ $this->extension2->expects($this->once())
->method('finishView')
- ->with($this->anything(), $form, $options)
- ->will($this->returnCallback($assertIndexAndNbOfChildViews(11, 2)));
+ ->with($view, $form, $options)
+ ->will($this->returnCallback($assertIndex(3)));
- $parentView = new FormView();
- $view = $resolvedType->createView($form, $parentView);
-
- $this->assertSame($parentView, $view->parent);
+ $this->resolvedType->finishView($view, $form, $options);
}
/**
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php
index 7d9c28943b..bf2c97f8e6 100644
--- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php
+++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php
@@ -11,17 +11,26 @@
namespace Symfony\Component\HttpKernel\DataCollector;
+use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
+use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporterInterface;
+
/**
* DataCollector.
*
* Children of this class must store the collected data in the data property.
*
* @author Fabien Potencier
+ * @author Bernhard Schussek
*/
abstract class DataCollector implements DataCollectorInterface, \Serializable
{
protected $data;
+ /**
+ * @var ValueExporter
+ */
+ private $valueExporter;
+
public function serialize()
{
return serialize($this->data);
@@ -41,35 +50,10 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
*/
protected function varToString($var)
{
- if (is_object($var)) {
- return sprintf('Object(%s)', get_class($var));
+ if (null === $this->valueExporter) {
+ $this->valueExporter = new ValueExporter();
}
- if (is_array($var)) {
- $a = array();
- foreach ($var as $k => $v) {
- $a[] = sprintf('%s => %s', $k, $this->varToString($v));
- }
-
- return sprintf("Array(%s)", implode(', ', $a));
- }
-
- if (is_resource($var)) {
- return sprintf('Resource(%s)', get_resource_type($var));
- }
-
- if (null === $var) {
- return 'null';
- }
-
- if (false === $var) {
- return 'false';
- }
-
- if (true === $var) {
- return 'true';
- }
-
- return (string) $var;
+ $this->valueExporter->exportValue($var);
}
}
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/Util/ValueExporter.php b/src/Symfony/Component/HttpKernel/DataCollector/Util/ValueExporter.php
new file mode 100644
index 0000000000..f3aeb80cb2
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/DataCollector/Util/ValueExporter.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\DataCollector\Util;
+
+/**
+ * @author Bernhard Schussek
+ */
+class ValueExporter
+{
+ /**
+ * Converts a PHP value to a string.
+ *
+ * @param mixed $value The PHP value
+ *
+ * @return string The string representation of the given value
+ */
+ public function exportValue($value)
+ {
+ if (is_object($value)) {
+ return sprintf('Object(%s)', get_class($value));
+ }
+
+ if (is_array($value)) {
+ $a = array();
+ foreach ($value as $k => $v) {
+ $a[] = sprintf('%s => %s', $k, $this->exportValue($v));
+ }
+
+ return sprintf("Array(%s)", implode(', ', $a));
+ }
+
+ if (is_resource($value)) {
+ return sprintf('Resource(%s)', get_resource_type($value));
+ }
+
+ if (null === $value) {
+ return 'null';
+ }
+
+ if (false === $value) {
+ return 'false';
+ }
+
+ if (true === $value) {
+ return 'true';
+ }
+
+ return (string) $value;
+ }
+}