ソースを参照

[Form] Added EventListener implementation and moved CollectionField to factory

Bernhard Schussek 14 年 前
コミット
9eff64dd54

+ 0 - 75
src/Symfony/Component/Form/CollectionField.php

@@ -74,79 +74,4 @@ class CollectionField extends Form
 
         parent::configure();
     }
-
-    public function setData($collection)
-    {
-        if (null === $collection) {
-            $collection = array();
-        }
-
-        if (!is_array($collection) && !$collection instanceof \Traversable) {
-            throw new UnexpectedTypeException($collection, 'array or \Traversable');
-        }
-
-        foreach ($this as $name => $field) {
-            if (!$this->modifiable || '$$key$$' != $name) {
-                $this->remove($name);
-            }
-        }
-
-        foreach ($collection as $name => $value) {
-            $this->add($this->newField($name, $name));
-        }
-
-        parent::setData($collection);
-    }
-
-    public function submit($data)
-    {
-        $this->removedFields = array();
-
-        if (null === $data) {
-            $data = array();
-        }
-
-        foreach ($this as $name => $field) {
-            if (!isset($data[$name]) && $this->modifiable && '$$key$$' != $name) {
-                $this->remove($name);
-                $this->removedFields[] = $name;
-            }
-        }
-
-        foreach ($data as $name => $value) {
-            if (!isset($this[$name]) && $this->modifiable) {
-                $this->add($this->newField($name, $name));
-            }
-        }
-
-        parent::submit($data);
-    }
-
-    protected function writeObject(&$objectOrArray)
-    {
-        parent::writeObject($objectOrArray);
-
-        foreach ($this->removedFields as $name) {
-            unset($objectOrArray[$name]);
-        }
-    }
-
-    protected function newField($key, $propertyPath)
-    {
-        if (null !== $propertyPath) {
-            $propertyPath = '['.$propertyPath.']';
-        }
-
-        if ($this->prototype) {
-            $field = clone $this->prototype;
-            $field->setKey($key);
-            $field->setPropertyPath($propertyPath);
-        } else {
-            $field = new TextField($key, array(
-                'property_path' => $propertyPath,
-            ));
-        }
-
-        return $field;
-    }
 }

+ 17 - 0
src/Symfony/Component/Form/EventListener/EventListenerInterface.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\EventListener;
+
+interface EventListenerInterface
+{
+    function getSupportedEvents();
+}

+ 46 - 0
src/Symfony/Component/Form/EventListener/EventManager.php

@@ -0,0 +1,46 @@
+<?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\EventListener;
+
+class EventManager
+{
+    private $listeners = array();
+
+    private $supportedEvents = array();
+
+    public function __construct(array $supportedEvents)
+    {
+        $this->supportedEvents = $supportedEvents;
+    }
+
+    public function addEventListener(EventListenerInterface $listener)
+    {
+        foreach ((array)$listener->getSupportedEvents() as $event) {
+            // TODO check whether the listener has the $event method
+
+            if (!isset($this->listeners[$event])) {
+                $this->listeners[$event] = array();
+            }
+
+            $this->listeners[$event][] = $listener;
+        }
+    }
+
+    public function triggerEvent($event, $data = null)
+    {
+        if (isset($this->listeners[$event])) {
+            foreach ($this->listeners[$event] as $listener) {
+                $listener->$event($data);
+            }
+        }
+    }
+}

+ 93 - 0
src/Symfony/Component/Form/EventListener/ResizeFormListener.php

