[Form] Added EventListener implementation and moved CollectionField to factory
This commit is contained in:
parent
528ef55da6
commit
9eff64dd54
@ -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;
|
||||
}
|
||||
}
|
@ -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
src/Symfony/Component/Form/EventListener/EventManager.php
Normal file
46
src/Symfony/Component/Form/EventListener/EventManager.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
src/Symfony/Component/Form/Events.php
Normal file
33
src/Symfony/Component/Form/Events.php
Normal 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,
|
||||
);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
));
|
||||
|
Reference in New Issue
Block a user