[Form] Merged classes FieldGroup and Form for simplicity
This commit is contained in:
parent
7680657944
commit
c468db5c5b
@ -13,7 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Templating\Helper;
|
||||
|
||||
use Symfony\Component\Templating\Helper\Helper;
|
||||
use Symfony\Component\Form\FieldInterface;
|
||||
use Symfony\Component\Form\FieldGroupInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
|
||||
|
||||
/**
|
||||
@ -148,14 +148,14 @@ class FormHelper extends Helper
|
||||
));
|
||||
}
|
||||
|
||||
public function hidden(/*FieldGroupInterface */$group, array $parameters = array(), $template = null)
|
||||
public function hidden(/*FormInterface */$form, array $parameters = array(), $template = null)
|
||||
{
|
||||
if (null === $template) {
|
||||
$template = 'FrameworkBundle:Form:hidden.html.php';
|
||||
}
|
||||
|
||||
return $this->engine->render($template, array(
|
||||
'field' => $group,
|
||||
'field' => $form,
|
||||
'params' => $parameters,
|
||||
));
|
||||
}
|
||||
@ -185,8 +185,8 @@ class FormHelper extends Helper
|
||||
$currentFqClassName = get_parent_class($currentFqClassName);
|
||||
} while (null === $template && false !== $currentFqClassName);
|
||||
|
||||
if (null === $template && $field instanceof FieldGroupInterface) {
|
||||
$template = 'FrameworkBundle:Form:field_group.html.php';
|
||||
if (null === $template && $field instanceof FormInterface) {
|
||||
$template = 'FrameworkBundle:Form:form.html.php';
|
||||
}
|
||||
|
||||
self::$cache[$fqClassName] = $template;
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Bundle\TwigBundle\Extension;
|
||||
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\FieldGroupInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FieldInterface;
|
||||
use Symfony\Component\Form\CollectionField;
|
||||
use Symfony\Component\Form\HybridField;
|
||||
@ -47,7 +47,7 @@ class FormExtension extends \Twig_Extension
|
||||
$this->environment = $environment;
|
||||
}
|
||||
|
||||
public function setTheme(FieldGroupInterface $group, array $resources)
|
||||
public function setTheme(FormInterface $group, array $resources)
|
||||
{
|
||||
$this->themes->attach($group, $resources);
|
||||
}
|
||||
@ -159,11 +159,11 @@ class FormExtension extends \Twig_Extension
|
||||
/**
|
||||
* Renders all hidden fields of the given field group
|
||||
*
|
||||
* @param FieldGroupInterface $group The field group
|
||||
* @param FormInterface $group The field group
|
||||
* @param array $params Additional variables passed to the
|
||||
* template
|
||||
*/
|
||||
public function renderHidden(FieldGroupInterface $group, array $parameters = array())
|
||||
public function renderHidden(FormInterface $group, array $parameters = array())
|
||||
{
|
||||
if (null === $this->templates) {
|
||||
$this->templates = $this->resolveResources($this->resources);
|
||||
|
@ -9,7 +9,7 @@
|
||||
{% endspaceless %}
|
||||
{% endblock field_row %}
|
||||
|
||||
{% block field_group %}
|
||||
{% block form %}
|
||||
{% spaceless %}
|
||||
{{ form_errors(field) }}
|
||||
{% for child in field.visibleFields %}
|
||||
@ -17,7 +17,7 @@
|
||||
{% endfor %}
|
||||
{{ form_hidden(field) }}
|
||||
{% endspaceless %}
|
||||
{% endblock field_group %}
|
||||
{% endblock form %}
|
||||
|
||||
{% block errors %}
|
||||
{% spaceless %}
|
||||
|
@ -24,7 +24,7 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
*
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class CollectionField extends FieldGroup
|
||||
class CollectionField extends Form
|
||||
{
|
||||
/**
|
||||
* The prototype for the inner fields
|
||||
|
@ -46,7 +46,7 @@ use Symfony\Component\Form\ValueTransformer\ValueTransformerChain;
|
||||
*
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class DateTimeField extends FieldGroup
|
||||
class DateTimeField extends Form
|
||||
{
|
||||
const DATETIME = 'datetime';
|
||||
const STRING = 'string';
|
||||
|
@ -1,561 +0,0 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Form\Exception\AlreadyBoundException;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Form\Exception\DanglingFieldException;
|
||||
use Symfony\Component\Form\Exception\FieldDefinitionException;
|
||||
|
||||
/**
|
||||
* FieldGroup represents an array of widgets bind to names and values.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterface
|
||||
{
|
||||
/**
|
||||
* Contains all the fields of this group
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* Contains the names of bound values who don't belong to any fields
|
||||
* @var array
|
||||
*/
|
||||
protected $extraFields = array();
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function __construct($key = null, array $options = array())
|
||||
{
|
||||
$this->addOption('virtual', false);
|
||||
|
||||
parent::__construct($key, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones this group
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->fields as $name => $field) {
|
||||
$field = clone $field;
|
||||
// this condition is only to "bypass" a PHPUnit bug with mocks
|
||||
if (null !== $field->getParent()) {
|
||||
$field->setParent($this);
|
||||
}
|
||||
$this->fields[$name] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new field to this group. A field must have a unique name within
|
||||
* the group. Otherwise the existing field is overwritten.
|
||||
*
|
||||
* If you add a nested group, this group should also be represented in the
|
||||
* object hierarchy. If you want to add a group that operates on the same
|
||||
* hierarchy level, use merge().
|
||||
*
|
||||
* <code>
|
||||
* class Entity
|
||||
* {
|
||||
* public $location;
|
||||
* }
|
||||
*
|
||||
* class Location
|
||||
* {
|
||||
* public $longitude;
|
||||
* public $latitude;
|
||||
* }
|
||||
*
|
||||
* $entity = new Entity();
|
||||
* $entity->location = new Location();
|
||||
*
|
||||
* $form = new Form('entity', $entity, $validator);
|
||||
*
|
||||
* $locationGroup = new FieldGroup('location');
|
||||
* $locationGroup->add(new TextField('longitude'));
|
||||
* $locationGroup->add(new TextField('latitude'));
|
||||
*
|
||||
* $form->add($locationGroup);
|
||||
* </code>
|
||||
*
|
||||
* @param FieldInterface|string $field
|
||||
* @return FieldInterface
|
||||
*/
|
||||
public function add($field)
|
||||
{
|
||||
if ($this->isBound()) {
|
||||
throw new AlreadyBoundException('You cannot add fields after binding a form');
|
||||
}
|
||||
|
||||
// if the field is given as string, ask the field factory of the form
|
||||
// to create a field
|
||||
if (!$field instanceof FieldInterface) {
|
||||
if (!is_string($field)) {
|
||||
throw new UnexpectedTypeException($field, 'FieldInterface or string');
|
||||
}
|
||||
|
||||
if (!$this->getRoot() instanceof Form) {
|
||||
throw new DanglingFieldException('Field groups must be added to a form before fields can be created automatically');
|
||||
}
|
||||
|
||||
$factory = $this->getRoot()->getFieldFactory();
|
||||
|
||||
if (!$factory) {
|
||||
throw new \LogicException('A field factory must be available to automatically create fields');
|
||||
}
|
||||
|
||||
$options = func_num_args() > 1 ? func_get_arg(1) : array();
|
||||
$field = $factory->getInstance($this->getData(), $field, $options);
|
||||
}
|
||||
|
||||
if ('' === $field->getKey() || null === $field->getKey()) {
|
||||
throw new FieldDefinitionException('You cannot add anonymous fields');
|
||||
}
|
||||
|
||||
$this->fields[$field->getKey()] = $field;
|
||||
|
||||
$field->setParent($this);
|
||||
|
||||
$data = $this->getTransformedData();
|
||||
|
||||
// if the property "data" is NULL, getTransformedData() returns an empty
|
||||
// string
|
||||
if (!empty($data)) {
|
||||
$field->updateFromProperty($data);
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the field with the given key.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function remove($key)
|
||||
{
|
||||
$this->fields[$key]->setParent(null);
|
||||
|
||||
unset($this->fields[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a field with the given key exists.
|
||||
*
|
||||
* @param string $key
|
||||
* @return Boolean
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
return isset($this->fields[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field with the given key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return FieldInterface
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
if (isset($this->fields[$key])) {
|
||||
return $this->fields[$key];
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all fields in this group
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of visible fields from the current schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getVisibleFields()
|
||||
{
|
||||
return $this->getFieldsByVisibility(false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of visible fields from the current schema.
|
||||
*
|
||||
* This variant of the method will recursively get all the
|
||||
* fields from the nested forms or field groups
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllVisibleFields()
|
||||
{
|
||||
return $this->getFieldsByVisibility(false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of hidden fields from the current schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHiddenFields()
|
||||
{
|
||||
return $this->getFieldsByVisibility(true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of hidden fields from the current schema.
|
||||
*
|
||||
* This variant of the method will recursively get all the
|
||||
* fields from the nested forms or field groups
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllHiddenFields()
|
||||
{
|
||||
return $this->getFieldsByVisibility(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filtered array of fields from the current schema.
|
||||
*
|
||||
* @param Boolean $hidden Whether to return hidden fields only or visible fields only
|
||||
* @param Boolean $recursive Whether to recur through embedded schemas
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFieldsByVisibility($hidden, $recursive)
|
||||
{
|
||||
$fields = array();
|
||||
$hidden = (Boolean)$hidden;
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
if ($field instanceof FieldGroup && $recursive) {
|
||||
$fields = array_merge($fields, $field->getFieldsByVisibility($hidden, $recursive));
|
||||
} else if ($hidden === $field->isHidden()) {
|
||||
$fields[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the field group with an object to operate on
|
||||
*
|
||||
* @see FieldInterface
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
parent::setData($data);
|
||||
|
||||
// get transformed data and pass its values to child fields
|
||||
$data = $this->getTransformedData();
|
||||
|
||||
if (!empty($data) && !is_array($data) && !is_object($data)) {
|
||||
throw new \InvalidArgumentException(sprintf('Expected argument of type object or array, %s given', gettype($data)));
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
$this->updateFromObject($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of the field as it is displayed to the user.
|
||||
*
|
||||
* @see FieldInterface
|
||||
* @return array of field name => value
|
||||
*/
|
||||
public function getDisplayedData()
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($this->fields as $key => $field) {
|
||||
$values[$key] = $field->getDisplayedData();
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds POST data to the field, transforms and validates it.
|
||||
*
|
||||
* @param string|array $taintedData The POST data
|
||||
*/
|
||||
public function bind($taintedData)
|
||||
{
|
||||
if (null === $taintedData) {
|
||||
$taintedData = array();
|
||||
}
|
||||
|
||||
if (!is_array($taintedData)) {
|
||||
throw new UnexpectedTypeException($taintedData, 'array');
|
||||
}
|
||||
|
||||
foreach ($this->fields as $key => $field) {
|
||||
if (!isset($taintedData[$key])) {
|
||||
$taintedData[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$taintedData = $this->preprocessData($taintedData);
|
||||
|
||||
foreach ($taintedData as $key => $value) {
|
||||
if ($this->has($key)) {
|
||||
$this->fields[$key]->bind($value);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->getTransformedData();
|
||||
|
||||
$this->updateObject($data);
|
||||
|
||||
// bind and reverse transform the data
|
||||
parent::bind($data);
|
||||
|
||||
$this->extraFields = array();
|
||||
|
||||
foreach ($taintedData as $key => $value) {
|
||||
if (!$this->has($key)) {
|
||||
$this->extraFields[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the child fields from the properties of the given data
|
||||
*
|
||||
* This method calls updateFromProperty() on all child fields that have a
|
||||
* property path set. If a child field has no property path set but
|
||||
* implements FieldGroupInterface, updateProperty() is called on its
|
||||
* children instead.
|
||||
*
|
||||
* @param array|object $objectOrArray
|
||||
*/
|
||||
protected function updateFromObject(&$objectOrArray)
|
||||
{
|
||||
$iterator = new RecursiveFieldIterator($this);
|
||||
$iterator = new \RecursiveIteratorIterator($iterator);
|
||||
|
||||
foreach ($iterator as $field) {
|
||||
$field->updateFromProperty($objectOrArray);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all properties of the given data from the child fields
|
||||
*
|
||||
* This method calls updateProperty() on all child fields that have a property
|
||||
* path set. If a child field has no property path set but implements
|
||||
* FieldGroupInterface, updateProperty() is called on its children instead.
|
||||
*
|
||||
* @param array|object $objectOrArray
|
||||
*/
|
||||
protected function updateObject(&$objectOrArray)
|
||||
{
|
||||
$iterator = new RecursiveFieldIterator($this);
|
||||
$iterator = new \RecursiveIteratorIterator($iterator);
|
||||
|
||||
foreach ($iterator as $field) {
|
||||
$field->updateProperty($objectOrArray);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the bound data before it is passed to the individual fields
|
||||
*
|
||||
* The data is in the user format.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function preprocessData(array $data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isVirtual()
|
||||
{
|
||||
return $this->getOption('virtual');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this form was bound with extra fields
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isBoundWithExtraFields()
|
||||
{
|
||||
// TODO: integrate the field names in the error message
|
||||
return count($this->extraFields) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the field is valid.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
if (!parent::isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
if (!$field->isValid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function addError(FieldError $error, PropertyPathIterator $pathIterator = null, $type = null)
|
||||
{
|
||||
if (null !== $pathIterator) {
|
||||
if ($type === self::FIELD_ERROR && $pathIterator->hasNext()) {
|
||||
$pathIterator->next();
|
||||
|
||||
if ($pathIterator->isProperty() && $pathIterator->current() === 'fields') {
|
||||
$pathIterator->next();
|
||||
}
|
||||
|
||||
if ($this->has($pathIterator->current()) && !$this->get($pathIterator->current())->isHidden()) {
|
||||
$this->get($pathIterator->current())->addError($error, $pathIterator, $type);
|
||||
|
||||
return;
|
||||
}
|
||||
} else if ($type === self::DATA_ERROR) {
|
||||
$iterator = new RecursiveFieldIterator($this);
|
||||
$iterator = new \RecursiveIteratorIterator($iterator);
|
||||
|
||||
foreach ($iterator as $field) {
|
||||
if (null !== ($fieldPath = $field->getPropertyPath())) {
|
||||
if ($fieldPath->getElement(0) === $pathIterator->current() && !$field->isHidden()) {
|
||||
if ($pathIterator->hasNext()) {
|
||||
$pathIterator->next();
|
||||
}
|
||||
|
||||
$field->addError($error, $pathIterator, $type);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent::addError($error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the field requires a multipart form.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isMultipart()
|
||||
{
|
||||
foreach ($this->fields as $field) {
|
||||
if ($field->isMultipart()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the bound field exists (implements the \ArrayAccess interface).
|
||||
*
|
||||
* @param string $key The key of the bound field
|
||||
*
|
||||
* @return Boolean true if the widget exists, false otherwise
|
||||
*/
|
||||
public function offsetExists($key)
|
||||
{
|
||||
return $this->has($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the form field associated with the name (implements the \ArrayAccess interface).
|
||||
*
|
||||
* @param string $key The offset of the value to get
|
||||
*
|
||||
* @return Field A form field instance
|
||||
*/
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception saying that values cannot be set (implements the \ArrayAccess interface).
|
||||
*
|
||||
* @param string $offset (ignored)
|
||||
* @param string $value (ignored)
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function offsetSet($key, $field)
|
||||
{
|
||||
throw new \LogicException('Use the method add() to add fields');
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception saying that values cannot be unset (implements the \ArrayAccess interface).
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function offsetUnset($key)
|
||||
{
|
||||
return $this->remove($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the iterator for this group.
|
||||
*
|
||||
* @return \ArrayIterator
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of form fields (implements the \Countable interface).
|
||||
*
|
||||
* @return integer The number of embedded form fields
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->fields);
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ use Symfony\Component\Form\Exception\FormException;
|
||||
/**
|
||||
* A file field to upload files.
|
||||
*/
|
||||
class FileField extends FieldGroup
|
||||
class FileField extends Form
|
||||
{
|
||||
/**
|
||||
* Whether the size of the uploaded file exceeds the upload_max_filesize
|
||||
@ -61,7 +61,7 @@ class FileField extends FieldGroup
|
||||
* This way the file can survive if the form does not validate and is
|
||||
* resubmitted.
|
||||
*
|
||||
* @see Symfony\Component\Form\FieldGroup::preprocessData()
|
||||
* @see Symfony\Component\Form\Form::preprocessData()
|
||||
*/
|
||||
protected function preprocessData(array $data)
|
||||
{
|
||||
|
@ -15,6 +15,10 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\FileBag;
|
||||
use Symfony\Component\Validator\ValidatorInterface;
|
||||
use Symfony\Component\Form\Exception\FormException;
|
||||
use Symfony\Component\Form\Exception\AlreadyBoundException;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Form\Exception\DanglingFieldException;
|
||||
use Symfony\Component\Form\Exception\FieldDefinitionException;
|
||||
use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
|
||||
|
||||
/**
|
||||
@ -32,13 +36,19 @@ use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class Form extends FieldGroup
|
||||
class Form extends Field implements \IteratorAggregate, FormInterface
|
||||
{
|
||||
/**
|
||||
* The validator to validate form values
|
||||
* @var ValidatorInterface
|
||||
* Contains all the fields of this group
|
||||
* @var array
|
||||
*/
|
||||
protected $validator = null;
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* Contains the names of bound values who don't belong to any fields
|
||||
* @var array
|
||||
*/
|
||||
protected $extraFields = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -48,19 +58,14 @@ class Form extends FieldGroup
|
||||
* @param ValidatorInterface $validator
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($name = null, $data = null, ValidatorInterface $validator = null, array $options = array())
|
||||
public function __construct($name = null, array $options = array())
|
||||
{
|
||||
$this->validator = $validator;
|
||||
|
||||
// Prefill the form with the given data
|
||||
if (null !== $data) {
|
||||
$this->setData($data);
|
||||
}
|
||||
|
||||
$this->addOption('csrf_field_name', '_token');
|
||||
$this->addOption('csrf_provider');
|
||||
$this->addOption('field_factory');
|
||||
$this->addOption('validation_groups');
|
||||
$this->addOption('virtual', false);
|
||||
$this->addOption('validator');
|
||||
|
||||
if (isset($options['validation_groups'])) {
|
||||
$options['validation_groups'] = (array)$options['validation_groups'];
|
||||
@ -70,11 +75,12 @@ class Form extends FieldGroup
|
||||
|
||||
// If data is passed to this constructor, objects from parent forms
|
||||
// should be ignored
|
||||
if (null !== $data) {
|
||||
$this->setPropertyPath(null);
|
||||
}
|
||||
// if (null !== $data) {
|
||||
// $this->setPropertyPath(null);
|
||||
// }
|
||||
|
||||
// Enable CSRF protection, if necessary
|
||||
// TODO only in root form
|
||||
if ($this->getOption('csrf_provider')) {
|
||||
if (!$this->getOption('csrf_provider') instanceof CsrfProviderInterface) {
|
||||
throw new FormException('The object passed to the "csrf_provider" option must implement CsrfProviderInterface');
|
||||
@ -91,6 +97,539 @@ class Form extends FieldGroup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones this group
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->fields as $name => $field) {
|
||||
$field = clone $field;
|
||||
// this condition is only to "bypass" a PHPUnit bug with mocks
|
||||
if (null !== $field->getParent()) {
|
||||
$field->setParent($this);
|
||||
}
|
||||
$this->fields[$name] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new field to this group. A field must have a unique name within
|
||||
* the group. Otherwise the existing field is overwritten.
|
||||
*
|
||||
* If you add a nested group, this group should also be represented in the
|
||||
* object hierarchy. If you want to add a group that operates on the same
|
||||
* hierarchy level, use merge().
|
||||
*
|
||||
* <code>
|
||||
* class Entity
|
||||
* {
|
||||
* public $location;
|
||||
* }
|
||||
*
|
||||
* class Location
|
||||
* {
|
||||
* public $longitude;
|
||||
* public $latitude;
|
||||
* }
|
||||
*
|
||||
* $entity = new Entity();
|
||||
* $entity->location = new Location();
|
||||
*
|
||||
* $form = new Form('entity', $entity, $validator);
|
||||
*
|
||||
* $locationGroup = new Form('location');
|
||||
* $locationGroup->add(new TextField('longitude'));
|
||||
* $locationGroup->add(new TextField('latitude'));
|
||||
*
|
||||
* $form->add($locationGroup);
|
||||
* </code>
|
||||
*
|
||||
* @param FieldInterface|string $field
|
||||
* @return FieldInterface
|
||||
*/
|
||||
public function add($field)
|
||||
{
|
||||
if ($this->isBound()) {
|
||||
throw new AlreadyBoundException('You cannot add fields after binding a form');
|
||||
}
|
||||
|
||||
// if the field is given as string, ask the field factory of the form
|
||||
// to create a field
|
||||
if (!$field instanceof FieldInterface) {
|
||||
if (!is_string($field)) {
|
||||
throw new UnexpectedTypeException($field, 'FieldInterface or string');
|
||||
}
|
||||
|
||||
$factory = $this->getRoot()->getFieldFactory();
|
||||
|
||||
if (!$factory) {
|
||||
throw new \LogicException('A field factory must be available to automatically create fields');
|
||||
}
|
||||
|
||||
$options = func_num_args() > 1 ? func_get_arg(1) : array();
|
||||
$field = $factory->getInstance($this->getData(), $field, $options);
|
||||
}
|
||||
|
||||
if ('' === $field->getKey() || null === $field->getKey()) {
|
||||
throw new FieldDefinitionException('You cannot add anonymous fields');
|
||||
}
|
||||
|
||||
$this->fields[$field->getKey()] = $field;
|
||||
|
||||
$field->setParent($this);
|
||||
|
||||
$data = $this->getTransformedData();
|
||||
|
||||
// if the property "data" is NULL, getTransformedData() returns an empty
|
||||
// string
|
||||
if (!empty($data)) {
|
||||
$field->updateFromProperty($data);
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the field with the given key.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function remove($key)
|
||||
{
|
||||
$this->fields[$key]->setParent(null);
|
||||
|
||||
unset($this->fields[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a field with the given key exists.
|
||||
*
|
||||
* @param string $key
|
||||
* @return Boolean
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
return isset($this->fields[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field with the given key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return FieldInterface
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
if (isset($this->fields[$key])) {
|
||||
return $this->fields[$key];
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all fields in this group
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of visible fields from the current schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getVisibleFields()
|
||||
{
|
||||
return $this->getFieldsByVisibility(false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of visible fields from the current schema.
|
||||
*
|
||||
* This variant of the method will recursively get all the
|
||||
* fields from the nested forms or field groups
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllVisibleFields()
|
||||
{
|
||||
return $this->getFieldsByVisibility(false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of hidden fields from the current schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHiddenFields()
|
||||
{
|
||||
return $this->getFieldsByVisibility(true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of hidden fields from the current schema.
|
||||
*
|
||||
* This variant of the method will recursively get all the
|
||||
* fields from the nested forms or field groups
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllHiddenFields()
|
||||
{
|
||||
return $this->getFieldsByVisibility(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filtered array of fields from the current schema.
|
||||
*
|
||||
* @param Boolean $hidden Whether to return hidden fields only or visible fields only
|
||||
* @param Boolean $recursive Whether to recur through embedded schemas
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFieldsByVisibility($hidden, $recursive)
|
||||
{
|
||||
$fields = array();
|
||||
$hidden = (Boolean)$hidden;
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
if ($field instanceof Form && $recursive) {
|
||||
$fields = array_merge($fields, $field->getFieldsByVisibility($hidden, $recursive));
|
||||
} else if ($hidden === $field->isHidden()) {
|
||||
$fields[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the field group with an object to operate on
|
||||
*
|
||||
* @see FieldInterface
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
parent::setData($data);
|
||||
|
||||
// get transformed data and pass its values to child fields
|
||||
$data = $this->getTransformedData();
|
||||
|
||||
if (!empty($data) && !is_array($data) && !is_object($data)) {
|
||||
throw new \InvalidArgumentException(sprintf('Expected argument of type object or array, %s given', gettype($data)));
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
$this->updateFromObject($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of the field as it is displayed to the user.
|
||||
*
|
||||
* @see FieldInterface
|
||||
* @return array of field name => value
|
||||
*/
|
||||
public function getDisplayedData()
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($this->fields as $key => $field) {
|
||||
$values[$key] = $field->getDisplayedData();
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds POST data to the field, transforms and validates it.
|
||||
*
|
||||
* @param string|array $taintedData The POST data
|
||||
*/
|
||||
public function bind($taintedData)
|
||||
{
|
||||
if (null === $taintedData) {
|
||||
$taintedData = array();
|
||||
}
|
||||
|
||||
if (!is_array($taintedData)) {
|
||||
throw new UnexpectedTypeException($taintedData, 'array');
|
||||
}
|
||||
|
||||
foreach ($this->fields as $key => $field) {
|
||||
if (!isset($taintedData[$key])) {
|
||||
$taintedData[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$taintedData = $this->preprocessData($taintedData);
|
||||
|
||||
foreach ($taintedData as $key => $value) {
|
||||
if ($this->has($key)) {
|
||||
$this->fields[$key]->bind($value);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->getTransformedData();
|
||||
|
||||
$this->updateObject($data);
|
||||
|
||||
// bind and reverse transform the data
|
||||
parent::bind($data);
|
||||
|
||||
$this->extraFields = array();
|
||||
|
||||
foreach ($taintedData as $key => $value) {
|
||||
if (!$this->has($key)) {
|
||||
$this->extraFields[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isRoot() && null !== $this->getOption('validator')) {
|
||||
// if () {
|
||||
// throw new FormException('A validator is required for binding. Forgot to pass it to the constructor of the form?');
|
||||
// }
|
||||
|
||||
if ($violations = $this->getOption('validator')->validate($this, $this->getOption('validation_groups'))) {
|
||||
// TODO: test me
|
||||
foreach ($violations as $violation) {
|
||||
$propertyPath = new PropertyPath($violation->getPropertyPath());
|
||||
$iterator = $propertyPath->getIterator();
|
||||
|
||||
if ($iterator->current() == 'data') {
|
||||
$type = self::DATA_ERROR;
|
||||
$iterator->next(); // point at the first data element
|
||||
} else {
|
||||
$type = self::FIELD_ERROR;
|
||||
}
|
||||
|
||||
$this->addError(new FieldError($violation->getMessageTemplate(), $violation->getMessageParameters()), $iterator, $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the child fields from the properties of the given data
|
||||
*
|
||||
* This method calls updateFromProperty() on all child fields that have a
|
||||
* property path set. If a child field has no property path set but
|
||||
* implements FormInterface, updateProperty() is called on its
|
||||
* children instead.
|
||||
*
|
||||
* @param array|object $objectOrArray
|
||||
*/
|
||||
protected function updateFromObject(&$objectOrArray)
|
||||
{
|
||||
$iterator = new RecursiveFieldIterator($this);
|
||||
$iterator = new \RecursiveIteratorIterator($iterator);
|
||||
|
||||
foreach ($iterator as $field) {
|
||||
$field->updateFromProperty($objectOrArray);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all properties of the given data from the child fields
|
||||
*
|
||||
* This method calls updateProperty() on all child fields that have a property
|
||||
* path set. If a child field has no property path set but implements
|
||||
* FormInterface, updateProperty() is called on its children instead.
|
||||
*
|
||||
* @param array|object $objectOrArray
|
||||
*/
|
||||
protected function updateObject(&$objectOrArray)
|
||||
{
|
||||
$iterator = new RecursiveFieldIterator($this);
|
||||
$iterator = new \RecursiveIteratorIterator($iterator);
|
||||
|
||||
foreach ($iterator as $field) {
|
||||
$field->updateProperty($objectOrArray);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the bound data before it is passed to the individual fields
|
||||
*
|
||||
* The data is in the user format.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function preprocessData(array $data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isVirtual()
|
||||
{
|
||||
return $this->getOption('virtual');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this form was bound with extra fields
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isBoundWithExtraFields()
|
||||
{
|
||||
// TODO: integrate the field names in the error message
|
||||
return count($this->extraFields) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the field is valid.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
if (!parent::isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
if (!$field->isValid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function addError(FieldError $error, PropertyPathIterator $pathIterator = null, $type = null)
|
||||
{
|
||||
if (null !== $pathIterator) {
|
||||
if ($type === self::FIELD_ERROR && $pathIterator->hasNext()) {
|
||||
$pathIterator->next();
|
||||
|
||||
if ($pathIterator->isProperty() && $pathIterator->current() === 'fields') {
|
||||
$pathIterator->next();
|
||||
}
|
||||
|
||||
if ($this->has($pathIterator->current()) && !$this->get($pathIterator->current())->isHidden()) {
|
||||
$this->get($pathIterator->current())->addError($error, $pathIterator, $type);
|
||||
|
||||
return;
|
||||
}
|
||||
} else if ($type === self::DATA_ERROR) {
|
||||
$iterator = new RecursiveFieldIterator($this);
|
||||
$iterator = new \RecursiveIteratorIterator($iterator);
|
||||
|
||||
foreach ($iterator as $field) {
|
||||
if (null !== ($fieldPath = $field->getPropertyPath())) {
|
||||
if ($fieldPath->getElement(0) === $pathIterator->current() && !$field->isHidden()) {
|
||||
if ($pathIterator->hasNext()) {
|
||||
$pathIterator->next();
|
||||
}
|
||||
|
||||
$field->addError($error, $pathIterator, $type);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent::addError($error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the field requires a multipart form.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isMultipart()
|
||||
{
|
||||
foreach ($this->fields as $field) {
|
||||
if ($field->isMultipart()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the bound field exists (implements the \ArrayAccess interface).
|
||||
*
|
||||
* @param string $key The key of the bound field
|
||||
*
|
||||
* @return Boolean true if the widget exists, false otherwise
|
||||
*/
|
||||
public function offsetExists($key)
|
||||
{
|
||||
return $this->has($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the form field associated with the name (implements the \ArrayAccess interface).
|
||||
*
|
||||
* @param string $key The offset of the value to get
|
||||
*
|
||||
* @return Field A form field instance
|
||||
*/
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception saying that values cannot be set (implements the \ArrayAccess interface).
|
||||
*
|
||||
* @param string $offset (ignored)
|
||||
* @param string $value (ignored)
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function offsetSet($key, $field)
|
||||
{
|
||||
throw new \LogicException('Use the method add() to add fields');
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception saying that values cannot be unset (implements the \ArrayAccess interface).
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function offsetUnset($key)
|
||||
{
|
||||
return $this->remove($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the iterator for this group.
|
||||
*
|
||||
* @return \ArrayIterator
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of form fields (implements the \Countable interface).
|
||||
*
|
||||
* @return integer The number of embedded form fields
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a factory for automatically creating fields based on metadata
|
||||
* available for a form's object
|
||||
@ -173,37 +712,6 @@ class Form extends FieldGroup
|
||||
$this->bind(self::deepArrayUnion($values, $files));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function bind($values)
|
||||
{
|
||||
parent::bind($values);
|
||||
|
||||
if ($this->getParent() === null) {
|
||||
if ($this->validator === null) {
|
||||
throw new FormException('A validator is required for binding. Forgot to pass it to the constructor of the form?');
|
||||
}
|
||||
|
||||
if ($violations = $this->validator->validate($this, $this->getOption('validation_groups'))) {
|
||||
// TODO: test me
|
||||
foreach ($violations as $violation) {
|
||||
$propertyPath = new PropertyPath($violation->getPropertyPath());
|
||||
$iterator = $propertyPath->getIterator();
|
||||
|
||||
if ($iterator->current() == 'data') {
|
||||
$type = self::DATA_ERROR;
|
||||
$iterator->next(); // point at the first data element
|
||||
} else {
|
||||
$type = self::FIELD_ERROR;
|
||||
}
|
||||
|
||||
$this->addError(new FieldError($violation->getMessageTemplate(), $violation->getMessageParameters()), $iterator, $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this form is CSRF protected
|
||||
*/
|
||||
|
@ -150,17 +150,19 @@ class FormContext implements FormContextInterface
|
||||
*/
|
||||
public function getForm($name, $data = null)
|
||||
{
|
||||
return new Form(
|
||||
$form = new Form(
|
||||
$name,
|
||||
$data,
|
||||
$this->validator,
|
||||
array(
|
||||
'validator' => $this->validator,
|
||||
'csrf_field_name' => $this->csrfFieldName,
|
||||
'csrf_provider' => $this->csrfProtection ? $this->csrfProvider : null,
|
||||
'validation_groups' => $this->validationGroups,
|
||||
'field_factory' => $this->fieldFactory,
|
||||
)
|
||||
);
|
||||
$form->setData($data);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,7 +16,7 @@ namespace Symfony\Component\Form;
|
||||
*
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
interface FieldGroupInterface extends FieldInterface, \ArrayAccess, \Traversable, \Countable
|
||||
interface FormInterface extends FieldInterface, \ArrayAccess, \Traversable, \Countable
|
||||
{
|
||||
/**
|
||||
* Returns whether this field group is virtual
|
||||
@ -27,7 +27,7 @@ interface FieldGroupInterface extends FieldInterface, \ArrayAccess, \Traversable
|
||||
* Example:
|
||||
*
|
||||
* <code>
|
||||
* $group = new FieldGroup('address');
|
||||
* $group = new Form('address');
|
||||
* $group->add(new TextField('street'));
|
||||
* $group->add(new TextField('postal_code'));
|
||||
* $form->add($group);
|
@ -24,7 +24,7 @@ use Symfony\Component\Form\Exception\FormException;
|
||||
*
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class HybridField extends FieldGroup
|
||||
class HybridField extends Form
|
||||
{
|
||||
const FIELD = 0;
|
||||
const GROUP = 1;
|
||||
|
@ -21,7 +21,7 @@ namespace Symfony\Component\Form;
|
||||
*/
|
||||
class RecursiveFieldIterator extends \IteratorIterator implements \RecursiveIterator
|
||||
{
|
||||
public function __construct(FieldGroupInterface $group)
|
||||
public function __construct(FormInterface $group)
|
||||
{
|
||||
parent::__construct($group);
|
||||
}
|
||||
@ -33,7 +33,7 @@ class RecursiveFieldIterator extends \IteratorIterator implements \RecursiveIter
|
||||
|
||||
public function hasChildren()
|
||||
{
|
||||
return $this->current() instanceof FieldGroupInterface
|
||||
return $this->current() instanceof FormInterface
|
||||
&& $this->current()->isVirtual();
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace Symfony\Component\Form;
|
||||
*
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class RepeatedField extends FieldGroup
|
||||
class RepeatedField extends Form
|
||||
{
|
||||
/**
|
||||
* The prototype for the inner fields
|
||||
|
@ -37,7 +37,7 @@ use Symfony\Component\Form\ValueTransformer\ValueTransformerChain;
|
||||
* * data_timezone: The timezone of the data. Default: UTC.
|
||||
* * user_timezone: The timezone of the user entering a new value. Default: UTC.
|
||||
*/
|
||||
class TimeField extends FieldGroup
|
||||
class TimeField extends Form
|
||||
{
|
||||
const INPUT = 'input';
|
||||
const CHOICE = 'choice';
|
||||
|
@ -14,7 +14,7 @@ namespace Symfony\Tests\Component\Form;
|
||||
require_once __DIR__ . '/Fixtures/TestField.php';
|
||||
|
||||
use Symfony\Component\Form\CollectionField;
|
||||
use Symfony\Component\Form\FieldGroup;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Tests\Component\Form\Fixtures\TestField;
|
||||
|
||||
class CollectionFieldTest extends \PHPUnit_Framework_TestCase
|
||||
|
@ -1,836 +0,0 @@
|
||||
<?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\Tests\Component\Form;
|
||||
|
||||
require_once __DIR__ . '/Fixtures/Author.php';
|
||||
require_once __DIR__ . '/Fixtures/TestField.php';
|
||||
require_once __DIR__ . '/Fixtures/TestFieldGroup.php';
|
||||
|
||||
use Symfony\Component\Form\Field;
|
||||
use Symfony\Component\Form\FieldError;
|
||||
use Symfony\Component\Form\FieldInterface;
|
||||
use Symfony\Component\Form\FieldGroup;
|
||||
use Symfony\Component\Form\PropertyPath;
|
||||
use Symfony\Tests\Component\Form\Fixtures\Author;
|
||||
use Symfony\Tests\Component\Form\Fixtures\TestField;
|
||||
use Symfony\Tests\Component\Form\Fixtures\TestFieldGroup;
|
||||
|
||||
class FieldGroupTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSupportsArrayAccess()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createMockField('firstName'));
|
||||
$this->assertEquals($group->get('firstName'), $group['firstName']);
|
||||
$this->assertTrue(isset($group['firstName']));
|
||||
}
|
||||
|
||||
public function testSupportsUnset()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createMockField('firstName'));
|
||||
unset($group['firstName']);
|
||||
$this->assertFalse(isset($group['firstName']));
|
||||
}
|
||||
|
||||
public function testDoesNotSupportAddingFields()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$this->setExpectedException('LogicException');
|
||||
$group[] = $this->createMockField('lastName');
|
||||
}
|
||||
|
||||
public function testSupportsCountable()
|
||||
{
|
||||
$group = new TestFieldGroup('group');
|
||||
$group->add($this->createMockField('firstName'));
|
||||
$group->add($this->createMockField('lastName'));
|
||||
$this->assertEquals(2, count($group));
|
||||
|
||||
$group->add($this->createMockField('australian'));
|
||||
$this->assertEquals(3, count($group));
|
||||
}
|
||||
|
||||
public function testSupportsIterable()
|
||||
{
|
||||
$group = new TestFieldGroup('group');
|
||||
$group->add($field1 = $this->createMockField('field1'));
|
||||
$group->add($field2 = $this->createMockField('field2'));
|
||||
$group->add($field3 = $this->createMockField('field3'));
|
||||
|
||||
$expected = array(
|
||||
'field1' => $field1,
|
||||
'field2' => $field2,
|
||||
'field3' => $field3,
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, iterator_to_array($group));
|
||||
}
|
||||
|
||||
public function testIsBound()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$this->assertFalse($group->isBound());
|
||||
$group->bind(array('firstName' => 'Bernhard'));
|
||||
$this->assertTrue($group->isBound());
|
||||
}
|
||||
|
||||
public function testValidIfAllFieldsAreValid()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createValidMockField('firstName'));
|
||||
$group->add($this->createValidMockField('lastName'));
|
||||
|
||||
$group->bind(array('firstName' => 'Bernhard', 'lastName' => 'Potencier'));
|
||||
|
||||
$this->assertTrue($group->isValid());
|
||||
}
|
||||
|
||||
public function testInvalidIfFieldIsInvalid()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createInvalidMockField('firstName'));
|
||||
$group->add($this->createValidMockField('lastName'));
|
||||
|
||||
$group->bind(array('firstName' => 'Bernhard', 'lastName' => 'Potencier'));
|
||||
|
||||
$this->assertFalse($group->isValid());
|
||||
}
|
||||
|
||||
public function testInvalidIfBoundWithExtraFields()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createValidMockField('firstName'));
|
||||
$group->add($this->createValidMockField('lastName'));
|
||||
|
||||
$group->bind(array('foo' => 'bar', 'firstName' => 'Bernhard', 'lastName' => 'Potencier'));
|
||||
|
||||
$this->assertTrue($group->isBoundWithExtraFields());
|
||||
}
|
||||
|
||||
public function testHasNoErrorsIfOnlyFieldHasErrors()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createInvalidMockField('firstName'));
|
||||
|
||||
$group->bind(array('firstName' => 'Bernhard'));
|
||||
|
||||
$this->assertFalse($group->hasErrors());
|
||||
}
|
||||
|
||||
public function testBindForwardsPreprocessedData()
|
||||
{
|
||||
$field = $this->createMockField('firstName');
|
||||
|
||||
$group = $this->getMock(
|
||||
'Symfony\Tests\Component\Form\Fixtures\TestFieldGroup',
|
||||
array('preprocessData'), // only mock preprocessData()
|
||||
array('author')
|
||||
);
|
||||
|
||||
// The data array is prepared directly after binding
|
||||
$group->expects($this->once())
|
||||
->method('preprocessData')
|
||||
->with($this->equalTo(array('firstName' => 'Bernhard')))
|
||||
->will($this->returnValue(array('firstName' => 'preprocessed[Bernhard]')));
|
||||
$group->add($field);
|
||||
|
||||
// The preprocessed data is then forwarded to the fields
|
||||
$field->expects($this->once())
|
||||
->method('bind')
|
||||
->with($this->equalTo('preprocessed[Bernhard]'));
|
||||
|
||||
$group->bind(array('firstName' => 'Bernhard'));
|
||||
}
|
||||
|
||||
public function testBindForwardsNullIfValueIsMissing()
|
||||
{
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->once())
|
||||
->method('bind')
|
||||
->with($this->equalTo(null));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$group->bind(array());
|
||||
}
|
||||
|
||||
public function testAddErrorMapsFieldValidationErrorsOntoFields()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->once())
|
||||
->method('addError')
|
||||
->with($this->equalTo($error));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$path = new PropertyPath('fields[firstName].data');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::FIELD_ERROR);
|
||||
}
|
||||
|
||||
public function testAddErrorMapsFieldValidationErrorsOntoFieldsWithinNestedFieldGroups()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->once())
|
||||
->method('addError')
|
||||
->with($this->equalTo($error));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$innerGroup = new TestFieldGroup('names');
|
||||
$innerGroup->add($field);
|
||||
$group->add($innerGroup);
|
||||
|
||||
$path = new PropertyPath('fields[names].fields[firstName].data');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::FIELD_ERROR);
|
||||
}
|
||||
|
||||
public function testAddErrorKeepsFieldValidationErrorsIfFieldNotFound()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
$field = $this->createMockField('foo');
|
||||
$field->expects($this->never())
|
||||
->method('addError');
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$path = new PropertyPath('fields[bar].data');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::FIELD_ERROR);
|
||||
|
||||
$this->assertEquals(array($error), $group->getErrors());
|
||||
}
|
||||
|
||||
public function testAddErrorKeepsFieldValidationErrorsIfFieldIsHidden()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->any())
|
||||
->method('isHidden')
|
||||
->will($this->returnValue(true));
|
||||
$field->expects($this->never())
|
||||
->method('addError');
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$path = new PropertyPath('fields[firstName].data');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::FIELD_ERROR);
|
||||
|
||||
$this->assertEquals(array($error), $group->getErrors());
|
||||
}
|
||||
|
||||
public function testAddErrorMapsDataValidationErrorsOntoFields()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
// path is expected to point at "firstName"
|
||||
$expectedPath = new PropertyPath('firstName');
|
||||
$expectedPathIterator = $expectedPath->getIterator();
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue(new PropertyPath('firstName')));
|
||||
$field->expects($this->once())
|
||||
->method('addError')
|
||||
->with($this->equalTo($error), $this->equalTo($expectedPathIterator), $this->equalTo(FieldGroup::DATA_ERROR));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$path = new PropertyPath('firstName');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::DATA_ERROR);
|
||||
}
|
||||
|
||||
public function testAddErrorKeepsDataValidationErrorsIfFieldNotFound()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
$field = $this->createMockField('foo');
|
||||
$field->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue(new PropertyPath('foo')));
|
||||
$field->expects($this->never())
|
||||
->method('addError');
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$path = new PropertyPath('bar');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::DATA_ERROR);
|
||||
}
|
||||
|
||||
public function testAddErrorKeepsDataValidationErrorsIfFieldIsHidden()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->any())
|
||||
->method('isHidden')
|
||||
->will($this->returnValue(true));
|
||||
$field->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue(new PropertyPath('firstName')));
|
||||
$field->expects($this->never())
|
||||
->method('addError');
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$path = new PropertyPath('firstName');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::DATA_ERROR);
|
||||
}
|
||||
|
||||
public function testAddErrorMapsDataValidationErrorsOntoNestedFields()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
// path is expected to point at "street"
|
||||
$expectedPath = new PropertyPath('address.street');
|
||||
$expectedPathIterator = $expectedPath->getIterator();
|
||||
$expectedPathIterator->next();
|
||||
|
||||
$field = $this->createMockField('address');
|
||||
$field->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue(new PropertyPath('address')));
|
||||
$field->expects($this->once())
|
||||
->method('addError')
|
||||
->with($this->equalTo($error), $this->equalTo($expectedPathIterator), $this->equalTo(FieldGroup::DATA_ERROR));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$path = new PropertyPath('address.street');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::DATA_ERROR);
|
||||
}
|
||||
|
||||
public function testAddErrorMapsErrorsOntoFieldsInVirtualGroups()
|
||||
{
|
||||
$error = new FieldError('Message');
|
||||
|
||||
// path is expected to point at "address"
|
||||
$expectedPath = new PropertyPath('address');
|
||||
$expectedPathIterator = $expectedPath->getIterator();
|
||||
|
||||
$field = $this->createMockField('address');
|
||||
$field->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue(new PropertyPath('address')));
|
||||
$field->expects($this->once())
|
||||
->method('addError')
|
||||
->with($this->equalTo($error), $this->equalTo($expectedPathIterator), $this->equalTo(FieldGroup::DATA_ERROR));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$nestedGroup = new TestFieldGroup('nested', array('virtual' => true));
|
||||
$nestedGroup->add($field);
|
||||
$group->add($nestedGroup);
|
||||
|
||||
$path = new PropertyPath('address');
|
||||
|
||||
$group->addError($error, $path->getIterator(), FieldGroup::DATA_ERROR);
|
||||
}
|
||||
|
||||
public function testAddThrowsExceptionIfAlreadyBound()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createMockField('firstName'));
|
||||
$group->bind(array('firstName' => 'Bernhard'));
|
||||
|
||||
$this->setExpectedException('Symfony\Component\Form\Exception\AlreadyBoundException');
|
||||
$group->add($this->createMockField('lastName'));
|
||||
}
|
||||
|
||||
public function testAddSetsFieldParent()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->once())
|
||||
->method('setParent');
|
||||
// PHPUnit fails to compare infinitely recursive objects
|
||||
//->with($this->equalTo($group));
|
||||
|
||||
$group->add($field);
|
||||
}
|
||||
|
||||
public function testRemoveUnsetsFieldParent()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->exactly(2))
|
||||
->method('setParent');
|
||||
// PHPUnit fails to compare subsequent method calls with different arguments
|
||||
|
||||
$group->add($field);
|
||||
$group->remove('firstName');
|
||||
}
|
||||
|
||||
public function testAddUpdatesFieldFromTransformedData()
|
||||
{
|
||||
$originalAuthor = new Author();
|
||||
$transformedAuthor = new Author();
|
||||
// the authors should differ to make sure the test works
|
||||
$transformedAuthor->firstName = 'Foo';
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$transformer = $this->createMockTransformer();
|
||||
$transformer->expects($this->once())
|
||||
->method('transform')
|
||||
->with($this->equalTo($originalAuthor))
|
||||
->will($this->returnValue($transformedAuthor));
|
||||
|
||||
$group->setValueTransformer($transformer);
|
||||
$group->setData($originalAuthor);
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue(new PropertyPath('firstName')));
|
||||
$field->expects($this->once())
|
||||
->method('updateFromProperty')
|
||||
->with($this->equalTo($transformedAuthor));
|
||||
|
||||
$group->add($field);
|
||||
}
|
||||
|
||||
public function testAddDoesNotUpdateFieldIfTransformedDataIsEmpty()
|
||||
{
|
||||
$originalAuthor = new Author();
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$transformer = $this->createMockTransformer();
|
||||
$transformer->expects($this->once())
|
||||
->method('transform')
|
||||
->with($this->equalTo($originalAuthor))
|
||||
->will($this->returnValue(''));
|
||||
|
||||
$group->setValueTransformer($transformer);
|
||||
$group->setData($originalAuthor);
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->never())
|
||||
->method('updateFromProperty');
|
||||
|
||||
$group->add($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testAddThrowsExceptionIfNoFieldOrString()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$group->add(1234);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\FormException
|
||||
*/
|
||||
public function testAddThrowsExceptionIfAnonymousField()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$field = $this->createMockField('');
|
||||
|
||||
$group->add($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\DanglingFieldException
|
||||
*/
|
||||
public function testAddThrowsExceptionIfStringButNoRootForm()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$group->add('firstName');
|
||||
}
|
||||
|
||||
public function testAddThrowsExceptionIfStringButNoFieldFactory()
|
||||
{
|
||||
$form = $this->createMockForm();
|
||||
$form->expects($this->once())
|
||||
->method('getFieldFactory')
|
||||
->will($this->returnValue(null));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->setParent($form);
|
||||
|
||||
$this->setExpectedException('\LogicException');
|
||||
|
||||
$group->add('firstName');
|
||||
}
|
||||
|
||||
public function testAddUsesFieldFromFactoryIfStringIsGiven()
|
||||
{
|
||||
$author = new \stdClass();
|
||||
$field = $this->createMockField('firstName');
|
||||
|
||||
$factory = $this->getMock('Symfony\Component\Form\FieldFactory\FieldFactoryInterface');
|
||||
$factory->expects($this->once())
|
||||
->method('getInstance')
|
||||
->with($this->equalTo($author), $this->equalTo('firstName'), $this->equalTo(array('foo' => 'bar')))
|
||||
->will($this->returnValue($field));
|
||||
$form = $this->createMockForm();
|
||||
$form->expects($this->once())
|
||||
->method('getFieldFactory')
|
||||
->will($this->returnValue($factory));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->setParent($form);
|
||||
$group->setData($author);
|
||||
$group->add('firstName', array('foo' => 'bar'));
|
||||
|
||||
$this->assertSame($field, $group['firstName']);
|
||||
}
|
||||
|
||||
public function testSetDataUpdatesAllFieldsFromTransformedData()
|
||||
{
|
||||
$originalAuthor = new Author();
|
||||
$transformedAuthor = new Author();
|
||||
// the authors should differ to make sure the test works
|
||||
$transformedAuthor->firstName = 'Foo';
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$transformer = $this->createMockTransformer();
|
||||
$transformer->expects($this->once())
|
||||
->method('transform')
|
||||
->with($this->equalTo($originalAuthor))
|
||||
->will($this->returnValue($transformedAuthor));
|
||||
|
||||
$group->setValueTransformer($transformer);
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->once())
|
||||
->method('updateFromProperty')
|
||||
->with($this->equalTo($transformedAuthor));
|
||||
|
||||
$group->add($field);
|
||||
|
||||
$field = $this->createMockField('lastName');
|
||||
$field->expects($this->once())
|
||||
->method('updateFromProperty')
|
||||
->with($this->equalTo($transformedAuthor));
|
||||
|
||||
$group->add($field);
|
||||
|
||||
$group->setData($originalAuthor);
|
||||
}
|
||||
|
||||
/**
|
||||
* The use case for this test are groups whose fields should be mapped
|
||||
* directly onto properties of the form's object.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <code>
|
||||
* $dateRangeField = new FieldGroup('dateRange');
|
||||
* $dateRangeField->add(new DateField('startDate'));
|
||||
* $dateRangeField->add(new DateField('endDate'));
|
||||
* $form->add($dateRangeField);
|
||||
* </code>
|
||||
*
|
||||
* If $dateRangeField is not virtual, the property "dateRange" must be
|
||||
* present on the form's object. In this property, an object or array
|
||||
* with the properties "startDate" and "endDate" is expected.
|
||||
*
|
||||
* If $dateRangeField is virtual though, it's children are mapped directly
|
||||
* onto the properties "startDate" and "endDate" of the form's object.
|
||||
*/
|
||||
public function testSetDataSkipsVirtualFieldGroups()
|
||||
{
|
||||
$author = new Author();
|
||||
$author->firstName = 'Foo';
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$nestedGroup = new TestFieldGroup('personal_data', array(
|
||||
'virtual' => true,
|
||||
));
|
||||
|
||||
// both fields are in the nested group but receive the object of the
|
||||
// top-level group because the nested group is virtual
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->once())
|
||||
->method('updateFromProperty')
|
||||
->with($this->equalTo($author));
|
||||
|
||||
$nestedGroup->add($field);
|
||||
|
||||
$field = $this->createMockField('lastName');
|
||||
$field->expects($this->once())
|
||||
->method('updateFromProperty')
|
||||
->with($this->equalTo($author));
|
||||
|
||||
$nestedGroup->add($field);
|
||||
|
||||
$group->add($nestedGroup);
|
||||
$group->setData($author);
|
||||
}
|
||||
|
||||
public function testSetDataThrowsAnExceptionIfArgumentIsNotObjectOrArray()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$this->setExpectedException('InvalidArgumentException');
|
||||
|
||||
$group->setData('foobar');
|
||||
}
|
||||
|
||||
public function testBindUpdatesTransformedDataFromAllFields()
|
||||
{
|
||||
$originalAuthor = new Author();
|
||||
$transformedAuthor = new Author();
|
||||
// the authors should differ to make sure the test works
|
||||
$transformedAuthor->firstName = 'Foo';
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
|
||||
$transformer = $this->createMockTransformer();
|
||||
$transformer->expects($this->exactly(2))
|
||||
->method('transform')
|
||||
// the method is first called with NULL, then
|
||||
// with $originalAuthor -> not testable by PHPUnit
|
||||
// ->with($this->equalTo(null))
|
||||
// ->with($this->equalTo($originalAuthor))
|
||||
->will($this->returnValue($transformedAuthor));
|
||||
|
||||
$group->setValueTransformer($transformer);
|
||||
$group->setData($originalAuthor);
|
||||
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->once())
|
||||
->method('updateProperty')
|
||||
->with($this->equalTo($transformedAuthor));
|
||||
|
||||
$group->add($field);
|
||||
|
||||
$field = $this->createMockField('lastName');
|
||||
$field->expects($this->once())
|
||||
->method('updateProperty')
|
||||
->with($this->equalTo($transformedAuthor));
|
||||
|
||||
$group->add($field);
|
||||
|
||||
$group->bind(array()); // irrelevant
|
||||
}
|
||||
|
||||
public function testGetDataReturnsObject()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$object = new \stdClass();
|
||||
$group->setData($object);
|
||||
$this->assertEquals($object, $group->getData());
|
||||
}
|
||||
|
||||
public function testGetDisplayedDataForwardsCall()
|
||||
{
|
||||
$field = $this->createValidMockField('firstName');
|
||||
$field->expects($this->atLeastOnce())
|
||||
->method('getDisplayedData')
|
||||
->will($this->returnValue('Bernhard'));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$this->assertEquals(array('firstName' => 'Bernhard'), $group->getDisplayedData());
|
||||
}
|
||||
|
||||
public function testIsMultipartIfAnyFieldIsMultipart()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createMultipartMockField('firstName'));
|
||||
$group->add($this->createNonMultipartMockField('lastName'));
|
||||
|
||||
$this->assertTrue($group->isMultipart());
|
||||
}
|
||||
|
||||
public function testIsNotMultipartIfNoFieldIsMultipart()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createNonMultipartMockField('firstName'));
|
||||
$group->add($this->createNonMultipartMockField('lastName'));
|
||||
|
||||
$this->assertFalse($group->isMultipart());
|
||||
}
|
||||
|
||||
public function testSupportsClone()
|
||||
{
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($this->createMockField('firstName'));
|
||||
|
||||
$clone = clone $group;
|
||||
|
||||
$this->assertNotSame($clone['firstName'], $group['firstName']);
|
||||
}
|
||||
|
||||
public function testBindWithoutPriorSetData()
|
||||
{
|
||||
return; // TODO
|
||||
$field = $this->createMockField('firstName');
|
||||
$field->expects($this->any())
|
||||
->method('getData')
|
||||
->will($this->returnValue('Bernhard'));
|
||||
|
||||
$group = new TestFieldGroup('author');
|
||||
$group->add($field);
|
||||
|
||||
$group->bind(array('firstName' => 'Bernhard'));
|
||||
|
||||
$this->assertEquals(array('firstName' => 'Bernhard'), $group->getData());
|
||||
}
|
||||
|
||||
public function testGetHiddenFieldsReturnsOnlyHiddenFields()
|
||||
{
|
||||
$group = $this->getGroupWithBothVisibleAndHiddenField();
|
||||
|
||||
$hiddenFields = $group->getHiddenFields(true, false);
|
||||
|
||||
$this->assertSame(array($group['hiddenField']), $hiddenFields);
|
||||
}
|
||||
|
||||
public function testGetVisibleFieldsReturnsOnlyVisibleFields()
|
||||
{
|
||||
$group = $this->getGroupWithBothVisibleAndHiddenField();
|
||||
|
||||
$visibleFields = $group->getVisibleFields(true, false);
|
||||
|
||||
$this->assertSame(array($group['visibleField']), $visibleFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a group containing two fields, "visibleField" and "hiddenField"
|
||||
*
|
||||
* @return FieldGroup
|
||||
*/
|
||||
protected function getGroupWithBothVisibleAndHiddenField()
|
||||
{
|
||||
$group = new TestFieldGroup('testGroup');
|
||||
|
||||
// add a visible field
|
||||
$visibleField = $this->createMockField('visibleField');
|
||||
$visibleField->expects($this->once())
|
||||
->method('isHidden')
|
||||
->will($this->returnValue(false));
|
||||
$group->add($visibleField);
|
||||
|
||||
// add a hidden field
|
||||
$hiddenField = $this->createMockField('hiddenField');
|
||||
$hiddenField->expects($this->once())
|
||||
->method('isHidden')
|
||||
->will($this->returnValue(true));
|
||||
$group->add($hiddenField);
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
protected function createMockField($key)
|
||||
{
|
||||
$field = $this->getMock(
|
||||
'Symfony\Component\Form\FieldInterface',
|
||||
array(),
|
||||
array(),
|
||||
'',
|
||||
false, // don't use constructor
|
||||
false // don't call parent::__clone
|
||||
);
|
||||
|
||||
$field->expects($this->any())
|
||||
->method('getKey')
|
||||
->will($this->returnValue($key));
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
protected function createMockForm()
|
||||
{
|
||||
$form = $this->getMock(
|
||||
'Symfony\Component\Form\Form',
|
||||
array(),
|
||||
array(),
|
||||
'',
|
||||
false, // don't use constructor
|
||||
false // don't call parent::__clone)
|
||||
);
|
||||
|
||||
$form->expects($this->any())
|
||||
->method('getRoot')
|
||||
->will($this->returnValue($form));
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
protected function createInvalidMockField($key)
|
||||
{
|
||||
$field = $this->createMockField($key);
|
||||
$field->expects($this->any())
|
||||
->method('isValid')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
protected function createValidMockField($key)
|
||||
{
|
||||
$field = $this->createMockField($key);
|
||||
$field->expects($this->any())
|
||||
->method('isValid')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
protected function createNonMultipartMockField($key)
|
||||
{
|
||||
$field = $this->createMockField($key);
|
||||
$field->expects($this->any())
|
||||
->method('isMultipart')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
protected function createMultipartMockField($key)
|
||||
{
|
||||
$field = $this->createMockField($key);
|
||||
$field->expects($this->any())
|
||||
->method('isMultipart')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
protected function createMockTransformer()
|
||||
{
|
||||
return $this->getMock('Symfony\Component\Form\ValueTransformer\ValueTransformerInterface', array(), array(), '', false, false);
|
||||
}
|
||||
}
|
@ -526,7 +526,7 @@ class FieldTest extends \PHPUnit_Framework_TestCase
|
||||
protected function createMockGroup()
|
||||
{
|
||||
return $this->getMock(
|
||||
'Symfony\Component\Form\FieldGroup',
|
||||
'Symfony\Component\Form\Form',
|
||||
array(),
|
||||
array(),
|
||||
'',
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
namespace Symfony\Tests\Component\Form\Fixtures;
|
||||
|
||||
use Symfony\Component\Form\FieldGroup;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\ValueTransformer\ValueTransformerInterface;
|
||||
|
||||
class TestFieldGroup extends FieldGroup
|
||||
class TestForm extends Form
|
||||
{
|
||||
/**
|
||||
* Expose method for testing purposes
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user