Ver código fonte

[Form] Implemented generic data filter hooks

You can now modify set or bound data by adding a filter for either of the following events:

* Filters::filterBoundDataFromClient
* Filters::filterBoundData
* Filters::filterSetData
Bernhard Schussek 14 anos atrás
pai
commit
528ef55da6

+ 6 - 6
src/Symfony/Component/Form/CollectionField.php

@@ -38,6 +38,8 @@ class CollectionField extends Form
      */
     protected $prototype;
 
+    private $modifiable = true;
+
     public function __construct($key, array $options = array())
     {
         // This doesn't work with addOption(), because the value of this option
@@ -63,9 +65,7 @@ class CollectionField extends Form
      */
     protected function configure()
     {
-        $this->addOption('modifiable', false);
-
-        if ($this->getOption('modifiable')) {
+        if ($this->modifiable) {
             $field = $this->newField('$$key$$', null);
             // TESTME
             $field->setRequired(false);
@@ -86,7 +86,7 @@ class CollectionField extends Form
         }
 
         foreach ($this as $name => $field) {
-            if (!$this->getOption('modifiable') || '$$key$$' != $name) {
+            if (!$this->modifiable || '$$key$$' != $name) {
                 $this->remove($name);
             }
         }
@@ -107,14 +107,14 @@ class CollectionField extends Form
         }
 
         foreach ($this as $name => $field) {
-            if (!isset($data[$name]) && $this->getOption('modifiable') && '$$key$$' != $name) {
+            if (!isset($data[$name]) && $this->modifiable && '$$key$$' != $name) {
                 $this->remove($name);
                 $this->removedFields[] = $name;
             }
         }
 
         foreach ($data as $name => $value) {
-            if (!isset($this[$name]) && $this->getOption('modifiable')) {
+            if (!isset($this[$name]) && $this->modifiable) {
                 $this->add($this->newField($name, $name));
             }
         }

+ 69 - 59
src/Symfony/Component/Form/Field.php

@@ -13,9 +13,11 @@ namespace Symfony\Component\Form;
 
 use Symfony\Component\Form\ValueTransformer\ValueTransformerInterface;
 use Symfony\Component\Form\ValueTransformer\TransformationFailedException;
-use Symfony\Component\Form\DataProcessor\DataProcessorInterface;
 use Symfony\Component\Form\Renderer\RendererInterface;
 use Symfony\Component\Form\Renderer\Plugin\PluginInterface;
+use Symfony\Component\Form\Filter\FilterChainInterface;
+use Symfony\Component\Form\Filter\FilterChain;
+use Symfony\Component\Form\Filter\FilterInterface;
 
 /**
  * Base class for form fields
@@ -49,7 +51,7 @@ use Symfony\Component\Form\Renderer\Plugin\PluginInterface;
  *
  * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  */
-class Field extends Configurable implements FieldInterface
+class Field implements FieldInterface
 {
     private $errors = array();
     private $key = '';
@@ -61,17 +63,39 @@ class Field extends Configurable implements FieldInterface
     private $transformedData = '';
     private $normalizationTransformer;
     private $valueTransformer;
-    private $dataProcessor;
     private $propertyPath;
     private $transformationSuccessful = true;
     private $renderer;
     private $hidden = false;
     private $trim = true;
     private $disabled = false;
+    private $filterChain;
 
     public function __construct($key = null)
     {
         $this->key = (string)$key;
+        // TODO should be injected instead
+        $this->filterChain = new FilterChain(Filters::$all);
+    }
+
+    /**
+     * @deprecated
+     */
+    public function prependFilter(FilterInterface $filter)
+    {
+        $this->filterChain->prependFilter($filter);
+
+        return $this;
+    }
+
+    /**
+     * @deprecated
+     */
+    public function appendFilter(FilterInterface $filter)
+    {
+        $this->filterChain->appendFilter($filter);
+
+        return $this;
     }
 
     /**
@@ -242,17 +266,23 @@ class Field extends Configurable implements FieldInterface
      *
      * @see FieldInterface
      */
-    public function setData($data)
+    public function setData($appData)
     {
-        // All four transformation methods must be executed to make sure
-        // that all three data representations are synchronized
-        // Store data in between steps because processData() might use
-        // this data
-        $this->data = $data;
-        $this->normalizedData = $this->normalize($data);
-        $this->transformedData = $this->transform($this->normalize($data));
-        $this->normalizedData = $this->processData($this->reverseTransform($this->transformedData));
-        $this->data = $this->denormalize($this->normalizedData);
+        // Hook to change content of the data
+        $appData = $this->filterChain->filter(Filters::filterSetData, $appData);
+
+        // Treat data as strings unless a value transformer exists
+        if (null === $this->valueTransformer && is_scalar($appData)) {
+            $appData = (string)$appData;
+        }
+
+        // Synchronize representations - must not change the content!
+        $normData = $this->normalize($appData);
+        $clientData = $this->transform($normData);
+
+        $this->data = $appData;
+        $this->normalizedData = $normData;
+        $this->transformedData = $clientData;
 
         return $this;
     }
@@ -262,44 +292,46 @@ class Field extends Configurable implements FieldInterface
      *
      * @param  string|array $data  The POST data
      */
-    public function submit($data)
+    public function submit($clientData)
     {
-        $this->transformedData = (is_array($data) || is_object($data)) ? $data : (string)$data;
         $this->submitted = true;
         $this->errors = array();
 
-        if (is_string($this->transformedData) && $this->trim) {
-            $this->transformedData = trim($this->transformedData);
+        if (is_scalar($clientData) || null === $clientData) {
+            $clientData = (string)$clientData;
         }
 
+        if (is_string($clientData) && $this->trim) {
+            $clientData = trim($clientData);
+        }
+
+        $appData = null;
+        $normData = null;
+
+        // Hook to change content of the data submitted by the browser
+        $clientData = $this->filterChain->filter(Filters::filterBoundDataFromClient, $clientData);
+
         try {
-            $this->normalizedData = $this->processData($this->reverseTransform($this->transformedData));
-            $this->data = $this->denormalize($this->normalizedData);
-            $this->transformedData = $this->transform($this->normalizedData);
+            // Normalize data to unified representation
+            $normData = $this->reverseTransform($clientData);
             $this->transformationSuccessful = true;
         } catch (TransformationFailedException $e) {
             $this->transformationSuccessful = false;
         }
-    }
 
-    /**
-     * Processes the submitted reverse-transformed data.
-     *
-     * This method can be overridden if you want to modify the data entered
-     * by the user. Note that the data is already in reverse transformed format.
-     *
-     * This method will not be called if reverse transformation fails.
-     *
-     * @param  mixed $data
-     * @return mixed
-     */
-    protected function processData($data)
-    {
-        if ($this->dataProcessor) {
-            return $this->dataProcessor->processData($data);
+        if ($this->transformationSuccessful) {
+            // Hook to change content of the data in the normalized
+            // representation
+            $normData = $this->filterChain->filter(Filters::filterBoundData, $normData);
+
+            // Synchronize representations - must not change the content!
+            $appData = $this->denormalize($normData);
+            $clientData = $this->transform($normData);
         }
 
-        return $data;
+        $this->data = $appData;
+        $this->normalizedData = $normData;
+        $this->transformedData = $clientData;
     }
 
     /**
@@ -432,28 +464,6 @@ class Field extends Configurable implements FieldInterface
         return $this->valueTransformer;
     }
 
-    /**
-     * Sets the data processor
-     *
-     * @param DataProcessorInterface $dataProcessor
-     */
-    public function setDataProcessor(DataProcessorInterface $dataProcessor = null)
-    {
-        $this->dataProcessor = $dataProcessor;
-
-        return $this;
-    }
-
-    /**
-     * Returns the data processor
-     *
-     * @return DataProcessorInterface
-     */
-    public function getDataProcessor()
-    {
-        return $this->dataProcessor;
-    }
-
     public function setTrim($trim)
     {
         $this->trim = $trim;

+ 9 - 3
src/Symfony/Component/Form/DataProcessor/FileUploader.php

@@ -9,9 +9,10 @@
  * file that was distributed with this source code.
  */
 
-namespace Symfony\Component\Form\DataProcessor;
+namespace Symfony\Component\Form\Filter;
 
 use Symfony\Component\Form\FieldInterface;
+use Symfony\Component\Form\Filters;
 use Symfony\Component\HttpFoundation\File\File;
 use Symfony\Component\HttpFoundation\File\UploadedFile;
 use Symfony\Component\HttpFoundation\File\TemporaryStorage;
@@ -21,7 +22,7 @@ use Symfony\Component\HttpFoundation\File\TemporaryStorage;
  *
  * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  */
-class FileUploader implements DataProcessorInterface
+class FileUploadFilter implements FilterInterface
 {
     private $field;
 
@@ -33,7 +34,12 @@ class FileUploader implements DataProcessorInterface
         $this->storage = $storage;
     }
 
-    public function processData($data)
+    public function getSupportedFilters()
+    {
+        return Filters::filterBoundDataFromClient;
+    }
+
+    public function filterBoundDataFromClient($data)
     {
         // TESTME
         $data = array_merge(array(

+ 64 - 0
src/Symfony/Component/Form/Filter/FilterChain.php

@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Filter;
+
+/**
+ * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
+ */
+class FilterChain
+{
+    private $supportedTypes;
+
+    private $filters = array();
+
+    public function __construct(array $supportedTypes)
+    {
+        $this->supportedTypes = $supportedTypes;
+    }
+
+    public function prependFilter(FilterInterface $filter)
+    {
+        foreach ((array)$filter->getSupportedFilters() as $type) {
+            // TODO check whether the filter has the $type method
+
+            if (!isset($this->filters[$type])) {
+                $this->filters[$type] = array();
+            }
+
+            array_unshift($this->filters[$type], $filter);
+        }
+    }
+
+    public function appendFilter(FilterInterface $filter)
+    {
+        foreach ((array)$filter->getSupportedFilters() as $type) {
+            // TODO check whether the filter has the $type method
+
+            if (!isset($this->filters[$type])) {
+                $this->filters[$type] = array();
+            }
+
+            $this->filters[$type][] = $filter;
+        }
+    }
+
+    public function filter($type, $data)
+    {
+        if (isset($this->filters[$type])) {
+            foreach ($this->filters[$type] as $filter) {
+                $data = $filter->$type($data);
+            }
+        }
+
+        return $data;
+    }
+}

+ 3 - 3
src/Symfony/Component/Form/DataProcessor/DataProcessorInterface.php

@@ -9,9 +9,9 @@
  * file that was distributed with this source code.
  */
 
-namespace Symfony\Component\Form\DataProcessor;
+namespace Symfony\Component\Form\Filter;
 
-interface DataProcessorInterface
+interface FilterChainInterface
 {
-    function processData($data);
+    function filter($type, $data);
 }

+ 17 - 0
src/Symfony/Component/Form/Filter/FilterInterface.php

@@ -0,0 +1,17 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Filter;
+
+interface FilterInterface
+{
+    function getSupportedFilters();
+}

+ 9 - 3
src/Symfony/Component/Form/DataProcessor/UrlProtocolFixer.php

@@ -9,16 +9,17 @@
  * file that was distributed with this source code.
  */
 
-namespace Symfony\Component\Form\DataProcessor;
+namespace Symfony\Component\Form\Filter;
 
 use Symfony\Component\Form\FieldInterface;
+use Symfony\Component\Form\Filters;
 
 /**
  * Adds a protocol to a URL if it doesn't already have one.
  *
  * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  */
-class UrlProtocolFixer implements DataProcessorInterface
+class FixUrlProtocolFilter implements FilterInterface
 {
     private $defaultProtocol;
 
@@ -27,7 +28,7 @@ class UrlProtocolFixer implements DataProcessorInterface
         $this->defaultProtocol = $defaultProtocol;
     }
 
-    public function processData($data)
+    public function filterBoundData($data)
     {
         if ($this->defaultProtocol && $data && !preg_match('~^\w+://~', $data)) {
             $data = $this->defaultProtocol . '://' . $data;
@@ -35,4 +36,9 @@ class UrlProtocolFixer implements DataProcessorInterface
 
         return $data;
     }
+
+    public function getSupportedFilters()
+    {
+        return Filters::filterBoundData;
+    }
 }

+ 9 - 3
src/Symfony/Component/Form/DataProcessor/RadioToArrayConverter.php

@@ -9,9 +9,10 @@
  * file that was distributed with this source code.
  */
 
-namespace Symfony\Component\Form\DataProcessor;
+namespace Symfony\Component\Form\Filter;
 
 use Symfony\Component\Form\FieldInterface;
+use Symfony\Component\Form\Filters;
 
 /**
  * Takes care of converting the input from a single radio button
@@ -19,10 +20,15 @@ use Symfony\Component\Form\FieldInterface;
  *
  * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  */
-class RadioToArrayConverter implements DataProcessorInterface
+class RadioInputFilter implements FilterInterface
 {
-    public function processData($data)
+    public function filterBoundDataFromClient($data)
     {
         return count((array)$data) === 0 ? array() : array($data => true);
     }
+
+    public function getSupportedFilters()
+    {
+        return Filters::filterBoundDataFromClient;
+    }
 }

+ 30 - 0
src/Symfony/Component/Form/Filters.php

@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+/**
+ * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
+ */
+final class Filters
+{
+    const filterBoundDataFromClient = 'filterBoundDataFromClient';
+
+    const filterBoundData = 'filterBoundData';
+
+    const filterSetData = 'filterSetData';
+
+    public static $all = array(
+        self::filterBoundDataFromClient,
+        self::filterBoundData,
+        self::filterSetData,
+    );
+}

+ 50 - 67
src/Symfony/Component/Form/Form.php

@@ -22,8 +22,8 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
 use Symfony\Component\Form\Exception\DanglingFieldException;
 use Symfony\Component\Form\Exception\FieldDefinitionException;
 use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
-use Symfony\Component\Form\DataProcessor\DataProcessorInterface;
 use Symfony\Component\Form\FieldFactory\FieldFactoryInterface;
+use Symfony\Component\Form\Filter\FilterInterface;
 
 /**
  * Form represents a form.
@@ -40,7 +40,7 @@ use Symfony\Component\Form\FieldFactory\FieldFactoryInterface;
  * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  */
-class Form extends Field implements \IteratorAggregate, FormInterface
+class Form extends Field implements \IteratorAggregate, FormInterface, FilterInterface
 {
     /**
      * Contains all the fields of this group
@@ -66,8 +66,6 @@ class Form extends Field implements \IteratorAggregate, FormInterface
      */
     private $dataConstructor;
 
-    private $dataPreprocessor;
-
     private $modifyByReference = true;
 
     private $validator;
@@ -80,6 +78,13 @@ class Form extends Field implements \IteratorAggregate, FormInterface
 
     private $csrfProvider;
 
+    public function __construct($key = null)
+    {
+        parent::__construct($key);
+
+        $this->appendFilter($this);
+    }
+
     /**
      * Clones this group
      */
@@ -255,28 +260,58 @@ class Form extends Field implements \IteratorAggregate, FormInterface
         return $this;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    protected function transform($value)
+    public function filterSetData($data)
     {
-        if (null === $this->getValueTransformer()) {
+        if (null === $this->getValueTransformer() && null === $this->getNormalizationTransformer()) {
             // Empty values must be converted to objects or arrays so that
             // they can be read by PropertyPath in the child fields
-            if (empty($value)) {
+            if (empty($data)) {
                 if ($this->dataConstructor) {
                     $constructor = $this->dataConstructor;
-                    return $constructor();
+                    $data = $constructor();
                 } else if ($this->dataClass) {
                     $class = $this->dataClass;
-                    return new $class();
+                    $data = new $class();
                 } else {
-                    return array();
+                    $data = array();
                 }
             }
         }
 
-        return parent::transform($value);
+        return $data;
+    }
+
+    public function filterBoundDataFromClient($data)
+    {
+        if (!is_array($data)) {
+            throw new UnexpectedTypeException($data, 'array');
+        }
+
+        foreach ($this->fields as $key => $field) {
+            if (!isset($data[$key])) {
+                $data[$key] = null;
+            }
+        }
+
+        foreach ($data as $key => $value) {
+            if ($this->has($key)) {
+                $this->fields[$key]->submit($value);
+            }
+        }
+
+        $data = $this->getTransformedData();
+
+        $this->writeObject($data);
+
+        return $data;
+    }
+
+    public function getSupportedFilters()
+    {
+        return array(
+            Filters::filterSetData,
+            Filters::filterBoundDataFromClient,
+        );
     }
 
     /**
@@ -303,42 +338,12 @@ class Form extends Field implements \IteratorAggregate, FormInterface
      */
     public function submit($data)
     {
-        if (null === $data) {
-            $data = array();
-        }
-
-        // might return an array, if $data isn't one already
-        $data = $this->preprocessData($data);
-
-        // remember for later
-        $submittedData = $data;
-
-        if (!is_array($data)) {
-            throw new UnexpectedTypeException($data, 'array');
-        }
-
-        foreach ($this->fields as $key => $field) {
-            if (!isset($data[$key])) {
-                $data[$key] = null;
-            }
-        }
-
-        foreach ($data as $key => $value) {
-            if ($this->has($key)) {
-                $this->fields[$key]->submit($value);
-            }
-        }
-
-        $data = $this->getTransformedData();
-
-        $this->writeObject($data);
-
         // set and reverse transform the data
         parent::submit($data);
 
         $this->extraFields = array();
 
-        foreach ($submittedData as $key => $value) {
+        foreach ((array)$data as $key => $value) {
             if (!$this->has($key)) {
                 $this->extraFields[] = $key;
             }
@@ -911,28 +916,6 @@ class Form extends Field implements \IteratorAggregate, FormInterface
         return true;
     }
 
-    /**
-     * Sets the data preprocessor
-     *
-     * @param DataProcessorInterface $dataPreprocessor
-     */
-    public function setDataPreprocessor(DataProcessorInterface $dataPreprocessor)
-    {
-        $this->dataPreprocessor = $dataPreprocessor;
-
-        return $this;
-    }
-
-    /**
-     * Returns the data preprocessor
-     *
-     * @return DataPreprocessorInterface
-     */
-    public function getDataPreprocessor()
-    {
-        return $this->dataPreprocessor;
-    }
-
     public function setModifyByReference($modifyByReference)
     {
         $this->modifyByReference = $modifyByReference;

+ 8 - 9
src/Symfony/Component/Form/FormFactory.php

@@ -18,10 +18,9 @@ use Symfony\Component\Form\ChoiceList\MonthChoiceList;
 use Symfony\Component\Form\ChoiceList\TimeZoneChoiceList;
 use Symfony\Component\Form\ChoiceList\EntityChoiceList;
 use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
-use Symfony\Component\Form\DataProcessor\RadioToArrayConverter;
-use Symfony\Component\Form\DataProcessor\UrlProtocolFixer;
-use Symfony\Component\Form\DataProcessor\CollectionMerger;
-use Symfony\Component\Form\DataProcessor\FileUploader;
+use Symfony\Component\Form\Filter\RadioInputFilter;
+use Symfony\Component\Form\Filter\FixUrlProtocolFilter;
+use Symfony\Component\Form\Filter\FileUploadFilter;
 use Symfony\Component\Form\FieldFactory\FieldFactoryInterface;
 use Symfony\Component\Form\Renderer\DefaultRenderer;
 use Symfony\Component\Form\Renderer\Theme\ThemeInterface;
@@ -54,6 +53,7 @@ use Symfony\Component\Form\ValueTransformer\ArrayToPartsTransformer;
 use Symfony\Component\Form\ValueTransformer\ValueToDuplicatesTransformer;
 use Symfony\Component\Form\ValueTransformer\FileToArrayTransformer;
 use Symfony\Component\Form\ValueTransformer\FileToStringTransformer;
+use Symfony\Component\Form\ValueTransformer\MergeCollectionTransformer;
 use Symfony\Component\Validator\ValidatorInterface;
 use Symfony\Component\Locale\Locale;
 use Symfony\Component\HttpFoundation\File\TemporaryStorage;
@@ -337,7 +337,7 @@ class FormFactory
         ), $options);
 
         return $this->getTextField($key, $options)
-            ->setDataProcessor(new UrlProtocolFixer($options['default_protocol']));
+            ->prependFilter(new FixUrlProtocolFilter($options['default_protocol']));
     }
 
     protected function getChoiceFieldForList($key, ChoiceListInterface $choiceList, array $options = array())
@@ -377,7 +377,7 @@ class FormFactory
 
         if (!$options['multiple'] && $options['expanded']) {
             $field->setValueTransformer(new ScalarToChoicesTransformer($choiceList));
-            $field->setDataPreprocessor(new RadioToArrayConverter());
+            $field->prependFilter(new RadioInputFilter());
         }
 
         if ($options['multiple'] && !$options['expanded']) {
@@ -429,8 +429,7 @@ class FormFactory
         $transformers = array();
 
         if ($options['multiple']) {
-            $field->setDataProcessor(new CollectionMerger($field));
-
+            $transformers[] = new MergeCollectionTransformer($field);
             $transformers[] = new EntitiesToArrayTransformer($choiceList);
 
             if ($options['expanded']) {
@@ -903,7 +902,7 @@ class FormFactory
         }
 
         return $field
-            ->setDataPreprocessor(new FileUploader($field, $this->storage))
+            ->prependFilter(new FileUploadFilter($field, $this->storage))
             ->setData(null) // FIXME
             ->add($this->getField('file')->setRendererVar('type', 'file'))
             ->add($this->getHiddenField('token'))

+ 10 - 4
src/Symfony/Component/Form/DataProcessor/CollectionMerger.php

@@ -9,19 +9,25 @@
  * file that was distributed with this source code.
  */
 
-namespace Symfony\Component\Form\DataProcessor;
+namespace Symfony\Component\Form\ValueTransformer;
 
 use Symfony\Component\Form\FieldInterface;
 
-class CollectionMerger implements DataProcessorInterface
+class MergeCollectionTransformer implements ValueTransformerInterface
 {
     private $field;
 
-    public function __construct(FieldInterface $field) {
+    public function __construct(FieldInterface $field)
+    {
         $this->field = $field;
     }
 
-    public function processData($data)
+    public function transform($data)
+    {
+        return $data;
+    }
+
+    public function reverseTransform($data)
     {
         $collection = $this->field->getData();
 

+ 2 - 2
tests/Symfony/Tests/Component/Form/EntityChoiceFieldTest.php

@@ -155,7 +155,7 @@ class EntityChoiceFieldTest extends DoctrineOrmTestCase
         ));
         $field->setData(null);
 
-        $this->assertEquals(new ArrayCollection(), $field->getData());
+        $this->assertEquals(null, $field->getData());
         $this->assertEquals(array(), $field->getDisplayedData());
     }
 
@@ -169,7 +169,7 @@ class EntityChoiceFieldTest extends DoctrineOrmTestCase
         ));
         $field->setData(null);
 
-        $this->assertEquals(new ArrayCollection(), $field->getData());
+        $this->assertEquals(null, $field->getData());
         $this->assertEquals(array(), $field->getDisplayedData());
     }
 

+ 39 - 110
tests/Symfony/Tests/Component/Form/FieldTest.php

@@ -15,6 +15,7 @@ require_once __DIR__ . '/TestCase.php';
 require_once __DIR__ . '/Fixtures/Author.php';
 require_once __DIR__ . '/Fixtures/InvalidField.php';
 require_once __DIR__ . '/Fixtures/FixedValueTransformer.php';
+require_once __DIR__ . '/Fixtures/FixedFilter.php';
 
 use Symfony\Component\Form\ValueTransformer\ValueTransformerInterface;
 use Symfony\Component\Form\PropertyPath;
@@ -24,6 +25,7 @@ use Symfony\Component\Form\ValueTransformer\TransformationFailedException;
 use Symfony\Tests\Component\Form\Fixtures\Author;
 use Symfony\Tests\Component\Form\Fixtures\InvalidField;
 use Symfony\Tests\Component\Form\Fixtures\FixedValueTransformer;
+use Symfony\Tests\Component\Form\Fixtures\FixedFilter;
 
 class FieldTest extends TestCase
 {
@@ -167,7 +169,7 @@ class FieldTest extends TestCase
         $this->assertTrue($this->field->isSubmitted());
     }
 
-    public function testDefaultValuesAreTransformedCorrectly()
+    public function testDefaultDataIsTransformedCorrectly()
     {
         $field = $this->factory->getField('name');
 
@@ -175,7 +177,7 @@ class FieldTest extends TestCase
         $this->assertEquals('', $this->field->getDisplayedData());
     }
 
-    public function testValuesAreTransformedCorrectlyIfNull_noValueTransformer()
+    public function testDataIsTransformedCorrectlyIfNull_noValueTransformer()
     {
         $this->field->setData(null);
 
@@ -183,7 +185,7 @@ class FieldTest extends TestCase
         $this->assertSame('', $this->field->getDisplayedData());
     }
 
-    public function testValuesAreTransformedCorrectlyIfNotNull_noValueTransformer()
+    public function testDataIsTransformedCorrectlyIfNotNull_noValueTransformer()
     {
         $this->field->setData(123);
 
@@ -195,116 +197,43 @@ class FieldTest extends TestCase
         $this->assertSame('123', $this->field->getDisplayedData());
     }
 
-    public function testSubmittedValuesAreTransformedCorrectly()
+    public function testSubmittedDataIsTransformedCorrectly()
     {
-        $valueTransformer = $this->createMockTransformer();
-        $normTransformer = $this->createMockTransformer();
-
-        $field = $this->getMock(
-            'Symfony\Component\Form\Field',
-            array('processData'), // only mock processData()
-            array('title')
-        );
-        $field->setValueTransformer($valueTransformer);
-        $field->setNormalizationTransformer($normTransformer);
-
-        // 1a. The value is converted to a string and passed to the value transformer
-        $valueTransformer->expects($this->once())
-                                ->method('reverseTransform')
-                                ->with($this->identicalTo('0'))
-                                ->will($this->returnValue('reverse[0]'));
-
-        // 2. The output of the reverse transformation is passed to processData()
-        //    The processed data is accessible through getNormalizedData()
-        $field->expects($this->once())
-                    ->method('processData')
-                    ->with($this->equalTo('reverse[0]'))
-                    ->will($this->returnValue('processed[reverse[0]]'));
-
-        // 3. The processed data is denormalized and then accessible through
-        //    getData()
-        $normTransformer->expects($this->once())
-                                ->method('reverseTransform')
-                                ->with($this->identicalTo('processed[reverse[0]]'))
-                                ->will($this->returnValue('denorm[processed[reverse[0]]]'));
-
-        // 4. The processed data is transformed again and then accessible
-        //    through getDisplayedData()
-        $valueTransformer->expects($this->once())
-                                ->method('transform')
-                                ->with($this->equalTo('processed[reverse[0]]'))
-                                ->will($this->returnValue('transform[processed[reverse[0]]]'));
-
-        $field->submit(0);
-
-        $this->assertEquals('denorm[processed[reverse[0]]]', $field->getData());
-        $this->assertEquals('processed[reverse[0]]', $field->getNormalizedData());
-        $this->assertEquals('transform[processed[reverse[0]]]', $field->getDisplayedData());
-    }
-
-    public function testSubmittedValuesAreTransformedCorrectlyIfEmpty_processDataReturnsValue()
-    {
-        $transformer = $this->createMockTransformer();
-
-        $field = $this->getMock(
-            'Symfony\Component\Form\Field',
-            array('processData'), // only mock processData()
-            array('title')
-        );
-        $field->setValueTransformer($transformer);
-
-        // 1. Empty values are converted to NULL by convention
-        $transformer->expects($this->once())
-                                ->method('reverseTransform')
-                                ->with($this->identicalTo(''))
-                                ->will($this->returnValue(null));
-
-        // 2. NULL is passed to processData()
-        $field->expects($this->once())
-                    ->method('processData')
-                    ->with($this->identicalTo(null))
-                    ->will($this->returnValue('processed'));
-
-        // 3. The processed data is transformed (for displayed data)
-        $transformer->expects($this->once())
-                                ->method('transform')
-                                ->with($this->equalTo('processed'))
-                                ->will($this->returnValue('transform[processed]'));
-
-        $field->submit('');
-
-        $this->assertSame('processed', $field->getData());
-        $this->assertEquals('transform[processed]', $field->getDisplayedData());
-    }
-
-    public function testSubmittedValuesAreTransformedCorrectlyIfEmpty_processDataReturnsNull()
-    {
-        $transformer = $this->createMockTransformer();
-
-        $field = $this->factory->getField('title', array(
-            'value_transformer' => $transformer,
+        $filter = new FixedFilter(array(
+            'filterBoundDataFromClient' => array(
+                // 1. The value is converted to a string and passed to the
+                //    first filter
+                '0' => 'filter1[0]',
+            ),
+            'filterBoundData' => array(
+                // 3. The normalized value is passed to the second filter
+                'norm[filter1[0]]' => 'filter2[norm[filter1[0]]]',
+            ),
+        ));
+        $valueTransformer = new FixedValueTransformer(array(
+            // 2. The filtered value is normalized
+            'norm[filter1[0]]' => 'filter1[0]',
+            // 4a. The filtered normalized value is converted to client
+            //     representation
+            'filter2[norm[filter1[0]]]' => 'client[filter2[norm[filter1[0]]]]'
+        ));
+        $normTransformer = new FixedValueTransformer(array(
+            // 4b. The filtered normalized value is converted to app
+            //     representation
+            'app[filter2[norm[filter1[0]]]]' => 'filter2[norm[filter1[0]]]',
         ));
 
-        // 1. Empty values are converted to NULL by convention
-        $transformer->expects($this->once())
-                                ->method('reverseTransform')
-                                ->with($this->identicalTo(''))
-                                ->will($this->returnValue(null));
-
-        // 2. The processed data is NULL and therefore transformed to an empty
-        //    string by convention
-        $transformer->expects($this->once())
-                                ->method('transform')
-                                ->with($this->identicalTo(null))
-                                ->will($this->returnValue(''));
-
-        $field->submit('');
+        $this->field->appendFilter($filter);
+        $this->field->setValueTransformer($valueTransformer);
+        $this->field->setNormalizationTransformer($normTransformer);
+        $this->field->submit(0);
 
-        $this->assertSame(null, $field->getData());
-        $this->assertEquals('', $field->getDisplayedData());
+        $this->assertEquals('app[filter2[norm[filter1[0]]]]', $this->field->getData());
+        $this->assertEquals('filter2[norm[filter1[0]]]', $this->field->getNormalizedData());
+        $this->assertEquals('client[filter2[norm[filter1[0]]]]', $this->field->getDisplayedData());
     }
 
-    public function testSubmittedValuesAreTransformedCorrectlyIfEmpty_processDataReturnsNull_noValueTransformer()
+    public function testSubmittedDataIsTransformedCorrectlyIfEmpty_noValueTransformer()
     {
         $this->field->submit('');
 
@@ -312,7 +241,7 @@ class FieldTest extends TestCase
         $this->assertEquals('', $this->field->getDisplayedData());
     }
 
-    public function testValuesAreTransformedCorrectly()
+    public function testSetDataIsTransformedCorrectly()
     {
         $normTransformer = new FixedValueTransformer(array(
             null => '',
@@ -336,7 +265,7 @@ class FieldTest extends TestCase
         $this->assertEquals('transform[norm[0]]', $field->getDisplayedData());
     }
 
-    public function testSubmittedValuesAreTrimmedBeforeTransforming()
+    public function testSubmittedDataIsTrimmedBeforeTransforming()
     {
         $transformer = new FixedValueTransformer(array(
             null => '',
@@ -353,7 +282,7 @@ class FieldTest extends TestCase
         $this->assertEquals('reverse[a]', $field->getData());
     }
 
-    public function testSubmittedValuesAreNotTrimmedBeforeTransformingIfDisabled()
+    public function testSubmittedDataIsNotTrimmedBeforeTransformingIfDisabled()
     {
         $transformer = new FixedValueTransformer(array(
             null => '',

+ 56 - 0
tests/Symfony/Tests/Component/Form/Fixtures/FixedFilter.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Symfony\Tests\Component\Form\Fixtures;
+
+use Symfony\Component\Form\Filters;
+use Symfony\Component\Form\Filter\FilterInterface;
+
+class FixedFilter implements FilterInterface
+{
+    private $mapping;
+
+    public function __construct(array $mapping)
+    {
+        $this->mapping = array_merge(array(
+            'filterBoundDataFromClient' => array(),
+            'filterBoundData' => array(),
+            'filterSetData' => array(),
+        ), $mapping);
+    }
+
+    public function filterBoundDataFromClient($data)
+    {
+        if (isset($this->mapping['filterBoundDataFromClient'][$data])) {
+            return $this->mapping['filterBoundDataFromClient'][$data];
+        }
+
+        return $data;
+    }
+
+    public function filterBoundData($data)
+    {
+        if (isset($this->mapping['filterBoundData'][$data])) {
+            return $this->mapping['filterBoundData'][$data];
+        }
+
+        return $data;
+    }
+
+    public function filterSetData($data)
+    {
+        if (isset($this->mapping['filterSetData'][$data])) {
+            return $this->mapping['filterSetData'][$data];
+        }
+
+        return $data;
+    }
+
+    public function getSupportedFilters()
+    {
+        return array(
+            Filters::filterBoundDataFromClient,
+            Filters::filterBoundData,
+            Filters::filterSetData,
+        );
+    }
+}

+ 0 - 25
tests/Symfony/Tests/Component/Form/FormTest.php

@@ -490,31 +490,6 @@ class FormTest extends TestCase
         $this->assertFalse($form->hasErrors());
     }
 
-    public function testSubmitForwardsPreprocessedData()
-    {
-        $field = $this->createMockField('firstName');
-
-        $form = $this->getMock(
-            'Symfony\Component\Form\Form',
-            array('preprocessData'), // only mock preprocessData()
-            array('author', array('validator' => $this->validator))
-        );
-
-        // The data array is prepared directly after binding
-        $form->expects($this->once())
-              ->method('preprocessData')
-              ->with($this->equalTo(array('firstName' => 'Bernhard')))
-              ->will($this->returnValue(array('firstName' => 'preprocessed[Bernhard]')));
-        $form->add($field);
-
-        // The preprocessed data is then forwarded to the fields
-        $field->expects($this->once())
-                    ->method('submit')
-                    ->with($this->equalTo('preprocessed[Bernhard]'));
-
-        $form->submit(array('firstName' => 'Bernhard'));
-    }
-
     public function testSubmitForwardsNullIfValueIsMissing()
     {
         $field = $this->createMockField('firstName');