[Form] Merged classes FieldGroup and Form for simplicity

This commit is contained in:
Bernhard Schussek 2011-01-29 22:39:36 +01:00
parent 7680657944
commit c468db5c5b
20 changed files with 1376 additions and 1536 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
{

View File

@ -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
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(),
'',

View File

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