2011-02-19 12:52:37 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
2011-04-24 12:59:46 +01:00
* ( c ) Fabien Potencier < fabien @ symfony . com >
2011-02-19 12:52:37 +00:00
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Form ;
2011-03-25 14:30:33 +00:00
use Symfony\Component\Form\Exception\FormException ;
2011-04-24 20:56:06 +01:00
use Symfony\Component\Form\Exception\UnexpectedTypeException ;
2011-05-13 17:29:18 +01:00
use Symfony\Component\Form\Exception\TypeDefinitionException ;
use Symfony\Component\Form\Exception\CreationException ;
2011-02-19 12:52:37 +00:00
2011-03-02 11:20:30 +00:00
class FormFactory implements FormFactoryInterface
2011-02-19 12:52:37 +00:00
{
2011-05-13 17:29:18 +01:00
private static $requiredOptions = array (
'data' ,
'required' ,
'max_length' ,
);
2011-05-10 17:23:58 +01:00
/**
* Extensions
* @ var array An array of FormExtensionInterface
*/
2011-04-22 16:41:21 +01:00
private $extensions = array ();
2011-03-02 13:58:19 +00:00
2011-05-10 17:23:58 +01:00
/**
* All known types ( cache )
* @ var array An array of FormTypeInterface
*/
2011-04-22 18:22:26 +01:00
private $types = array ();
2011-05-10 17:23:58 +01:00
/**
* The guesser chain
* @ var FormTypeGuesserChain
*/
2011-04-22 16:41:21 +01:00
private $guesser ;
2011-03-02 13:58:19 +00:00
2011-05-10 14:32:14 +01:00
/**
* Constructor .
*
* @ param array $extensions An array of FormExtensionInterface
*
* @ throws UnexpectedTypeException if any extension does not implement FormExtensionInterface
*/
2011-04-24 20:56:06 +01:00
public function __construct ( array $extensions )
2011-02-19 12:52:37 +00:00
{
2011-04-22 16:41:21 +01:00
foreach ( $extensions as $extension ) {
2011-04-24 20:56:06 +01:00
if ( ! $extension instanceof FormExtensionInterface ) {
throw new UnexpectedTypeException ( $extension , 'Symfony\Component\Form\FormExtensionInterface' );
}
2011-03-25 01:26:14 +00:00
}
2011-04-22 16:41:21 +01:00
2011-04-24 20:56:06 +01:00
$this -> extensions = $extensions ;
2011-04-22 16:41:21 +01:00
}
2011-05-15 18:24:09 +01:00
/**
* Returns whether the given type is supported .
*
* @ param string $name The name of the type
*
* @ return Boolean Whether the type is supported
*/
2011-05-13 17:29:18 +01:00
public function hasType ( $name )
2011-04-22 18:22:26 +01:00
{
2011-05-13 17:29:18 +01:00
if ( isset ( $this -> types [ $name ])) {
return true ;
}
2011-04-22 18:22:26 +01:00
2011-05-13 17:29:18 +01:00
try {
$this -> loadType ( $name );
} catch ( FormException $e ) {
return false ;
2011-04-22 18:22:26 +01:00
}
2011-05-13 17:29:18 +01:00
return true ;
}
2011-04-22 18:22:26 +01:00
2011-05-15 18:24:09 +01:00
/**
* Add a type .
*
* @ param FormTypeInterface $type The type
*/
2011-05-13 17:29:18 +01:00
public function addType ( FormTypeInterface $type )
{
$this -> loadTypeExtensions ( $type );
2011-04-22 18:22:26 +01:00
2011-07-07 08:16:13 +01:00
$this -> validateFormTypeName ( $type );
2011-05-13 17:29:18 +01:00
$this -> types [ $type -> getName ()] = $type ;
}
2011-04-22 18:22:26 +01:00
2011-05-10 17:23:58 +01:00
/**
* Returns a type by name .
*
* This methods registers the type extensions from the form extensions .
*
* @ param string | FormTypeInterface $name The name of the type or a type instance
*
* @ return FormTypeInterface The type
*
* @ throws FormException if the type can not be retrieved from any extension
*/
2011-04-22 18:22:26 +01:00
public function getType ( $name )
{
2011-05-13 17:29:18 +01:00
if ( ! is_string ( $name )) {
throw new UnexpectedTypeException ( $name , 'string' );
2011-04-22 18:22:26 +01:00
}
if ( ! isset ( $this -> types [ $name ])) {
2011-05-13 17:29:18 +01:00
$this -> loadType ( $name );
2011-04-22 18:22:26 +01:00
}
return $this -> types [ $name ];
}
2011-05-10 17:23:58 +01:00
/**
* Returns a form .
*
* @ see createBuilder ()
*
* @ param string | FormTypeInterface $type The type of the form
* @ param mixed $data The initial data
* @ param array $options The options
2011-12-14 14:01:09 +00:00
* @ param FormBuilder $parent The parent builder
2011-05-10 17:23:58 +01:00
*
* @ return Form The form named after the type
*
* @ throws FormException if any given option is not applicable to the given type
*/
2011-12-14 14:01:09 +00:00
public function create ( $type , $data = null , array $options = array (), FormBuilder $parent = null )
2011-02-19 14:26:07 +00:00
{
2011-12-14 14:01:09 +00:00
return $this -> createBuilder ( $type , $data , $options , $parent ) -> getForm ();
2011-04-22 09:42:21 +01:00
}
2011-05-10 17:23:58 +01:00
/**
* Returns a form .
*
* @ see createNamedBuilder ()
*
* @ param string | FormTypeInterface $type The type of the form
* @ param string $name The name of the form
* @ param mixed $data The initial data
* @ param array $options The options
2011-12-14 14:01:09 +00:00
* @ param FormBuilder $parent The parent builder
2011-05-10 17:23:58 +01:00
*
* @ return Form The form
*
* @ throws FormException if any given option is not applicable to the given type
*/
2011-12-14 14:01:09 +00:00
public function createNamed ( $type , $name , $data = null , array $options = array (), FormBuilder $parent = null )
2011-04-22 09:42:21 +01:00
{
2011-12-14 14:01:09 +00:00
return $this -> createNamedBuilder ( $type , $name , $data , $options , $parent ) -> getForm ();
2011-04-22 09:42:21 +01:00
}
/**
2011-05-10 17:23:58 +01:00
* Returns a form for a property of a class .
*
* @ see createBuilderForProperty ()
*
2011-12-14 14:01:09 +00:00
* @ param string $class The fully qualified class name
* @ param string $property The name of the property to guess for
* @ param mixed $data The initial data
* @ param array $options The options for the builder
* @ param FormBuilder $parent The parent builder
2011-05-10 17:23:58 +01:00
*
* @ return Form The form named after the property
*
* @ throws FormException if any given option is not applicable to the form type
2011-04-22 09:42:21 +01:00
*/
2011-12-14 14:01:09 +00:00
public function createForProperty ( $class , $property , $data = null , array $options = array (), FormBuilder $parent = null )
2011-04-22 09:42:21 +01:00
{
2011-12-14 14:01:09 +00:00
return $this -> createBuilderForProperty ( $class , $property , $data , $options , $parent ) -> getForm ();
2011-04-22 09:42:21 +01:00
}
2011-05-10 17:23:58 +01:00
/**
* Returns a form builder
*
* @ param string | FormTypeInterface $type The type of the form
* @ param mixed $data The initial data
* @ param array $options The options
2011-12-14 14:01:09 +00:00
* @ param FormBuilder $parent The parent builder
2011-05-10 17:23:58 +01:00
*
* @ return FormBuilder The form builder
*
* @ throws FormException if any given option is not applicable to the given type
*/
2011-12-14 14:01:09 +00:00
public function createBuilder ( $type , $data = null , array $options = array (), FormBuilder $parent = null )
2011-04-22 09:42:21 +01:00
{
$name = is_object ( $type ) ? $type -> getName () : $type ;
2011-12-14 14:01:09 +00:00
return $this -> createNamedBuilder ( $type , $name , $data , $options , $parent );
2011-04-22 09:42:21 +01:00
}
2011-03-04 15:01:50 +00:00
2011-05-10 17:23:58 +01:00
/**
* Returns a form builder .
*
* @ param string | FormTypeInterface $type The type of the form
* @ param string $name The name of the form
* @ param mixed $data The initial data
* @ param array $options The options
2011-12-14 14:01:09 +00:00
* @ param FormBuilder $parent The parent builder
2011-05-10 17:23:58 +01:00
*
* @ return FormBuilder The form builder
*
* @ throws FormException if any given option is not applicable to the given type
*/
2011-12-14 14:01:09 +00:00
public function createNamedBuilder ( $type , $name , $data = null , array $options = array (), FormBuilder $parent = null )
2011-04-22 09:42:21 +01:00
{
2011-04-24 12:32:29 +01:00
if ( ! array_key_exists ( 'data' , $options )) {
$options [ 'data' ] = $data ;
}
2012-02-07 09:51:21 +00:00
$builder = null ;
$types = array ();
$defaultOptions = array ();
$optionValues = array ();
$passedOptions = $options ;
// Bottom-up determination of the type hierarchy
// Start with the actual type and look for the parent type
// The complete hierarchy is saved in $types, the first entry being
// the root and the last entry being the leaf (the concrete type)
2011-03-18 15:20:00 +00:00
while ( null !== $type ) {
2011-05-13 17:29:18 +01:00
if ( $type instanceof FormTypeInterface ) {
2011-11-18 13:23:22 +00:00
if ( $type -> getName () == $type -> getParent ( $options )) {
2011-11-22 09:24:03 +00:00
throw new FormException ( sprintf ( 'The form type name "%s" for class "%s" cannot be the same as the parent type.' , $type -> getName (), get_class ( $type )));
2011-11-18 13:23:22 +00:00
}
2011-05-13 17:29:18 +01:00
$this -> addType ( $type );
2011-12-13 10:53:49 +00:00
} elseif ( is_string ( $type )) {
2011-05-13 17:29:18 +01:00
$type = $this -> getType ( $type );
2011-12-13 10:53:49 +00:00
} else {
throw new UnexpectedTypeException ( $type , 'string or Symfony\Component\Form\FormTypeInterface' );
2011-05-13 17:29:18 +01:00
}
2011-04-22 16:41:21 +01:00
2012-02-07 09:51:21 +00:00
array_unshift ( $types , $type );
// getParent() cannot see default options set by this type nor
// default options set by parent types
// As a result, the options always have to be checked for
// existence with isset() before using them in this method.
$type = $type -> getParent ( $options );
}
// Top-down determination of the options and default options
foreach ( $types as $type ) {
// Merge the default options of all types to an array of default
// options. Default options of children override default options
// of parents.
// Default options of ancestors are already visible in the $options
// array passed to the following methods.
$defaultOptions = array_replace ( $defaultOptions , $type -> getDefaultOptions ( $options ));
2011-05-13 17:41:23 +01:00
$optionValues = array_merge_recursive ( $optionValues , $type -> getAllowedOptionValues ( $options ));
2011-04-22 18:22:26 +01:00
foreach ( $type -> getExtensions () as $typeExtension ) {
2011-05-13 17:41:23 +01:00
$defaultOptions = array_replace ( $defaultOptions , $typeExtension -> getDefaultOptions ( $options ));
$optionValues = array_merge_recursive ( $optionValues , $typeExtension -> getAllowedOptionValues ( $options ));
2011-03-26 17:48:55 +00:00
}
2012-02-07 09:51:21 +00:00
// In each turn, the options are replaced by the combination of
// the currently known default options and the passed options.
// It is important to merge with $passedOptions and not with
// $options, otherwise default options of parents would override
// default options of child types.
$options = array_replace ( $defaultOptions , $passedOptions );
2011-02-21 22:40:12 +00:00
}
2011-02-19 12:52:37 +00:00
2011-05-13 17:29:18 +01:00
$type = end ( $types );
2012-02-07 09:51:21 +00:00
$knownOptions = array_keys ( $defaultOptions );
2011-05-13 17:29:18 +01:00
$diff = array_diff ( self :: $requiredOptions , $knownOptions );
2011-03-25 14:30:33 +00:00
2011-05-13 17:29:18 +01:00
if ( count ( $diff ) > 0 ) {
throw new TypeDefinitionException ( sprintf ( 'Type "%s" should support the option(s) "%s"' , $type -> getName (), implode ( '", "' , $diff )));
}
2012-02-07 09:51:21 +00:00
$diff = array_diff ( array_keys ( $passedOptions ), $knownOptions );
2011-03-25 14:30:33 +00:00
2011-05-13 17:29:18 +01:00
if ( count ( $diff ) > 1 ) {
2011-11-01 13:16:02 +00:00
throw new CreationException ( sprintf ( 'The options "%s" do not exist. Known options are: "%s"' , implode ( '", "' , $diff ), implode ( '", "' , $knownOptions )));
2011-05-13 17:29:18 +01:00
}
2011-03-25 14:30:33 +00:00
if ( count ( $diff ) > 0 ) {
2011-11-01 13:16:02 +00:00
throw new CreationException ( sprintf ( 'The option "%s" does not exist. Known options are: "%s"' , current ( $diff ), implode ( '", "' , $knownOptions )));
2011-03-25 14:30:33 +00:00
}
2011-05-13 17:41:23 +01:00
foreach ( $optionValues as $option => $allowedValues ) {
if ( ! in_array ( $options [ $option ], $allowedValues , true )) {
throw new CreationException ( sprintf ( 'The option "%s" has the value "%s", but is expected to be one of "%s"' , $option , $options [ $option ], implode ( '", "' , $allowedValues )));
}
2011-03-25 14:30:33 +00:00
}
for ( $i = 0 , $l = count ( $types ); $i < $l && ! $builder ; ++ $i ) {
2011-04-14 14:25:30 +01:00
$builder = $types [ $i ] -> createBuilder ( $name , $this , $options );
2011-03-25 14:30:33 +00:00
}
2011-05-13 17:29:18 +01:00
if ( ! $builder ) {
throw new TypeDefinitionException ( sprintf ( 'Type "%s" or any of its parents should return a FormBuilder instance from createBuilder()' , $type -> getName ()));
}
2011-02-19 16:30:19 +00:00
2011-03-24 15:16:31 +00:00
$builder -> setTypes ( $types );
2011-06-09 16:10:34 +01:00
$builder -> setCurrentLoadingType ( $type -> getName ());
2011-12-14 14:01:09 +00:00
$builder -> setParent ( $parent );
2011-03-18 11:50:26 +00:00
2011-03-24 15:16:31 +00:00
foreach ( $types as $type ) {
2011-03-26 17:52:24 +00:00
$type -> buildForm ( $builder , $options );
2011-04-22 18:22:26 +01:00
foreach ( $type -> getExtensions () as $typeExtension ) {
$typeExtension -> buildForm ( $builder , $options );
}
2011-02-19 14:26:07 +00:00
}
2011-06-09 16:10:34 +01:00
$builder -> setCurrentLoadingType ( null );
2011-02-19 14:26:07 +00:00
2011-04-22 09:42:21 +01:00
return $builder ;
2011-03-18 11:50:26 +00:00
}
2011-05-10 17:23:58 +01:00
/**
* Returns a form builder for a property of a class .
*
* If any of the 'max_length' , 'required' and type options can be guessed ,
* and are not provided in the options argument , the guessed value is used .
*
2011-12-14 14:01:09 +00:00
* @ param string $class The fully qualified class name
* @ param string $property The name of the property to guess for
* @ param mixed $data The initial data
* @ param array $options The options for the builder
* @ param FormBuilder $parent The parent builder
2011-05-10 17:23:58 +01:00
*
* @ return FormBuilder The form builder named after the property
*
* @ throws FormException if any given option is not applicable to the form type
*/
2011-12-14 14:01:09 +00:00
public function createBuilderForProperty ( $class , $property , $data = null , array $options = array (), FormBuilder $parent = null )
2011-03-02 13:58:19 +00:00
{
2011-04-22 16:41:21 +01:00
if ( ! $this -> guesser ) {
$this -> loadGuesser ();
}
$typeGuess = $this -> guesser -> guessType ( $class , $property );
$maxLengthGuess = $this -> guesser -> guessMaxLength ( $class , $property );
2011-05-04 22:16:41 +01:00
$minLengthGuess = $this -> guesser -> guessMinLength ( $class , $property );
2011-04-22 16:41:21 +01:00
$requiredGuess = $this -> guesser -> guessRequired ( $class , $property );
2011-03-18 15:20:00 +00:00
$type = $typeGuess ? $typeGuess -> getType () : 'text' ;
2011-03-02 13:58:19 +00:00
2012-03-19 22:57:21 +00:00
$maxLength = $maxLengthGuess ? $maxLengthGuess -> getValue () : null ;
$minLength = $minLengthGuess ? $minLengthGuess -> getValue () : null ;
$minLength = $minLength ? : 0 ;
if ( null !== $maxLength ) {
$options = array_merge ( array ( 'max_length' => $maxLength ), $options );
2011-03-02 13:58:19 +00:00
}
2012-03-19 22:57:21 +00:00
if ( $minLength > 0 ) {
$options = array_merge ( array ( 'pattern' => '.{' . $minLength . ',' . $maxLength . '}' ), $options );
2011-05-04 22:16:41 +01:00
}
2011-03-02 13:58:19 +00:00
if ( $requiredGuess ) {
$options = array_merge ( array ( 'required' => $requiredGuess -> getValue ()), $options );
}
// user options may override guessed options
2011-03-18 15:20:00 +00:00
if ( $typeGuess ) {
$options = array_merge ( $typeGuess -> getOptions (), $options );
2011-03-18 13:37:54 +00:00
}
2011-03-02 13:58:19 +00:00
2011-12-14 14:01:09 +00:00
return $this -> createNamedBuilder ( $type , $property , $data , $options , $parent );
2011-03-02 13:58:19 +00:00
}
2011-05-06 11:48:43 +01:00
2011-05-10 17:23:58 +01:00
/**
* Initializes the guesser chain .
*/
2011-05-06 11:48:43 +01:00
private function loadGuesser ()
{
$guessers = array ();
foreach ( $this -> extensions as $extension ) {
$guesser = $extension -> getTypeGuesser ();
if ( $guesser ) {
$guessers [] = $guesser ;
}
}
$this -> guesser = new FormTypeGuesserChain ( $guessers );
}
2011-05-13 17:29:18 +01:00
2011-05-15 18:24:09 +01:00
/**
* Loads a type .
*
* @ param string $name The type name
*
* @ throws FormException if the type is not provided by any registered extension
*/
2011-05-13 17:29:18 +01:00
private function loadType ( $name )
{
$type = null ;
foreach ( $this -> extensions as $extension ) {
if ( $extension -> hasType ( $name )) {
$type = $extension -> getType ( $name );
break ;
}
}
if ( ! $type ) {
throw new FormException ( sprintf ( 'Could not load type "%s"' , $name ));
}
$this -> loadTypeExtensions ( $type );
2011-07-07 08:16:13 +01:00
$this -> validateFormTypeName ( $type );
2011-05-13 17:29:18 +01:00
$this -> types [ $name ] = $type ;
}
2011-05-15 18:24:09 +01:00
/**
* Loads the extensions for a given type .
*
* @ param FormTypeInterface $type The type
*/
2011-05-13 17:29:18 +01:00
private function loadTypeExtensions ( FormTypeInterface $type )
{
$typeExtensions = array ();
foreach ( $this -> extensions as $extension ) {
$typeExtensions = array_merge (
$typeExtensions ,
$extension -> getTypeExtensions ( $type -> getName ())
);
}
$type -> setExtensions ( $typeExtensions );
}
2011-07-07 08:16:13 +01:00
private function validateFormTypeName ( FormTypeInterface $type )
{
2011-12-21 14:50:59 +00:00
if ( ! preg_match ( '/^[a-z0-9_]*$/i' , $type -> getName ())) {
2011-07-07 08:16:13 +01:00
throw new FormException ( sprintf ( 'The "%s" form type name ("%s") is not valid. Names must only contain letters, numbers, and "_".' , get_class ( $type ), $type -> getName ()));
}
}
2011-03-24 21:20:54 +00:00
}