2010-06-24 09:40:05 +01:00
< ? php
/*
2011-01-15 13:29:43 +00:00
* This file is part of the Symfony package .
2010-10-02 11:38:11 +01:00
*
2011-03-06 11:40:06 +00:00
* ( c ) Fabien Potencier < fabien @ symfony . com >
2010-06-24 09:40:05 +01:00
*
2011-01-15 13:29:43 +00:00
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
2010-06-24 09:40:05 +01:00
*/
2011-01-15 13:29:43 +00:00
namespace Symfony\Component\Form ;
2011-03-17 14:22:10 +00:00
use Symfony\Component\Form\Event\DataEvent ;
2011-03-16 17:20:13 +00:00
use Symfony\Component\Form\Event\FilterDataEvent ;
2010-12-15 14:36:47 +00:00
use Symfony\Component\Form\Exception\FormException ;
2012-02-10 12:47:43 +00:00
use Symfony\Component\Form\Exception\AlreadyBoundException ;
2011-01-29 21:39:36 +00:00
use Symfony\Component\Form\Exception\UnexpectedTypeException ;
2011-04-22 16:41:21 +01:00
use Symfony\Component\Form\Exception\TransformationFailedException ;
use Symfony\Component\HttpFoundation\Request ;
2011-03-16 17:20:13 +00:00
use Symfony\Component\EventDispatcher\EventDispatcherInterface ;
2010-10-02 11:38:11 +01:00
2010-06-24 09:40:05 +01:00
/**
* Form represents a form .
*
* A form is composed of a validator schema and a widget form schema .
*
2011-03-20 12:35:19 +00:00
* To implement your own form fields , you need to have a thorough understanding
* of the data flow within a form field . A form field stores its data in three
* different representations :
*
* ( 1 ) the format required by the form ' s object
* ( 2 ) a normalized format for internal processing
* ( 3 ) the format used for display
*
* A date field , for example , may store a date as " Y-m-d " string ( 1 ) in the
* object . To facilitate processing in the field , this value is normalized
* to a DateTime object ( 2 ) . In the HTML representation of your form , a
* localized string ( 3 ) is presented to and modified by the user .
*
* In most cases , format ( 1 ) and format ( 2 ) will be the same . For example ,
* a checkbox field uses a Boolean value both for internal processing as for
* storage in the object . In these cases you simply need to set a value
* transformer to convert between formats ( 2 ) and ( 3 ) . You can do this by
2011-08-14 16:42:02 +01:00
* calling appendClientTransformer () .
2011-03-20 12:35:19 +00:00
*
* In some cases though it makes sense to make format ( 1 ) configurable . To
* demonstrate this , let ' s extend our above date field to store the value
* either as " Y-m-d " string or as timestamp . Internally we still want to
* use a DateTime object for processing . To convert the data from string / integer
* to DateTime you can set a normalization transformer by calling
2011-08-14 16:42:02 +01:00
* appendNormTransformer () . The normalized data is then
2011-03-20 12:35:19 +00:00
* converted to the displayed data as described before .
*
2011-03-06 11:40:06 +00:00
* @ author Fabien Potencier < fabien @ symfony . com >
* @ author Bernhard Schussek < bernhard . schussek @ symfony . com >
2010-06-24 09:40:05 +01:00
*/
2011-03-20 12:35:19 +00:00
class Form implements \IteratorAggregate , FormInterface
2010-06-24 09:40:05 +01:00
{
2011-01-25 08:56:37 +00:00
/**
2011-03-31 14:23:33 +01:00
* The name of this form
* @ var string
*/
private $name ;
/**
2011-05-10 14:32:14 +01:00
* The parent of this form
2011-03-31 14:23:33 +01:00
* @ var FormInterface
*/
private $parent ;
/**
* The children of this form
2011-05-10 17:23:58 +01:00
* @ var array An array of FormInterface instances
2011-01-25 08:56:37 +00:00
*/
2011-03-20 12:35:19 +00:00
private $children = array ();
2011-03-31 14:23:33 +01:00
/**
* The mapper for mapping data to children and back
* @ var DataMapper\DataMapperInterface
*/
2011-03-20 12:35:19 +00:00
private $dataMapper ;
2011-03-31 14:23:33 +01:00
/**
* The errors of this form
2011-05-10 17:23:58 +01:00
* @ var array An array of FromError instances
2011-03-31 14:23:33 +01:00
*/
2011-03-20 12:35:19 +00:00
private $errors = array ();
2011-03-31 14:23:33 +01:00
/**
* Whether added errors should bubble up to the parent
* @ var Boolean
*/
2011-03-21 21:10:53 +00:00
private $errorBubbling ;
2011-03-31 14:23:33 +01:00
/**
* Whether this form is bound
* @ var Boolean
*/
2011-03-20 12:35:19 +00:00
private $bound = false ;
2011-03-31 14:23:33 +01:00
/**
* Whether this form may not be empty
* @ var Boolean
*/
2011-03-20 12:35:19 +00:00
private $required ;
2011-03-31 14:23:33 +01:00
/**
* The form data in application format
* @ var mixed
*/
2011-05-10 14:32:14 +01:00
private $appData ;
2011-03-31 14:23:33 +01:00
/**
* The form data in normalized format
* @ var mixed
*/
2011-03-20 12:35:19 +00:00
private $normData ;
2011-03-31 14:23:33 +01:00
/**
* The form data in client format
* @ var mixed
*/
2011-03-20 12:35:19 +00:00
private $clientData ;
2011-01-29 21:39:36 +00:00
2011-04-06 12:45:19 +01:00
/**
* Data used for the client data when no value is bound
* @ var mixed
*/
private $emptyData = '' ;
2011-01-29 21:39:36 +00:00
/**
2011-05-10 14:32:14 +01:00
* The bound values that don ' t belong to any children
2011-01-29 21:39:36 +00:00
* @ var array
*/
2011-03-20 11:00:19 +00:00
private $extraData = array ();
2010-06-24 10:24:08 +01:00
2011-03-31 14:23:33 +01:00
/**
2011-05-10 14:32:14 +01:00
* The transformers for transforming from application to normalized format
2011-03-31 14:23:33 +01:00
* and back
2011-05-10 14:32:14 +01:00
* @ var array An array of DataTransformerInterface
2011-03-31 14:23:33 +01:00
*/
2011-04-02 15:39:19 +01:00
private $normTransformers ;
2011-03-31 14:23:33 +01:00
/**
2011-05-10 14:32:14 +01:00
* The transformers for transforming from normalized to client format and
2011-03-31 14:23:33 +01:00
* back
2011-05-10 14:32:14 +01:00
* @ var array An array of DataTransformerInterface
2011-03-31 14:23:33 +01:00
*/
2011-04-02 15:39:19 +01:00
private $clientTransformers ;
2011-03-31 14:23:33 +01:00
/**
* Whether the data in application , normalized and client format is
* synchronized . Data may not be synchronized if transformation errors
* occur .
* @ var Boolean
*/
2011-03-22 01:03:22 +00:00
private $synchronized = true ;
2011-03-31 14:23:33 +01:00
/**
* The validators attached to this form
2011-05-10 17:23:58 +01:00
* @ var array An array of FormValidatorInterface instances
2011-03-31 14:23:33 +01:00
*/
2011-03-20 12:35:19 +00:00
private $validators ;
2011-03-31 14:23:33 +01:00
/**
* Whether this form may only be read , but not bound
* @ var Boolean
*/
2012-01-26 15:54:42 +00:00
private $disabled = false ;
2011-03-31 14:23:33 +01:00
/**
* The dispatcher for distributing events of this form
* @ var Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
2011-03-20 12:35:19 +00:00
private $dispatcher ;
2011-03-31 14:23:33 +01:00
/**
* Key - value store for arbitrary attributes attached to this form
* @ var array
*/
2011-03-20 12:35:19 +00:00
private $attributes ;
2011-03-31 14:23:33 +01:00
/**
* The FormTypeInterface instances used to create this form
2011-05-10 14:32:14 +01:00
* @ var array An array of FormTypeInterface
2011-03-31 14:23:33 +01:00
*/
2011-03-24 15:16:31 +00:00
private $types ;
2010-06-24 10:24:08 +01:00
2011-04-02 15:39:19 +01:00
public function __construct ( $name , EventDispatcherInterface $dispatcher ,
array $types = array (), array $clientTransformers = array (),
array $normTransformers = array (),
2011-03-20 12:35:19 +00:00
DataMapperInterface $dataMapper = null , array $validators = array (),
2012-01-26 15:54:42 +00:00
$required = false , $disabled = false , $errorBubbling = false ,
2011-03-27 14:12:35 +01:00
$emptyData = null , array $attributes = array ())
2011-03-01 13:19:28 +00:00
{
2012-01-16 23:02:58 +00:00
$name = ( string ) $name ;
self :: validateName ( $name );
2011-04-02 15:39:19 +01:00
foreach ( $clientTransformers as $transformer ) {
if ( ! $transformer instanceof DataTransformerInterface ) {
2011-04-22 16:41:21 +01:00
throw new UnexpectedTypeException ( $transformer , 'Symfony\Component\Form\DataTransformerInterface' );
2011-04-02 15:39:19 +01:00
}
}
foreach ( $normTransformers as $transformer ) {
if ( ! $transformer instanceof DataTransformerInterface ) {
2011-04-22 16:41:21 +01:00
throw new UnexpectedTypeException ( $transformer , 'Symfony\Component\Form\DataTransformerInterface' );
2011-04-02 15:39:19 +01:00
}
}
2011-03-20 12:35:19 +00:00
foreach ( $validators as $validator ) {
if ( ! $validator instanceof FormValidatorInterface ) {
2011-04-22 16:41:21 +01:00
throw new UnexpectedTypeException ( $validator , 'Symfony\Component\Form\FormValidatorInterface' );
2011-03-20 12:35:19 +00:00
}
}
2011-03-16 17:20:13 +00:00
2012-01-16 23:02:58 +00:00
$this -> name = $name ;
2011-03-20 12:35:19 +00:00
$this -> dispatcher = $dispatcher ;
2011-05-10 14:32:14 +01:00
$this -> types = $types ;
2011-04-02 15:39:19 +01:00
$this -> clientTransformers = $clientTransformers ;
$this -> normTransformers = $normTransformers ;
2011-03-18 11:50:26 +00:00
$this -> dataMapper = $dataMapper ;
2011-05-10 14:32:14 +01:00
$this -> validators = $validators ;
$this -> required = ( Boolean ) $required ;
2012-01-26 15:54:42 +00:00
$this -> disabled = ( Boolean ) $disabled ;
2011-05-10 14:32:14 +01:00
$this -> errorBubbling = ( Boolean ) $errorBubbling ;
2011-03-27 14:12:35 +01:00
$this -> emptyData = $emptyData ;
2011-05-10 14:32:14 +01:00
$this -> attributes = $attributes ;
2011-03-01 13:19:28 +00:00
2011-03-20 12:35:19 +00:00
$this -> setData ( null );
2011-03-01 13:19:28 +00:00
}
2011-03-21 21:10:53 +00:00
public function __clone ()
2011-01-29 21:39:36 +00:00
{
2011-03-21 21:10:53 +00:00
foreach ( $this -> children as $key => $child ) {
$this -> children [ $key ] = clone $child ;
}
2011-03-18 11:50:26 +00:00
}
2011-01-29 21:39:36 +00:00
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Returns the name by which the form is identified in forms .
*
* @ return string The name of the form .
2011-03-20 12:35:19 +00:00
*/
public function getName ()
2011-03-18 11:50:26 +00:00
{
2011-03-20 12:35:19 +00:00
return $this -> name ;
}
2011-01-29 21:39:36 +00:00
2011-05-10 14:32:14 +01:00
/**
2011-05-10 17:23:58 +01:00
* Returns the types used by this form .
2011-05-10 14:32:14 +01:00
*
* @ return array An array of FormTypeInterface
*/
2011-03-24 15:16:31 +00:00
public function getTypes ()
{
return $this -> types ;
}
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Returns whether the form is required to be filled out .
*
* If the form has a parent and the parent is not required , this method
* will always return false . Otherwise the value set with setRequired ()
* is returned .
*
* @ return Boolean
2011-03-20 12:35:19 +00:00
*/
public function isRequired ()
{
if ( null === $this -> parent || $this -> parent -> isRequired ()) {
return $this -> required ;
}
2011-01-29 21:39:36 +00:00
2011-03-20 12:35:19 +00:00
return false ;
}
2011-01-29 21:39:36 +00:00
2011-03-20 12:35:19 +00:00
/**
2012-01-26 15:54:42 +00:00
* { @ inheritDoc }
2011-03-20 12:35:19 +00:00
*/
2012-01-26 15:54:42 +00:00
public function isDisabled ()
2011-03-20 12:35:19 +00:00
{
2012-01-26 15:54:42 +00:00
if ( null === $this -> parent || ! $this -> parent -> isDisabled ()) {
return $this -> disabled ;
2011-01-29 21:39:36 +00:00
}
2011-03-20 12:35:19 +00:00
return true ;
2011-01-29 21:39:36 +00:00
}
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Sets the parent form .
*
* @ param FormInterface $parent The parent form
*
* @ return Form The current form
2011-03-20 12:35:19 +00:00
*/
public function setParent ( FormInterface $parent = null )
2011-01-29 21:39:36 +00:00
{
2012-02-10 12:47:43 +00:00
if ( $this -> bound ) {
throw new AlreadyBoundException ( 'You cannot set the parent of a bound form' );
}
2012-01-07 14:14:50 +00:00
if ( '' === $this -> getName ()) {
throw new FormException ( 'Form with empty name can not have parent form.' );
}
2011-03-20 12:35:19 +00:00
$this -> parent = $parent ;
2011-01-29 21:39:36 +00:00
2011-03-20 12:35:19 +00:00
return $this ;
2011-01-29 21:39:36 +00:00
}
/**
2011-03-20 12:35:19 +00:00
* Returns the parent field .
2011-01-29 21:39:36 +00:00
*
2011-05-10 14:32:14 +01:00
* @ return FormInterface The parent field
2011-01-29 21:39:36 +00:00
*/
2011-03-20 12:35:19 +00:00
public function getParent ()
2011-01-29 21:39:36 +00:00
{
2011-03-20 12:35:19 +00:00
return $this -> parent ;
2011-01-29 21:39:36 +00:00
}
/**
2011-05-10 14:32:14 +01:00
* Returns whether the form has a parent .
2011-01-29 21:39:36 +00:00
*
2011-03-20 12:35:19 +00:00
* @ return Boolean
2011-01-29 21:39:36 +00:00
*/
2011-03-20 12:35:19 +00:00
public function hasParent ()
2011-01-29 21:39:36 +00:00
{
2011-03-20 12:35:19 +00:00
return null !== $this -> parent ;
}
2011-01-29 21:39:36 +00:00
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Returns the root of the form tree .
2011-03-20 12:35:19 +00:00
*
* @ return FormInterface The root of the tree
*/
public function getRoot ()
{
return $this -> parent ? $this -> parent -> getRoot () : $this ;
2011-01-29 21:39:36 +00:00
}
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Returns whether the field is the root of the form tree .
2011-03-20 12:35:19 +00:00
*
* @ return Boolean
*/
public function isRoot ()
2011-01-29 21:39:36 +00:00
{
2011-03-20 12:35:19 +00:00
return ! $this -> hasParent ();
}
2011-03-18 11:50:26 +00:00
2011-05-10 14:32:14 +01:00
/**
* Returns whether the form has an attribute with the given name .
*
* @ param string $name The name of the attribute
2011-08-14 16:42:02 +01:00
*
* @ return Boolean
2011-05-10 14:32:14 +01:00
*/
2011-03-20 12:35:19 +00:00
public function hasAttribute ( $name )
{
return isset ( $this -> attributes [ $name ]);
2011-03-18 11:50:26 +00:00
}
2011-05-10 14:32:14 +01:00
/**
* Returns the value of the attributes with the given name .
*
* @ param string $name The name of the attribute
*/
2011-03-20 12:35:19 +00:00
public function getAttribute ( $name )
2011-03-18 11:50:26 +00:00
{
2011-03-20 12:35:19 +00:00
return $this -> attributes [ $name ];
}
2011-03-18 11:50:26 +00:00
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Updates the field with default data .
2011-03-20 12:35:19 +00:00
*
2011-05-10 14:32:14 +01:00
* @ param array $appData The data formatted as expected for the underlying object
*
* @ return Form The current form
2011-03-20 12:35:19 +00:00
*/
public function setData ( $appData )
{
2012-02-10 12:47:43 +00:00
if ( $this -> bound ) {
throw new AlreadyBoundException ( 'You cannot change the data of a bound form' );
}
2011-03-20 12:35:19 +00:00
$event = new DataEvent ( $this , $appData );
2011-05-30 08:19:21 +01:00
$this -> dispatcher -> dispatch ( FormEvents :: PRE_SET_DATA , $event );
2011-03-20 12:35:19 +00:00
// Hook to change content of the data
$event = new FilterDataEvent ( $this , $appData );
2011-05-31 06:19:18 +01:00
$this -> dispatcher -> dispatch ( FormEvents :: SET_DATA , $event );
2011-03-20 12:35:19 +00:00
$appData = $event -> getData ();
2011-03-27 22:09:46 +01:00
// Treat data as strings unless a value transformer exists
2011-04-02 15:39:19 +01:00
if ( ! $this -> clientTransformers && ! $this -> normTransformers && is_scalar ( $appData )) {
2011-05-10 14:32:14 +01:00
$appData = ( string ) $appData ;
2011-03-18 11:50:26 +00:00
}
2011-03-20 12:35:19 +00:00
// Synchronize representations - must not change the content!
2011-03-20 14:22:37 +00:00
$normData = $this -> appToNorm ( $appData );
$clientData = $this -> normToClient ( $normData );
2011-03-20 12:35:19 +00:00
2011-05-10 14:32:14 +01:00
$this -> appData = $appData ;
2011-03-20 12:35:19 +00:00
$this -> normData = $normData ;
$this -> clientData = $clientData ;
2011-03-22 01:03:22 +00:00
$this -> synchronized = true ;
2011-03-20 12:35:19 +00:00
if ( $this -> dataMapper ) {
// Update child forms from the data
$this -> dataMapper -> mapDataToForms ( $clientData , $this -> children );
}
$event = new DataEvent ( $this , $appData );
2011-05-30 08:19:21 +01:00
$this -> dispatcher -> dispatch ( FormEvents :: POST_SET_DATA , $event );
2011-03-20 12:35:19 +00:00
return $this ;
2011-01-29 21:39:36 +00:00
}
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Returns the data in the format needed for the underlying object .
2011-03-20 12:35:19 +00:00
*
2011-05-10 14:32:14 +01:00
* @ return mixed
*/
public function getData ()
{
return $this -> appData ;
}
/**
* Returns the data transformed by the value transformer .
*
* @ return string
*/
public function getClientData ()
{
return $this -> clientData ;
}
/**
* Returns the extra data .
*
* @ return array The bound data which do not belong to a child
*/
public function getExtraData ()
{
return $this -> extraData ;
}
/**
* Binds data to the field , transforms and validates it .
*
* @ param string | array $clientData The data
*
* @ return Form The current form
*
* @ throws UnexpectedTypeException
2011-03-20 12:35:19 +00:00
*/
public function bind ( $clientData )
2011-03-01 13:19:28 +00:00
{
2012-02-10 12:47:43 +00:00
if ( $this -> bound ) {
throw new AlreadyBoundException ( 'A form can only be bound once' );
}
2012-01-26 15:54:42 +00:00
if ( $this -> isDisabled ()) {
2011-05-19 00:24:28 +01:00
$this -> bound = true ;
2011-05-11 16:08:53 +01:00
return $this ;
2011-04-02 10:39:15 +01:00
}
2011-03-20 12:35:19 +00:00
if ( is_scalar ( $clientData ) || null === $clientData ) {
2011-05-10 14:32:14 +01:00
$clientData = ( string ) $clientData ;
2011-03-20 12:35:19 +00:00
}
2011-03-16 17:20:13 +00:00
2011-03-24 23:09:08 +00:00
// Initialize errors in the very beginning so that we don't lose any
// errors added during listeners
$this -> errors = array ();
2011-03-20 12:35:19 +00:00
$event = new DataEvent ( $this , $clientData );
2011-05-30 08:19:21 +01:00
$this -> dispatcher -> dispatch ( FormEvents :: PRE_BIND , $event );
2011-03-20 12:35:19 +00:00
$appData = null ;
$normData = null ;
$extraData = array ();
2011-03-22 01:03:22 +00:00
$synchronized = false ;
2011-03-20 12:35:19 +00:00
// Hook to change content of the data bound by the browser
$event = new FilterDataEvent ( $this , $clientData );
2011-05-31 06:19:18 +01:00
$this -> dispatcher -> dispatch ( FormEvents :: BIND_CLIENT_DATA , $event );
2011-03-20 12:35:19 +00:00
$clientData = $event -> getData ();
if ( count ( $this -> children ) > 0 ) {
2011-04-02 11:00:19 +01:00
if ( null === $clientData || '' === $clientData ) {
2011-03-20 12:35:19 +00:00
$clientData = array ();
}
if ( ! is_array ( $clientData )) {
throw new UnexpectedTypeException ( $clientData , 'array' );
}
foreach ( $this -> children as $name => $child ) {
if ( ! isset ( $clientData [ $name ])) {
$clientData [ $name ] = null ;
}
}
foreach ( $clientData as $name => $value ) {
if ( $this -> has ( $name )) {
$this -> children [ $name ] -> bind ( $value );
} else {
$extraData [ $name ] = $value ;
}
}
2011-04-02 11:00:19 +01:00
// If we have a data mapper, use old client data and merge
// data from the children into it later
2011-03-20 12:35:19 +00:00
if ( $this -> dataMapper ) {
$clientData = $this -> getClientData ();
2011-04-02 11:00:19 +01:00
}
}
2011-03-20 12:35:19 +00:00
2011-04-02 11:00:19 +01:00
if ( null === $clientData || '' === $clientData ) {
$clientData = $this -> emptyData ;
2011-03-27 22:07:30 +01:00
2011-04-02 11:00:19 +01:00
if ( $clientData instanceof \Closure ) {
2011-05-10 14:32:14 +01:00
$clientData = $clientData ( $this );
2011-03-20 12:35:19 +00:00
}
2011-03-19 14:06:54 +00:00
}
2011-04-02 11:00:19 +01:00
// Merge form data from children into existing client data
2012-02-02 13:30:38 +00:00
if ( count ( $this -> children ) > 0 && $this -> dataMapper && null !== $clientData ) {
2011-04-02 11:00:19 +01:00
$this -> dataMapper -> mapFormsToData ( $this -> children , $clientData );
}
2011-03-20 12:35:19 +00:00
try {
// Normalize data to unified representation
2011-03-20 14:22:37 +00:00
$normData = $this -> clientToNorm ( $clientData );
2011-03-22 01:03:22 +00:00
$synchronized = true ;
2011-03-20 12:35:19 +00:00
} catch ( TransformationFailedException $e ) {
2011-03-01 13:19:28 +00:00
}
2011-03-22 01:03:22 +00:00
if ( $synchronized ) {
2011-03-20 12:35:19 +00:00
// Hook to change content of the data in the normalized
// representation
$event = new FilterDataEvent ( $this , $normData );
2011-05-31 06:19:18 +01:00
$this -> dispatcher -> dispatch ( FormEvents :: BIND_NORM_DATA , $event );
2011-03-20 12:35:19 +00:00
$normData = $event -> getData ();
// Synchronize representations - must not change the content!
2011-03-20 14:22:37 +00:00
$appData = $this -> normToApp ( $normData );
$clientData = $this -> normToClient ( $normData );
2011-03-01 13:19:28 +00:00
}
2011-03-20 12:35:19 +00:00
$this -> bound = true ;
2011-05-10 14:32:14 +01:00
$this -> appData = $appData ;
2011-03-20 12:35:19 +00:00
$this -> normData = $normData ;
$this -> clientData = $clientData ;
$this -> extraData = $extraData ;
2011-03-22 01:03:22 +00:00
$this -> synchronized = $synchronized ;
2011-03-19 18:53:39 +00:00
2011-03-20 12:35:19 +00:00
$event = new DataEvent ( $this , $clientData );
2011-05-30 08:19:21 +01:00
$this -> dispatcher -> dispatch ( FormEvents :: POST_BIND , $event );
2011-03-20 12:35:19 +00:00
foreach ( $this -> validators as $validator ) {
$validator -> validate ( $this );
2011-03-01 13:19:28 +00:00
}
2012-03-01 08:41:17 +00:00
$event = new DataEvent ( $this , $clientData );
$this -> dispatcher -> dispatch ( FormEvents :: POST_VALIDATE , $event );
2011-05-10 13:40:20 +01:00
return $this ;
2011-03-20 12:35:19 +00:00
}
2011-03-01 13:19:28 +00:00
2011-03-31 14:23:33 +01:00
/**
2011-05-10 14:32:14 +01:00
* Binds a request to the form .
2011-03-31 14:23:33 +01:00
*
2011-05-10 14:32:14 +01:00
* If the request method is POST , PUT or GET , the data is bound to the form ,
2011-03-31 14:23:33 +01:00
* transformed and written into the form data ( an object or an array ) .
*
* @ param Request $request The request to bind to the form
2011-05-10 14:32:14 +01:00
*
2011-05-10 13:40:20 +01:00
* @ return Form This form
*
2011-05-10 14:32:14 +01:00
* @ throws FormException if the method of the request is not one of GET , POST or PUT
2011-03-31 14:23:33 +01:00
*/
public function bindRequest ( Request $request )
{
// Store the bound data in case of a post request
switch ( $request -> getMethod ()) {
case 'POST' :
case 'PUT' :
2012-01-20 00:04:31 +00:00
case 'DELETE' :
2012-01-24 18:46:37 +00:00
case 'PATCH' :
2011-12-21 14:50:59 +00:00
if ( '' === $this -> getName ()) {
2012-02-10 09:36:16 +00:00
// Form bound without name
$params = $request -> request -> all ();
$files = $request -> files -> all ();
} elseif ( $this -> hasChildren ()) {
// Form bound with name and children
$params = $request -> request -> get ( $this -> getName (), array ());
$files = $request -> files -> get ( $this -> getName (), array ());
2011-12-21 14:50:59 +00:00
} else {
2012-02-10 09:36:16 +00:00
// Form bound with name, but without children
$params = $request -> request -> get ( $this -> getName (), null );
$files = $request -> files -> get ( $this -> getName (), null );
}
if ( is_array ( $params ) && is_array ( $files )) {
$data = array_replace_recursive ( $params , $files );
} else {
$data = $params ? : $files ;
2011-12-21 14:50:59 +00:00
}
2011-03-31 14:23:33 +01:00
break ;
case 'GET' :
2011-12-21 14:50:59 +00:00
$data = '' === $this -> getName () ? $request -> query -> all () : $request -> query -> get ( $this -> getName (), array ());
2011-03-31 14:23:33 +01:00
break ;
default :
throw new FormException ( sprintf ( 'The request method "%s" is not supported' , $request -> getMethod ()));
}
2011-05-10 13:40:20 +01:00
return $this -> bind ( $data );
2011-03-31 14:23:33 +01:00
}
2011-03-20 12:35:19 +00:00
/**
* Returns the normalized data of the field .
*
* @ return mixed When the field is not bound , the default data is returned .
* When the field is bound , the normalized bound data is
* returned if the field is valid , null otherwise .
*/
public function getNormData ()
{
return $this -> normData ;
}
2011-03-18 11:50:26 +00:00
2011-03-20 12:35:19 +00:00
/**
2011-05-10 13:40:20 +01:00
* Adds an error to this form .
*
* @ param FormError $error
2011-03-20 12:35:19 +00:00
*
2011-05-10 13:40:20 +01:00
* @ return Form The current form
2011-03-20 12:35:19 +00:00
*/
2011-03-21 20:01:14 +00:00
public function addError ( FormError $error )
2011-03-20 12:35:19 +00:00
{
2011-03-21 21:10:53 +00:00
if ( $this -> parent && $this -> errorBubbling ) {
$this -> parent -> addError ( $error );
} else {
$this -> errors [] = $error ;
}
2011-05-10 13:40:20 +01:00
return $this ;
2011-03-21 21:10:53 +00:00
}
/**
2011-05-10 14:32:14 +01:00
* Returns whether errors bubble up to the parent .
2011-03-21 21:10:53 +00:00
*
* @ return Boolean
*/
public function getErrorBubbling ()
{
return $this -> errorBubbling ;
2011-03-20 12:35:19 +00:00
}
/**
* Returns whether the field is bound .
*
2011-05-10 14:32:14 +01:00
* @ return Boolean true if the form is bound to input values , false otherwise
2011-03-20 12:35:19 +00:00
*/
public function isBound ()
{
return $this -> bound ;
}
/**
2011-05-10 14:32:14 +01:00
* Returns whether the data in the different formats is synchronized .
2011-03-20 12:35:19 +00:00
*
* @ return Boolean
*/
2011-03-22 01:03:22 +00:00
public function isSynchronized ()
2011-03-20 12:35:19 +00:00
{
2011-03-22 01:03:22 +00:00
return $this -> synchronized ;
2011-03-20 12:35:19 +00:00
}
/**
2011-05-10 14:32:14 +01:00
* Returns whether the form is empty .
*
* @ return Boolean
2011-03-20 12:35:19 +00:00
*/
public function isEmpty ()
{
foreach ( $this -> children as $child ) {
if ( ! $child -> isEmpty ()) {
return false ;
}
}
2011-05-10 14:32:14 +01:00
return array () === $this -> appData || null === $this -> appData || '' === $this -> appData ;
2011-03-20 12:35:19 +00:00
}
2011-01-29 21:39:36 +00:00
/**
* Returns whether the field is valid .
*
* @ return Boolean
*/
public function isValid ()
{
2012-02-10 12:47:43 +00:00
if ( ! $this -> bound ) {
2011-07-04 11:12:42 +01:00
throw new \LogicException ( 'You cannot call isValid() on a form that is not bound.' );
2011-06-28 20:18:02 +01:00
}
if ( $this -> hasErrors ()) {
2011-01-29 21:39:36 +00:00
return false ;
}
2012-01-26 15:54:42 +00:00
if ( ! $this -> isDisabled ()) {
2011-06-10 15:11:50 +01:00
foreach ( $this -> children as $child ) {
if ( ! $child -> isValid ()) {
return false ;
}
2011-01-29 21:39:36 +00:00
}
}
return true ;
}
/**
2011-03-20 12:35:19 +00:00
* Returns whether or not there are errors .
2011-01-29 21:39:36 +00:00
*
2011-03-20 12:35:19 +00:00
* @ return Boolean true if form is bound and not valid
*/
public function hasErrors ()
{
// Don't call isValid() here, as its semantics are slightly different
// Field groups are not valid if their children are invalid, but
// hasErrors() returns only true if a field/field group itself has
// errors
return count ( $this -> errors ) > 0 ;
}
/**
2011-05-10 14:32:14 +01:00
* Returns all errors .
2011-03-20 12:35:19 +00:00
*
* @ return array An array of FormError instances that occurred during binding
*/
public function getErrors ()
{
return $this -> errors ;
}
2011-09-25 13:31:31 +01:00
/**
* Returns a string representation of all form errors ( including children errors ) .
*
* This method should only be used to help debug a form .
*
* @ param integer $level The indentation level ( used internally )
*
* @ return string A string representation of all errors
*/
public function getErrorsAsString ( $level = 0 )
{
$errors = '' ;
foreach ( $this -> errors as $error ) {
2011-11-29 17:26:32 +00:00
$errors .= str_repeat ( ' ' , $level ) . 'ERROR: ' . $error -> getMessage () . " \n " ;
2011-09-25 13:31:31 +01:00
}
if ( $this -> hasChildren ()) {
foreach ( $this -> children as $key => $child ) {
$errors .= str_repeat ( ' ' , $level ) . $key . " : \n " ;
if ( $err = $child -> getErrorsAsString ( $level + 4 )) {
$errors .= $err ;
} else {
$errors .= str_repeat ( ' ' , $level + 4 ) . " No errors \n " ;
}
}
}
return $errors ;
}
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Returns the DataTransformers .
2011-03-20 12:35:19 +00:00
*
2011-05-10 14:32:14 +01:00
* @ return array An array of DataTransformerInterface
2011-03-20 12:35:19 +00:00
*/
2011-04-02 15:39:19 +01:00
public function getNormTransformers ()
2011-03-20 12:35:19 +00:00
{
2011-04-02 15:39:19 +01:00
return $this -> normTransformers ;
2011-03-20 12:35:19 +00:00
}
/**
2011-05-10 14:32:14 +01:00
* Returns the DataTransformers .
2011-03-20 12:35:19 +00:00
*
2011-05-10 14:32:14 +01:00
* @ return array An array of DataTransformerInterface
2011-03-20 12:35:19 +00:00
*/
2011-04-02 15:39:19 +01:00
public function getClientTransformers ()
2011-03-20 12:35:19 +00:00
{
2011-04-02 15:39:19 +01:00
return $this -> clientTransformers ;
2011-03-20 12:35:19 +00:00
}
2011-12-28 20:47:11 +00:00
/**
* Returns the Validators
*
* @ return array An array of FormValidatorInterface
*/
public function getValidators ()
{
return $this -> validators ;
}
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Returns all children in this group .
2011-03-20 12:35:19 +00:00
*
* @ return array
*/
public function getChildren ()
{
return $this -> children ;
}
2011-05-10 14:32:14 +01:00
/**
* Return whether the form has children .
*
* @ return Boolean
*/
2011-03-22 00:15:48 +00:00
public function hasChildren ()
{
return count ( $this -> children ) > 0 ;
}
2011-05-10 14:32:14 +01:00
/**
* Adds a child to the form .
*
* @ param FormInterface $child The FormInterface to add as a child
2011-05-10 13:40:20 +01:00
*
* @ return Form the current form
2011-05-10 14:32:14 +01:00
*/
2011-03-20 12:35:19 +00:00
public function add ( FormInterface $child )
{
2012-02-10 12:47:43 +00:00
if ( $this -> bound ) {
throw new AlreadyBoundException ( 'You cannot add children to a bound form' );
}
2011-03-20 12:35:19 +00:00
$this -> children [ $child -> getName ()] = $child ;
$child -> setParent ( $this );
2011-03-22 00:15:48 +00:00
if ( $this -> dataMapper ) {
$this -> dataMapper -> mapDataToForm ( $this -> getClientData (), $child );
}
2011-05-10 13:40:20 +01:00
return $this ;
2011-03-20 12:35:19 +00:00
}
2011-05-10 14:32:14 +01:00
/**
* Removes a child from the form .
*
* @ param string $name The name of the child to remove
2011-05-10 13:40:20 +01:00
*
* @ return Form the current form
2011-05-10 14:32:14 +01:00
*/
2011-03-20 12:35:19 +00:00
public function remove ( $name )
{
2012-02-10 12:47:43 +00:00
if ( $this -> bound ) {
throw new AlreadyBoundException ( 'You cannot remove children from a bound form' );
}
2011-03-20 12:35:19 +00:00
if ( isset ( $this -> children [ $name ])) {
$this -> children [ $name ] -> setParent ( null );
unset ( $this -> children [ $name ]);
}
2011-05-10 13:40:20 +01:00
return $this ;
2011-03-20 12:35:19 +00:00
}
/**
* Returns whether a child with the given name exists .
*
* @ param string $name
2011-05-10 14:32:14 +01:00
*
2011-03-20 12:35:19 +00:00
* @ return Boolean
*/
public function has ( $name )
{
return isset ( $this -> children [ $name ]);
}
/**
* Returns the child with the given name .
*
* @ param string $name
2011-05-10 14:32:14 +01:00
*
2011-03-20 12:35:19 +00:00
* @ return FormInterface
2011-05-10 14:32:14 +01:00
*
* @ throws \InvalidArgumentException if the child does not exist
2011-03-20 12:35:19 +00:00
*/
public function get ( $name )
{
if ( isset ( $this -> children [ $name ])) {
return $this -> children [ $name ];
}
throw new \InvalidArgumentException ( sprintf ( 'Field "%s" does not exist.' , $name ));
}
/**
* Returns true if the child exists ( implements the \ArrayAccess interface ) .
*
* @ param string $name The name of the child
2011-01-29 21:39:36 +00:00
*
* @ return Boolean true if the widget exists , false otherwise
*/
2011-03-17 10:03:10 +00:00
public function offsetExists ( $name )
2011-01-29 21:39:36 +00:00
{
2011-03-17 10:03:10 +00:00
return $this -> has ( $name );
2011-01-29 21:39:36 +00:00
}
/**
2011-03-20 12:35:19 +00:00
* Returns the form child associated with the name ( implements the \ArrayAccess interface ) .
2011-01-29 21:39:36 +00:00
*
2011-03-17 10:03:10 +00:00
* @ param string $name The offset of the value to get
2011-01-29 21:39:36 +00:00
*
2011-03-31 14:23:33 +01:00
* @ return FormInterface A form instance
2011-01-29 21:39:36 +00:00
*/
2011-03-17 10:03:10 +00:00
public function offsetGet ( $name )
2011-01-29 21:39:36 +00:00
{
2011-03-17 10:03:10 +00:00
return $this -> get ( $name );
2011-01-29 21:39:36 +00:00
}
/**
2011-03-31 14:23:33 +01:00
* Adds a child to the form ( implements the \ArrayAccess interface ) .
2011-01-29 21:39:36 +00:00
*
2011-03-31 14:23:33 +01:00
* @ param string $name Ignored . The name of the child is used .
* @ param FormInterface $child The child to be added
2011-01-29 21:39:36 +00:00
*/
2011-03-20 12:35:19 +00:00
public function offsetSet ( $name , $child )
2011-01-29 21:39:36 +00:00
{
2011-03-31 14:23:33 +01:00
$this -> add ( $child );
2011-01-29 21:39:36 +00:00
}
/**
2011-03-31 14:23:33 +01:00
* Removes the child with the given name from the form ( implements the \ArrayAccess interface ) .
2011-01-29 21:39:36 +00:00
*
2011-03-31 14:23:33 +01:00
* @ param string $name The name of the child to be removed
2011-01-29 21:39:36 +00:00
*/
2011-03-17 10:03:10 +00:00
public function offsetUnset ( $name )
2011-01-29 21:39:36 +00:00
{
2011-03-31 14:23:33 +01:00
$this -> remove ( $name );
2011-01-29 21:39:36 +00:00
}
/**
* Returns the iterator for this group .
*
* @ return \ArrayIterator
*/
public function getIterator ()
{
2011-03-20 12:35:19 +00:00
return new \ArrayIterator ( $this -> children );
2011-01-29 21:39:36 +00:00
}
/**
2011-03-20 12:35:19 +00:00
* Returns the number of form children ( implements the \Countable interface ) .
2011-01-29 21:39:36 +00:00
*
2011-03-20 12:35:19 +00:00
* @ return integer The number of embedded form children
2011-01-29 21:39:36 +00:00
*/
public function count ()
{
2011-03-20 12:35:19 +00:00
return count ( $this -> children );
}
2011-05-06 11:48:43 +01:00
/**
* Creates a view .
*
* @ param FormView $parent The parent view
*
* @ return FormView The view
*/
public function createView ( FormView $parent = null )
{
if ( null === $parent && $this -> parent ) {
$parent = $this -> parent -> createView ();
}
$view = new FormView ();
$view -> setParent ( $parent );
$types = ( array ) $this -> types ;
foreach ( $types as $type ) {
$type -> buildView ( $view , $this );
foreach ( $type -> getExtensions () as $typeExtension ) {
$typeExtension -> buildView ( $view , $this );
}
}
2011-06-15 08:45:11 +01:00
$childViews = array ();
foreach ( $this -> children as $key => $child ) {
$childViews [ $key ] = $child -> createView ( $view );
}
2011-05-06 11:48:43 +01:00
$view -> setChildren ( $childViews );
foreach ( $types as $type ) {
$type -> buildViewBottomUp ( $view , $this );
foreach ( $type -> getExtensions () as $typeExtension ) {
$typeExtension -> buildViewBottomUp ( $view , $this );
}
}
return $view ;
}
2011-03-20 12:35:19 +00:00
/**
2011-05-10 14:32:14 +01:00
* Normalizes the value if a normalization transformer is set .
2011-03-20 12:35:19 +00:00
*
* @ param mixed $value The value to transform
2011-05-10 14:32:14 +01:00
*
2011-03-20 12:35:19 +00:00
* @ return string
*/
2011-03-20 14:22:37 +00:00
private function appToNorm ( $value )
2011-03-20 12:35:19 +00:00
{
2011-04-02 15:39:19 +01:00
foreach ( $this -> normTransformers as $transformer ) {
$value = $transformer -> transform ( $value );
2011-03-20 12:35:19 +00:00
}
2011-04-02 15:39:19 +01:00
return $value ;
2011-03-20 12:35:19 +00:00
}
/**
* Reverse transforms a value if a normalization transformer is set .
*
* @ param string $value The value to reverse transform
2011-05-10 14:32:14 +01:00
*
2011-03-20 12:35:19 +00:00
* @ return mixed
*/
2011-03-20 14:22:37 +00:00
private function normToApp ( $value )
2011-03-20 12:35:19 +00:00
{
2011-04-02 15:39:19 +01:00
for ( $i = count ( $this -> normTransformers ) - 1 ; $i >= 0 ; -- $i ) {
$value = $this -> normTransformers [ $i ] -> reverseTransform ( $value );
2011-03-20 12:35:19 +00:00
}
2011-04-02 15:39:19 +01:00
return $value ;
2011-03-20 12:35:19 +00:00
}
/**
* Transforms the value if a value transformer is set .
*
* @ param mixed $value The value to transform
2011-05-10 14:32:14 +01:00
*
2011-03-20 12:35:19 +00:00
* @ return string
*/
2011-03-20 14:22:37 +00:00
private function normToClient ( $value )
2011-03-20 12:35:19 +00:00
{
2011-04-02 15:39:19 +01:00
if ( ! $this -> clientTransformers ) {
2011-03-20 12:35:19 +00:00
// Scalar values should always be converted to strings to
// facilitate differentiation between empty ("") and zero (0).
2011-05-10 14:32:14 +01:00
return null === $value || is_scalar ( $value ) ? ( string ) $value : $value ;
2011-03-20 12:35:19 +00:00
}
2011-04-02 15:39:19 +01:00
foreach ( $this -> clientTransformers as $transformer ) {
$value = $transformer -> transform ( $value );
}
return $value ;
2011-03-20 12:35:19 +00:00
}
/**
* Reverse transforms a value if a value transformer is set .
*
* @ param string $value The value to reverse transform
2011-05-10 14:32:14 +01:00
*
2011-03-20 12:35:19 +00:00
* @ return mixed
*/
2011-03-20 14:22:37 +00:00
private function clientToNorm ( $value )
2011-03-20 12:35:19 +00:00
{
2011-04-02 15:39:19 +01:00
if ( ! $this -> clientTransformers ) {
2011-03-20 12:35:19 +00:00
return '' === $value ? null : $value ;
}
2011-04-02 15:39:19 +01:00
for ( $i = count ( $this -> clientTransformers ) - 1 ; $i >= 0 ; -- $i ) {
$value = $this -> clientTransformers [ $i ] -> reverseTransform ( $value );
}
return $value ;
2011-01-29 21:39:36 +00:00
}
2012-01-16 23:02:58 +00:00
/**
* Validates whether the given variable is a valid form name .
*
* @ param string $name The tested form name .
2012-01-24 11:21:25 +00:00
*
2012-01-16 23:02:58 +00:00
* @ throws UnexpectedTypeException If the name is not a string .
* @ throws \InvalidArgumentException If the name contains invalid characters .
*/
static public function validateName ( $name )
{
if ( ! is_string ( $name )) {
throw new UnexpectedTypeException ( $name , 'string' );
}
2012-01-24 11:21:25 +00:00
if ( ! self :: isValidName ( $name )) {
2012-01-16 23:02:58 +00:00
throw new \InvalidArgumentException ( sprintf (
'The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contains letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").' ,
$name
));
}
}
2012-01-24 11:21:25 +00:00
/**
* Returns whether the given variable contains a valid form name .
*
* A name is accepted if it
*
* * is empty
* * starts with a letter , digit or underscore
* * contains only letters , digits , numbers , underscores ( " _ " ),
* hyphens ( " - " ) and colons ( " : " )
*
* @ param string $name The tested form name .
*
* @ return Boolean Whether the name is valid .
*/
static public function isValidName ( $name )
{
2012-01-28 12:37:24 +00:00
return '' === $name || preg_match ( '/^[a-zA-Z0-9_][a-zA-Z0-9_\-:]*$/D' , $name );
2012-01-24 11:21:25 +00:00
}
2010-06-24 09:40:05 +01:00
}