[Form] Added EventListener implementation and moved CollectionField to factory

This commit is contained in:
Bernhard Schussek 2011-03-01 15:18:55 +01:00
parent 528ef55da6
commit 9eff64dd54
8 changed files with 256 additions and 90 deletions

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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,
);
}

View File

@ -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);
}
/**

View File

@ -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;
}
}

View File

@ -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,
));