@@ -0,0 +1,93 @@
+<?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\EventListener;
+
+use Symfony\Component\Form\Events;
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\Form\FieldInterface;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+
+class ResizeFormListener implements EventListenerInterface
+{
+    private $form;
+
+    private $prototype;
+
+    private $resizeOnBind;
+
+    public function __construct(FormInterface $form, FieldInterface $prototype, $resizeOnBind = false)
+    {
+        $this->form = $form;
+        $this->prototype = $prototype;
+        $this->resizeOnBind = $resizeOnBind;
+    }
+
+    public function getSupportedEvents()
+    {
+        return array(
+            Events::preSetData,
+            Events::preBind,
+        );
+    }
+
+    public function preSetData($collection)
+    {
+        if (null === $collection) {
+            $collection = array();
+        }
+
+        if (!is_array($collection) && !$collection instanceof \Traversable) {
+            throw new UnexpectedTypeException($collection, 'array or \Traversable');
+        }
+
+        foreach ($this->form as $name => $field) {
+            if (!$this->resizeOnBind || '$$key$$' != $name) {
+                $this->form->remove($name);
+            }
+        }
+
+        foreach ($collection as $name => $value) {
+            $this->form->add($this->newField($name));
+        }
+    }
+
+    public function preBind($data)
+    {
+        $this->removedFields = array();
+
+        if (null === $data) {
+            $data = array();
+        }
+
+        foreach ($this->form as $name => $field) {
+            if (!isset($data[$name]) && $this->resizeOnBind && '$$key$$' != $name) {
+                $this->form->remove($name);
+                $this->removedFields[] = $name;
+            }
+        }
+
+        foreach ($data as $name => $value) {
+            if (!$this->form->has($name) && $this->resizeOnBind) {
+                $this->form->add($this->newField($name));
+            }
+        }
+    }
+
+    protected function newField($key)
+    {
+        $field = clone $this->prototype;
+        $field->setKey($key);
+        $field->setPropertyPath('['.$key.']');
+
+        return $field;
+    }
+}

+ 33 - 0
src/Symfony/Component/Form/Events.php

@@ -0,0 +1,33 @@
+<?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 Events
+{
+    const preBind = 'preBind';
+
+    const postBind = 'postBind';
+
+    const preSetData = 'preSetData';
+
+    const postSetData = 'postSetData';
+
+    public static $all = array(
+        self::preBind,
+        self::postBind,
+        self::preSetData,
+        self::postSetData,
+    );
+}

+ 24 - 4
src/Symfony/Component/Form/Field.php

@@ -15,9 +15,10 @@ use Symfony\Component\Form\ValueTransformer\ValueTransformerInterface;
 use Symfony\Component\Form\ValueTransformer\TransformationFailedException;
 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;
+use Symfony\Component\Form\EventListener\EventManager;
+use Symfony\Component\Form\EventListener\EventListenerInterface;
 
 /**
  * Base class for form fields
@@ -70,12 +71,14 @@ class Field implements FieldInterface
     private $trim = true;
     private $disabled = false;
     private $filterChain;
+    private $eventManager;
 
     public function __construct($key = null)
     {
         $this->key = (string)$key;
         // TODO should be injected instead
         $this->filterChain = new FilterChain(Filters::$all);
+        $this->eventManager = new EventManager(Events::$all);
     }
 
     /**
@@ -98,6 +101,16 @@ class Field implements FieldInterface
         return $this;
     }
 
+    /**
+     * @deprecated
+     */
+    public function addEventListener(EventListenerInterface $listener)
+    {
+        $this->eventManager->addEventListener($listener);
+
+        return $this;
+    }
+
     /**
      * Clones this field.
      */
@@ -268,6 +281,8 @@ class Field implements FieldInterface
      */
     public function setData($appData)
     {
+        $this->eventManager->triggerEvent(Events::preSetData, $appData);
+
         // Hook to change content of the data
         $appData = $this->filterChain->filter(Filters::filterSetData, $appData);
 
@@ -284,6 +299,8 @@ class Field implements FieldInterface
         $this->normalizedData = $normData;
         $this->transformedData = $clientData;
 
+        $this->eventManager->triggerEvent(Events::postSetData);
+
         return $this;
     }
 
@@ -294,13 +311,12 @@ class Field implements FieldInterface
      */
     public function submit($clientData)
     {
-        $this->submitted = true;
-        $this->errors = array();
-
         if (is_scalar($clientData) || null === $clientData) {
             $clientData = (string)$clientData;
         }
 
+        $this->eventManager->triggerEvent(Events::preBind, $clientData);
+
         if (is_string($clientData) && $this->trim) {
             $clientData = trim($clientData);
         }
@@ -329,9 +345,13 @@ class Field implements FieldInterface
             $clientData = $this->transform($normData);
         }
 
+        $this->submitted = true;
+        $this->errors = array();
         $this->data = $appData;
         $this->normalizedData = $normData;
         $this->transformedData = $clientData;
+
+        $this->eventManager->triggerEvent(Events::postBind);
     }
 
     /**

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

@@ -18,6 +18,7 @@ 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\EventListener\ResizeFormListener;
 use Symfony\Component\Form\Filter\RadioInputFilter;
 use Symfony\Component\Form\Filter\FixUrlProtocolFilter;
 use Symfony\Component\Form\Filter\FileUploadFilter;
@@ -908,4 +909,33 @@ class FormFactory
             ->add($this->getHiddenField('token'))
             ->add($this->getHiddenField('name'));
     }
+
+    public function getCollectionField($key, array $options = array())
+    {
+        $options = array_merge(array(
+            'template' => 'collection',
+            'prototype' => null,
+            'modifiable' => false,
+        ), $options);
+
+        $field = $this->getForm($key, $options);
+
+        if (!isset($options['prototype'])) {
+            $options['prototype'] = $this->getTextField('prototype');
+        }
+
+        if ($options['modifiable']) {
+            $child = clone $options['prototype'];
+            $child->setKey('$$key$$');
+            $child->setPropertyPath(null);
+            // TESTME
+            $child->setRequired(false);
+            $field->add($child);
+        }
+
+        $field->addEventListener(new ResizeFormListener($field,
+                $options['prototype'], $options['modifiable']));
+
+        return $field;
+    }
 }

+ 13 - 11
tests/Symfony/Tests/Component/Form/CollectionFieldTest.php

@@ -11,15 +11,17 @@
 
 namespace Symfony\Tests\Component\Form;
 
+require_once __DIR__.'/TestCase.php';
+
 use Symfony\Component\Form\CollectionField;
 use Symfony\Component\Form\Form;
 use Symfony\Component\Form\Field;
 
-class CollectionFieldTest extends \PHPUnit_Framework_TestCase
+class CollectionFieldTest extends TestCase
 {
     public function testContainsNoFieldsByDefault()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
         ));
         $this->assertEquals(0, count($field));
@@ -27,7 +29,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testSetDataAdjustsSize()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
         ));
         $field->setData(array('foo@foo.com', 'foo@bar.com'));
@@ -47,7 +49,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testSetDataAdjustsSizeIfModifiable()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
             'modifiable' => true,
         ));
@@ -67,7 +69,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testThrowsExceptionIfObjectIsNotTraversable()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
         ));
         $this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
@@ -76,7 +78,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testModifiableCollectionsContainExtraField()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
             'modifiable' => true,
         ));
@@ -89,7 +91,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testNotResizedIfSubmittedWithMissingData()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
         ));
         $field->setData(array('foo@foo.com', 'bar@bar.com'));
@@ -103,7 +105,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testResizedIfSubmittedWithMissingDataAndModifiable()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
             'modifiable' => true,
         ));
@@ -117,7 +119,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testNotResizedIfSubmittedWithExtraData()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
         ));
         $field->setData(array('foo@bar.com'));
@@ -130,7 +132,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testResizedUpIfSubmittedWithExtraDataAndModifiable()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
             'modifiable' => true,
         ));
@@ -146,7 +148,7 @@ class CollectionFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testResizedDownIfSubmittedWithLessDataAndModifiable()
     {
-        $field = new CollectionField('emails', array(
+        $field = $this->factory->getCollectionField('emails', array(
             'prototype' => new Field(),
             'modifiable' => true,
         ));