From 29963400e8d01a42ecd1e88339cd28c33d9ddb8d Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 16 May 2012 19:25:27 +0200 Subject: [PATCH] [Form] Extracted FormConfig class to simplify the Form's constructor --- .../Extension/Core/ChoiceList/ChoiceList.php | 4 +- src/Symfony/Component/Form/Form.php | 298 +++------ src/Symfony/Component/Form/FormBuilder.php | 574 ++---------------- src/Symfony/Component/Form/FormConfig.php | 524 ++++++++++++++++ .../Component/Form/FormConfigInterface.php | 131 ++++ .../Component/Form/ImmutableFormConfig.php | 232 +++++++ .../Component/Form/Tests/FormBuilderTest.php | 41 +- .../Component/Form/Tests/FormConfigTest.php | 78 +++ src/Symfony/Component/Form/Tests/FormTest.php | 74 +-- 9 files changed, 1118 insertions(+), 838 deletions(-) create mode 100644 src/Symfony/Component/Form/FormConfig.php create mode 100644 src/Symfony/Component/Form/FormConfigInterface.php create mode 100644 src/Symfony/Component/Form/ImmutableFormConfig.php create mode 100644 src/Symfony/Component/Form/Tests/FormConfigTest.php diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php index 6634f682ad..34f7356ee3 100644 --- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php +++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Form\Extension\Core\ChoiceList; -use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormConfig; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\InvalidConfigurationException; use Symfony\Component\Form\Extension\Core\View\ChoiceView; @@ -335,7 +335,7 @@ class ChoiceList implements ChoiceListInterface { $index = $this->createIndex($choice); - if ('' === $index || null === $index || !Form::isValidName((string)$index)) { + if ('' === $index || null === $index || !FormConfig::isValidName((string)$index)) { throw new InvalidConfigurationException('The index "' . $index . '" created by the choice list is invalid. It should be a valid, non-empty Form name.'); } diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 94accae0bd..92d0532870 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -58,10 +58,10 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Form implements \IteratorAggregate, FormInterface { /** - * The name of this form - * @var string + * The form's configuration + * @var FormConfigInterface */ - private $name; + private $config; /** * The parent of this form @@ -75,36 +75,18 @@ class Form implements \IteratorAggregate, FormInterface */ private $children = array(); - /** - * The mapper for mapping data to children and back - * @var DataMapperInterface - */ - private $dataMapper; - /** * The errors of this form * @var array An array of FormError instances */ private $errors = array(); - /** - * Whether added errors should bubble up to the parent - * @var Boolean - */ - private $errorBubbling; - /** * Whether this form is bound * @var Boolean */ private $bound = false; - /** - * Whether this form may or may not be empty - * @var Boolean - */ - private $required; - /** * The form data in application format * @var mixed @@ -123,32 +105,12 @@ class Form implements \IteratorAggregate, FormInterface */ private $clientData; - /** - * Data used for the client data when no value is bound - * @var mixed - */ - private $emptyData = ''; - /** * The bound values that don't belong to any children * @var array */ private $extraData = array(); - /** - * The transformers for transforming from application to normalized format - * and back - * @var array An array of DataTransformerInterface - */ - private $normTransformers; - - /** - * The transformers for transforming from normalized to client format and - * back - * @var array An array of DataTransformerInterface - */ - private $clientTransformers; - /** * Whether the data in application, normalized and client format is * synchronized. Data may not be synchronized if transformation errors @@ -158,78 +120,19 @@ class Form implements \IteratorAggregate, FormInterface private $synchronized = true; /** - * The validators attached to this form - * @var array An array of FormValidatorInterface instances + * Creates a new form based on the given configuration. + * + * @param FormConfigInterface $config The form configuration. */ - private $validators; - - /** - * Whether this form may only be read, but not bound - * @var Boolean - */ - private $disabled = false; - - /** - * The dispatcher for distributing events of this form - * @var Symfony\Component\EventDispatcher\EventDispatcherInterface - */ - private $dispatcher; - - /** - * Key-value store for arbitrary attributes attached to this form - * @var array - */ - private $attributes; - - /** - * The FormTypeInterface instances used to create this form - * @var array An array of FormTypeInterface - */ - private $types; - - public function __construct($name, EventDispatcherInterface $dispatcher, - array $types = array(), array $clientTransformers = array(), - array $normTransformers = array(), - DataMapperInterface $dataMapper = null, array $validators = array(), - $required = false, $disabled = false, $errorBubbling = null, - $emptyData = null, array $attributes = array()) + public function __construct(FormConfigInterface $config) { - $name = (string) $name; - - self::validateName($name); - - foreach ($clientTransformers as $transformer) { - if (!$transformer instanceof DataTransformerInterface) { - throw new UnexpectedTypeException($transformer, 'Symfony\Component\Form\DataTransformerInterface'); - } + if (!$config instanceof ImmutableFormConfig) { + $config = new ImmutableFormConfig($config); } - foreach ($normTransformers as $transformer) { - if (!$transformer instanceof DataTransformerInterface) { - throw new UnexpectedTypeException($transformer, 'Symfony\Component\Form\DataTransformerInterface'); - } - } + $this->config = $config; - foreach ($validators as $validator) { - if (!$validator instanceof FormValidatorInterface) { - throw new UnexpectedTypeException($validator, 'Symfony\Component\Form\FormValidatorInterface'); - } - } - - $this->name = $name; - $this->dispatcher = $dispatcher; - $this->types = $types; - $this->clientTransformers = $clientTransformers; - $this->normTransformers = $normTransformers; - $this->dataMapper = $dataMapper; - $this->validators = $validators; - $this->required = (Boolean) $required; - $this->disabled = (Boolean) $disabled; - $this->errorBubbling = (Boolean) $errorBubbling; - $this->emptyData = $emptyData; - $this->attributes = $attributes; - - $this->setData(null); + $this->setData($config->getData()); } public function __clone() @@ -239,6 +142,16 @@ class Form implements \IteratorAggregate, FormInterface } } + /** + * Returns the configuration of the form. + * + * @return ImmutableFormConfig The form's immutable configuration. + */ + public function getConfig() + { + return $this->config; + } + /** * Returns the name by which the form is identified in forms. * @@ -246,7 +159,7 @@ class Form implements \IteratorAggregate, FormInterface */ public function getName() { - return $this->name; + return $this->config->getName(); } /** @@ -256,22 +169,16 @@ class Form implements \IteratorAggregate, FormInterface */ public function getTypes() { - return $this->types; + return $this->config->getTypes(); } /** - * 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 + * {@inheritdoc} */ public function isRequired() { if (null === $this->parent || $this->parent->isRequired()) { - return $this->required; + return $this->config->getRequired(); } return false; @@ -283,7 +190,7 @@ class Form implements \IteratorAggregate, FormInterface public function isDisabled() { if (null === $this->parent || !$this->parent->isDisabled()) { - return $this->disabled; + return $this->config->getDisabled(); } return true; @@ -302,8 +209,8 @@ class Form implements \IteratorAggregate, FormInterface throw new AlreadyBoundException('You cannot set the parent of a bound form'); } - if ('' === $this->getName()) { - throw new FormException('Form with empty name can not have parent form.'); + if ('' === $this->config->getName()) { + throw new FormException('A form with an empty name cannot have a parent form.'); } $this->parent = $parent; @@ -360,7 +267,7 @@ class Form implements \IteratorAggregate, FormInterface */ public function hasAttribute($name) { - return isset($this->attributes[$name]); + return $this->config->hasAttribute($name); } /** @@ -370,7 +277,7 @@ class Form implements \IteratorAggregate, FormInterface */ public function getAttribute($name) { - return $this->attributes[$name]; + return $this->config->getAttribute($name); } /** @@ -387,15 +294,15 @@ class Form implements \IteratorAggregate, FormInterface } $event = new DataEvent($this, $appData); - $this->dispatcher->dispatch(FormEvents::PRE_SET_DATA, $event); + $this->config->getEventDispatcher()->dispatch(FormEvents::PRE_SET_DATA, $event); // Hook to change content of the data $event = new FilterDataEvent($this, $appData); - $this->dispatcher->dispatch(FormEvents::SET_DATA, $event); + $this->config->getEventDispatcher()->dispatch(FormEvents::SET_DATA, $event); $appData = $event->getData(); // Treat data as strings unless a value transformer exists - if (!$this->clientTransformers && !$this->normTransformers && is_scalar($appData)) { + if (!$this->config->getClientTransformers() && !$this->config->getNormTransformers() && is_scalar($appData)) { $appData = (string) $appData; } @@ -408,13 +315,13 @@ class Form implements \IteratorAggregate, FormInterface $this->clientData = $clientData; $this->synchronized = true; - if (count($this->children) > 0 && $this->dataMapper) { + if (count($this->children) > 0 && $this->config->getDataMapper()) { // Update child forms from the data - $this->dataMapper->mapDataToForms($clientData, $this->children); + $this->config->getDataMapper()->mapDataToForms($clientData, $this->children); } $event = new DataEvent($this, $appData); - $this->dispatcher->dispatch(FormEvents::POST_SET_DATA, $event); + $this->config->getEventDispatcher()->dispatch(FormEvents::POST_SET_DATA, $event); return $this; } @@ -483,7 +390,7 @@ class Form implements \IteratorAggregate, FormInterface $this->errors = array(); $event = new DataEvent($this, $clientData); - $this->dispatcher->dispatch(FormEvents::PRE_BIND, $event); + $this->config->getEventDispatcher()->dispatch(FormEvents::PRE_BIND, $event); $appData = null; $normData = null; @@ -492,7 +399,7 @@ class Form implements \IteratorAggregate, FormInterface // Hook to change content of the data bound by the browser $event = new FilterDataEvent($this, $clientData); - $this->dispatcher->dispatch(FormEvents::BIND_CLIENT_DATA, $event); + $this->config->getEventDispatcher()->dispatch(FormEvents::BIND_CLIENT_DATA, $event); $clientData = $event->getData(); if (count($this->children) > 0) { @@ -520,13 +427,13 @@ class Form implements \IteratorAggregate, FormInterface // If we have a data mapper, use old client data and merge // data from the children into it later - if ($this->dataMapper) { + if ($this->config->getDataMapper()) { $clientData = $this->getClientData(); } } if (null === $clientData || '' === $clientData) { - $emptyData = $this->emptyData; + $emptyData = $this->config->getEmptyData(); if ($emptyData instanceof \Closure) { $emptyData = $emptyData($this, $clientData); @@ -536,8 +443,8 @@ class Form implements \IteratorAggregate, FormInterface } // Merge form data from children into existing client data - if (count($this->children) > 0 && $this->dataMapper && null !== $clientData) { - $this->dataMapper->mapFormsToData($this->children, $clientData); + if (count($this->children) > 0 && $this->config->getDataMapper() && null !== $clientData) { + $this->config->getDataMapper()->mapFormsToData($this->children, $clientData); } try { @@ -551,7 +458,7 @@ class Form implements \IteratorAggregate, FormInterface // Hook to change content of the data into the normalized // representation $event = new FilterDataEvent($this, $normData); - $this->dispatcher->dispatch(FormEvents::BIND_NORM_DATA, $event); + $this->config->getEventDispatcher()->dispatch(FormEvents::BIND_NORM_DATA, $event); $normData = $event->getData(); // Synchronize representations - must not change the content! @@ -567,9 +474,9 @@ class Form implements \IteratorAggregate, FormInterface $this->synchronized = $synchronized; $event = new DataEvent($this, $clientData); - $this->dispatcher->dispatch(FormEvents::POST_BIND, $event); + $this->config->getEventDispatcher()->dispatch(FormEvents::POST_BIND, $event); - foreach ($this->validators as $validator) { + foreach ($this->config->getValidators() as $validator) { $validator->validate($this); } @@ -590,24 +497,26 @@ class Form implements \IteratorAggregate, FormInterface */ public function bindRequest(Request $request) { + $name = $this->config->getName(); + // Store the bound data in case of a post request switch ($request->getMethod()) { case 'POST': case 'PUT': case 'DELETE': case 'PATCH': - if ('' === $this->getName()) { + if ('' === $name) { // 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()); + $params = $request->request->get($name, array()); + $files = $request->files->get($name, array()); } else { // Form bound with name, but without children - $params = $request->request->get($this->getName(), null); - $files = $request->files->get($this->getName(), null); + $params = $request->request->get($name, null); + $files = $request->files->get($name, null); } if (is_array($params) && is_array($files)) { $data = array_replace_recursive($params, $files); @@ -616,7 +525,7 @@ class Form implements \IteratorAggregate, FormInterface } break; case 'GET': - $data = '' === $this->getName() ? $request->query->all() : $request->query->get($this->getName(), array()); + $data = '' === $name ? $request->query->all() : $request->query->get($name, array()); break; default: throw new FormException(sprintf('The request method "%s" is not supported', $request->getMethod())); @@ -662,7 +571,7 @@ class Form implements \IteratorAggregate, FormInterface */ public function getErrorBubbling() { - return $this->errorBubbling; + return $this->config->getErrorBubbling(); } /** @@ -787,7 +696,7 @@ class Form implements \IteratorAggregate, FormInterface */ public function getNormTransformers() { - return $this->normTransformers; + return $this->config->getNormTransformers(); } /** @@ -797,7 +706,7 @@ class Form implements \IteratorAggregate, FormInterface */ public function getClientTransformers() { - return $this->clientTransformers; + return $this->config->getClientTransformers(); } /** @@ -821,11 +730,7 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Adds a child to the form. - * - * @param FormInterface $child The FormInterface to add as a child - * - * @return Form the current form + * {@inheritdoc} */ public function add(FormInterface $child) { @@ -837,19 +742,15 @@ class Form implements \IteratorAggregate, FormInterface $child->setParent($this); - if ($this->dataMapper) { - $this->dataMapper->mapDataToForm($this->getClientData(), $child); + if ($this->config->getDataMapper()) { + $this->config->getDataMapper()->mapDataToForm($this->getClientData(), $child); } return $this; } /** - * Removes a child from the form. - * - * @param string $name The name of the child to remove - * - * @return Form the current form + * {@inheritdoc} */ public function remove($name) { @@ -867,11 +768,7 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Returns whether a child with the given name exists. - * - * @param string $name - * - * @return Boolean + * {@inheritdoc} */ public function has($name) { @@ -879,13 +776,7 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Returns the child with the given name. - * - * @param string $name - * - * @return FormInterface - * - * @throws \InvalidArgumentException if the child does not exist + * {@inheritdoc} */ public function get($name) { @@ -974,11 +865,11 @@ class Form implements \IteratorAggregate, FormInterface $parent = $this->parent->createView(); } - $view = new FormView($this->name); + $view = new FormView($this->config->getName()); $view->setParent($parent); - $types = (array) $this->types; + $types = (array) $this->config->getTypes(); foreach ($types as $type) { $type->buildView($view, $this); @@ -1012,7 +903,7 @@ class Form implements \IteratorAggregate, FormInterface */ private function appToNorm($value) { - foreach ($this->normTransformers as $transformer) { + foreach ($this->config->getNormTransformers() as $transformer) { $value = $transformer->transform($value); } @@ -1028,8 +919,10 @@ class Form implements \IteratorAggregate, FormInterface */ private function normToApp($value) { - for ($i = count($this->normTransformers) - 1; $i >= 0; --$i) { - $value = $this->normTransformers[$i]->reverseTransform($value); + $transformers = $this->config->getNormTransformers(); + + for ($i = count($transformers) - 1; $i >= 0; --$i) { + $value = $transformers[$i]->reverseTransform($value); } return $value; @@ -1044,13 +937,13 @@ class Form implements \IteratorAggregate, FormInterface */ private function normToClient($value) { - if (!$this->clientTransformers) { + if (!$this->config->getClientTransformers()) { // Scalar values should always be converted to strings to // facilitate differentiation between empty ("") and zero (0). return null === $value || is_scalar($value) ? (string) $value : $value; } - foreach ($this->clientTransformers as $transformer) { + foreach ($this->config->getClientTransformers() as $transformer) { $value = $transformer->transform($value); } @@ -1066,55 +959,16 @@ class Form implements \IteratorAggregate, FormInterface */ private function clientToNorm($value) { - if (!$this->clientTransformers) { + $transformers = $this->config->getClientTransformers(); + + if (!$transformers) { return '' === $value ? null : $value; } - for ($i = count($this->clientTransformers) - 1; $i >= 0; --$i) { - $value = $this->clientTransformers[$i]->reverseTransform($value); + for ($i = count($transformers) - 1; $i >= 0; --$i) { + $value = $transformers[$i]->reverseTransform($value); } return $value; } - - /** - * Validates whether the given variable is a valid form name. - * - * @param string $name The tested form name. - * - * @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'); - } - - if (!self::isValidName($name)) { - throw new \InvalidArgumentException(sprintf( - 'The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").', - $name - )); - } - } - - /** - * 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) - { - return '' === $name || preg_match('/^[a-zA-Z0-9_][a-zA-Z0-9_\-:]*$/D', $name); - } } diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 01173e9a4b..6ee94014a5 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -18,101 +18,37 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** + * A builder for creating {@link Form} instances. + * * @author Bernhard Schussek */ -class FormBuilder +class FormBuilder extends FormConfig { /** - * @var string - */ - private $name; - - /** - * The form data in application format - * @var mixed - */ - private $appData; - - /** - * The event dispatcher + * The form factory. * - * @var EventDispatcherInterface - */ - private $dispatcher; - - /** - * The form factory * @var FormFactoryInterface */ private $factory; - /** - * @var Boolean - */ - private $disabled; - - /** - * @var Boolean - */ - private $required; - - /** - * The transformers for transforming from normalized to client format and - * back - * @var array An array of DataTransformerInterface - */ - private $clientTransformers = array(); - - /** - * The transformers for transforming from application to normalized format - * and back - * @var array An array of DataTransformerInterface - */ - private $normTransformers = array(); - - /** - * @var array An array of FormValidatorInterface - */ - private $validators = array(); - - /** - * Key-value store for arbitrary attributes attached to the form - * @var array - */ - private $attributes = array(); - - /** - * @var array An array of FormTypeInterface - */ - private $types = array(); - /** * @var string */ private $dataClass; /** - * The children of the form + * The children of the form builder. + * * @var array */ private $children = array(); /** - * @var DataMapperInterface + * The data of children who haven't been converted to form builders yet. + * + * @var array */ - private $dataMapper; - - /** - * Whether added errors should bubble up to the parent - * @var Boolean - */ - private $errorBubbling; - - /** - * Data used for the client data when no value is bound - * @var mixed - */ - private $emptyData = ''; + private $unresolvedChildren = array(); private $currentLoadingType; @@ -132,13 +68,9 @@ class FormBuilder */ public function __construct($name, FormFactoryInterface $factory, EventDispatcherInterface $dispatcher, $dataClass = null) { - $name = (string) $name; + parent::__construct($name, $dispatcher); - Form::validateName($name); - - $this->name = $name; $this->factory = $factory; - $this->dispatcher = $dispatcher; $this->dataClass = $dataClass; } @@ -152,383 +84,6 @@ class FormBuilder return $this->factory; } - /** - * Returns the name of the form. - * - * @return string The form name - */ - public function getName() - { - return $this->name; - } - - /** - * Updates the field with default data. - * - * @param array $appData The data formatted as expected for the underlying object - * - * @return FormBuilder The current builder - */ - public function setData($appData) - { - $this->appData = $appData; - - return $this; - } - - /** - * Returns the data in the format needed for the underlying object. - * - * @return mixed - */ - public function getData() - { - return $this->appData; - } - - /** - * Set whether the form is disabled. - * - * @param Boolean $disabled Whether the form is disabled - * - * @return FormBuilder The current builder - */ - public function setDisabled($disabled) - { - $this->disabled = (Boolean) $disabled; - - return $this; - } - - /** - * Returns whether the form is disabled. - * - * @return Boolean Whether the form is disabled - */ - public function getDisabled() - { - return $this->disabled; - } - - /** - * Sets whether this field is required to be filled out when bound. - * - * @param Boolean $required - * - * @return FormBuilder The current builder - */ - public function setRequired($required) - { - $this->required = (Boolean) $required; - - return $this; - } - - /** - * Returns whether this field is required to be filled out when bound. - * - * @return Boolean Whether this field is required - */ - public function getRequired() - { - return $this->required; - } - - /** - * Sets whether errors bubble up to the parent. - * - * @param type $errorBubbling - * - * @return FormBuilder The current builder - */ - public function setErrorBubbling($errorBubbling) - { - $this->errorBubbling = null === $errorBubbling ? null : (Boolean) $errorBubbling; - - return $this; - } - - /** - * Returns whether errors bubble up to the parent. - * - * @return Boolean - */ - public function getErrorBubbling() - { - return $this->errorBubbling; - } - - /** - * Adds a validator to the form. - * - * @param FormValidatorInterface $validator The validator - * - * @return FormBuilder The current builder - * - * @deprecated Deprecated since version 2.1, to be removed in 2.3. - */ - public function addValidator(FormValidatorInterface $validator) - { - $this->validators[] = $validator; - - return $this; - } - - /** - * Returns the validators used by the form. - * - * @return array An array of FormValidatorInterface - * - * @deprecated Deprecated since version 2.1, to be removed in 2.3. - */ - public function getValidators() - { - return $this->validators; - } - - /** - * Adds an event listener for events on this field - * - * @see Symfony\Component\EventDispatcher\EventDispatcherInterface::addListener - * - * @return FormBuilder The current builder - */ - public function addEventListener($eventName, $listener, $priority = 0) - { - $this->dispatcher->addListener($eventName, $listener, $priority); - - return $this; - } - - /** - * Adds an event subscriber for events on this field - * - * @see Symfony\Component\EventDispatcher\EventDispatcherInterface::addSubscriber - * - * @return FormBuilder The current builder - */ - public function addEventSubscriber(EventSubscriberInterface $subscriber) - { - $this->dispatcher->addSubscriber($subscriber); - - return $this; - } - - /** - * Appends a transformer to the normalization transformer chain - * - * @param DataTransformerInterface $normTransformer - * - * @return FormBuilder The current builder - */ - public function appendNormTransformer(DataTransformerInterface $normTransformer) - { - $this->normTransformers[] = $normTransformer; - - return $this; - } - - /** - * Prepends a transformer to the normalization transformer chain - * - * @param DataTransformerInterface $normTransformer - * - * @return FormBuilder The current builder - */ - public function prependNormTransformer(DataTransformerInterface $normTransformer) - { - array_unshift($this->normTransformers, $normTransformer); - - return $this; - } - - /** - * Clears the normalization transformers. - * - * @return FormBuilder The current builder - */ - public function resetNormTransformers() - { - $this->normTransformers = array(); - - return $this; - } - - /** - * Returns all the normalization transformers. - * - * @return array An array of DataTransformerInterface - */ - public function getNormTransformers() - { - return $this->normTransformers; - } - - /** - * Appends a transformer to the client transformer chain - * - * @param DataTransformerInterface $clientTransformer - * - * @return FormBuilder The current builder - */ - public function appendClientTransformer(DataTransformerInterface $clientTransformer) - { - $this->clientTransformers[] = $clientTransformer; - - return $this; - } - - /** - * Prepends a transformer to the client transformer chain - * - * @param DataTransformerInterface $clientTransformer - * - * @return FormBuilder The current builder - */ - public function prependClientTransformer(DataTransformerInterface $clientTransformer) - { - array_unshift($this->clientTransformers, $clientTransformer); - - return $this; - } - - /** - * Clears the client transformers. - * - * @return FormBuilder The current builder - */ - public function resetClientTransformers() - { - $this->clientTransformers = array(); - - return $this; - } - - /** - * Returns all the client transformers. - * - * @return array An array of DataTransformerInterface - */ - public function getClientTransformers() - { - return $this->clientTransformers; - } - - /** - * Sets the value for an attribute. - * - * @param string $name The name of the attribute - * @param string $value The value of the attribute - * - * @return FormBuilder The current builder - */ - public function setAttribute($name, $value) - { - $this->attributes[$name] = $value; - - return $this; - } - - /** - * Returns the value of the attributes with the given name. - * - * @param string $name The name of the attribute - */ - public function getAttribute($name) - { - return $this->attributes[$name]; - } - - /** - * Returns whether the form has an attribute with the given name. - * - * @param string $name The name of the attribute - */ - public function hasAttribute($name) - { - return isset($this->attributes[$name]); - } - - /** - * Returns all the attributes. - * - * @return array An array of attributes - */ - public function getAttributes() - { - return $this->attributes; - } - - /** - * Sets the data mapper used by the form. - * - * @param DataMapperInterface $dataMapper - * - * @return FormBuilder The current builder - */ - public function setDataMapper(DataMapperInterface $dataMapper = null) - { - $this->dataMapper = $dataMapper; - - return $this; - } - - /** - * Returns the data mapper used by the form. - * - * @return array An array of DataMapperInterface - */ - public function getDataMapper() - { - return $this->dataMapper; - } - - /** - * Set the types. - * - * @param array $types An array FormTypeInterface - * - * @return FormBuilder The current builder - */ - public function setTypes(array $types) - { - $this->types = $types; - - return $this; - } - - /** - * Return the types. - * - * @return array An array of FormTypeInterface - */ - public function getTypes() - { - return $this->types; - } - - /** - * Sets the data used for the client data when no value is bound. - * - * @param mixed $emptyData - */ - public function setEmptyData($emptyData) - { - $this->emptyData = $emptyData; - - return $this; - } - - /** - * Returns the data used for the client data when no value is bound. - * - * @return mixed - */ - public function getEmptyData() - { - return $this->emptyData; - } - /** * Adds a new field to this group. A field must have a unique name within * the group. Otherwise the existing field is overwritten. @@ -540,7 +95,7 @@ class FormBuilder * @param string|FormTypeInterface $type * @param array $options * - * @return FormBuilder The current builder + * @return FormBuilder The builder object. */ public function add($child, $type = null, array $options = array()) { @@ -548,6 +103,9 @@ class FormBuilder $child->setParent($this); $this->children[$child->getName()] = $child; + // In case an unresolved child with the same name exists + unset($this->unresolvedChildren[$child->getName()]); + return $this; } @@ -563,9 +121,9 @@ class FormBuilder throw new CircularReferenceException(is_string($type) ? $this->getFormFactory()->getType($type) : $type); } - $this->children[$child] = array( - 'type' => $type, - 'options' => $options, + $this->unresolvedChildren[$child] = array( + 'type' => $type, + 'options' => $options, ); return $this; @@ -578,7 +136,7 @@ class FormBuilder * @param string|FormTypeInterface $type The type of the form or null if name is a property * @param array $options The options * - * @return FormBuilder The builder + * @return FormBuilder The created builder. */ public function create($name, $type = null, array $options = array()) { @@ -604,19 +162,15 @@ class FormBuilder */ public function get($name) { - if (!isset($this->children[$name])) { - throw new FormException(sprintf('The field "%s" does not exist', $name)); + if (isset($this->unresolvedChildren[$name])) { + return $this->resolveChild($name); } - if (!$this->children[$name] instanceof FormBuilder) { - $this->children[$name] = $this->create( - $name, - $this->children[$name]['type'], - $this->children[$name]['options'] - ); + if (isset($this->children[$name])) { + return $this->children[$name]; } - return $this->children[$name]; + throw new FormException(sprintf('The child with the name "%s" does not exist.', $name)); } /** @@ -624,10 +178,14 @@ class FormBuilder * * @param string $name * - * @return FormBuilder The current builder + * @return FormBuilder The builder object. */ public function remove($name) { + if (isset($this->unresolvedChildren[$name])) { + unset($this->unresolvedChildren[$name]); + } + if (isset($this->children[$name])) { if ($this->children[$name] instanceof self) { $this->children[$name]->setParent(null); @@ -647,7 +205,15 @@ class FormBuilder */ public function has($name) { - return isset($this->children[$name]); + if (isset($this->unresolvedChildren[$name])) { + return true; + } + + if (isset($this->children[$name])) { + return true; + } + + return false; } /** @@ -657,6 +223,8 @@ class FormBuilder */ public function all() { + $this->resolveChildren(); + return $this->children; } @@ -667,30 +235,15 @@ class FormBuilder */ public function getForm() { - $instance = new Form( - $this->getName(), - $this->buildDispatcher(), - $this->getTypes(), - $this->getClientTransformers(), - $this->getNormTransformers(), - $this->getDataMapper(), - $this->getValidators(), - $this->getRequired(), - $this->getDisabled(), - $this->getErrorBubbling(), - $this->getEmptyData(), - $this->getAttributes() - ); + $this->resolveChildren(); - foreach ($this->buildChildren() as $child) { - $instance->add($child); + $form = new Form($this); + + foreach ($this->children as $child) { + $form->add($child->getForm()); } - if (null !== $this->getData()) { - $instance->setData($this->getData()); - } - - return $instance; + return $form; } public function setCurrentLoadingType($type) @@ -713,7 +266,7 @@ class FormBuilder * * @param FormBuilder $parent The parent builder * - * @return FormBuilder The current builder + * @return FormBuilder The builder object. */ public function setParent(FormBuilder $parent = null) { @@ -723,32 +276,31 @@ class FormBuilder } /** - * Returns the event dispatcher. + * Converts an unresolved child into a {@link FormBuilder} instance. * - * @return type + * @param string $name The name of the unresolved child. + * + * @return FormBuilder The created instance. */ - protected function buildDispatcher() + private function resolveChild($name) { - return $this->dispatcher; + $info = $this->unresolvedChildren[$name]; + $child = $this->create($name, $info['type'], $info['options']); + $this->children[$name] = $child; + unset($this->unresolvedChildren[$name]); + + return $child; } /** - * Creates the children. - * - * @return array An array of Form + * Converts all unresolved children into {@link FormBuilder} instances. */ - protected function buildChildren() + private function resolveChildren() { - $children = array(); - - foreach ($this->children as $name => $builder) { - if (!$builder instanceof FormBuilder) { - $builder = $this->create($name, $builder['type'], $builder['options']); - } - - $children[$builder->getName()] = $builder->getForm(); + foreach ($this->unresolvedChildren as $name => $info) { + $this->children[$name] = $this->create($name, $info['type'], $info['options']); } - return $children; + $this->unresolvedChildren = array(); } } diff --git a/src/Symfony/Component/Form/FormConfig.php b/src/Symfony/Component/Form/FormConfig.php new file mode 100644 index 0000000000..7aa31f5d45 --- /dev/null +++ b/src/Symfony/Component/Form/FormConfig.php @@ -0,0 +1,524 @@ + + * + * 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\UnexpectedTypeException; +use Symfony\Component\Form\Util\PropertyPath; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * A basic form configuration. + * + * @author Bernhard Schussek + */ +class FormConfig implements FormConfigInterface +{ + /** + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + private $dispatcher; + + /** + * @var string + */ + private $name; + + /** + * @var array + */ + private $types = array(); + + /** + * @var array + */ + private $clientTransformers = array(); + + /** + * @var array + */ + private $normTransformers = array(); + + /** + * @var DataMapperInterface + */ + private $dataMapper; + + /** + * @var FormValidatorInterface + */ + private $validators = array(); + + /** + * @var Boolean + */ + private $required; + + /** + * @var Boolean + */ + private $disabled; + + /** + * @var Boolean + */ + private $errorBubbling; + + /** + * @var mixed + */ + private $emptyData; + + /** + * @var array + */ + private $attributes = array(); + + /** + * @var mixed + */ + private $data; + + /** + * Creates an empty form configuration. + * + * @param string $name The form name. + * @param EventDispatcherInterface $dispatcher The event dispatcher. + */ + public function __construct($name, EventDispatcherInterface $dispatcher) + { + $name = (string) $name; + + self::validateName($name); + + $this->name = $name; + $this->dispatcher = $dispatcher; + } + + /** + * Adds an event listener to an event on this form. + * + * @param string $eventName The name of the event to listen to. + * @param callable $listener The listener to execute. + * @param integer $priority The priority of the listener. Listeners + * with a higher priority are called before + * listeners with a lower priority. + * + * @return self The configuration object. + */ + public function addEventListener($eventName, $listener, $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + + return $this; + } + + /** + * Adds an event subscriber for events on this form. + * + * @param EventSubscriberInterface $subscriber The subscriber to attach. + * + * @return self The configuration object. + */ + public function addEventSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + + return $this; + } + + /** + * Adds a validator to the form. + * + * @param FormValidatorInterface $validator The validator. + * + * @return self The configuration object. + * + * @deprecated Deprecated since version 2.1, to be removed in 2.3. + */ + public function addValidator(FormValidatorInterface $validator) + { + $this->validators[] = $validator; + + return $this; + } + + /** + * Appends a transformer to the client transformer chain + * + * @param DataTransformerInterface $clientTransformer + * + * @return self The configuration object. + */ + public function appendClientTransformer(DataTransformerInterface $clientTransformer) + { + $this->clientTransformers[] = $clientTransformer; + + return $this; + } + + /** + * Prepends a transformer to the client transformer chain. + * + * @param DataTransformerInterface $clientTransformer + * + * @return self The configuration object. + */ + public function prependClientTransformer(DataTransformerInterface $clientTransformer) + { + array_unshift($this->clientTransformers, $clientTransformer); + + return $this; + } + + /** + * Clears the client transformers. + * + * @return self The configuration object. + */ + public function resetClientTransformers() + { + $this->clientTransformers = array(); + + return $this; + } + + /** + * Appends a transformer to the normalization transformer chain + * + * @param DataTransformerInterface $normTransformer + * + * @return self The configuration object. + */ + public function appendNormTransformer(DataTransformerInterface $normTransformer) + { + $this->normTransformers[] = $normTransformer; + + return $this; + } + + /** + * Prepends a transformer to the normalization transformer chain + * + * @param DataTransformerInterface $normTransformer + * + * @return self The configuration object. + */ + public function prependNormTransformer(DataTransformerInterface $normTransformer) + { + array_unshift($this->normTransformers, $normTransformer); + + return $this; + } + + /** + * Clears the normalization transformers. + * + * @return self The configuration object. + */ + public function resetNormTransformers() + { + $this->normTransformers = array(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getEventDispatcher() + { + return $this->dispatcher; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getTypes() + { + return $this->types; + } + + /** + * {@inheritdoc} + */ + public function getClientTransformers() + { + return $this->clientTransformers; + } + + /** + * {@inheritdoc} + */ + public function getNormTransformers() + { + return $this->normTransformers; + } + + /** + * Returns the data mapper of the form. + * + * @return DataMapperInterface The data mapper. + */ + public function getDataMapper() + { + return $this->dataMapper; + } + + /** + * {@inheritdoc} + */ + public function getValidators() + { + return $this->validators; + } + + /** + * {@inheritdoc} + */ + public function getRequired() + { + return $this->required; + } + + /** + * {@inheritdoc} + */ + public function getDisabled() + { + return $this->disabled; + } + + /** + * {@inheritdoc} + */ + public function getErrorBubbling() + { + return $this->errorBubbling; + } + + /** + * {@inheritdoc} + */ + public function getEmptyData() + { + return $this->emptyData; + } + + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + /** + * {@inheritdoc} + */ + function getAttribute($name) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + } + + /** + * {@inheritdoc} + */ + public function getData() + { + return $this->data; + } + + /** + * Sets the value for an attribute. + * + * @param string $name The name of the attribute + * @param string $value The value of the attribute + * + * @return self The configuration object. + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + + return $this; + } + + /** + * Sets the attributes. + * + * @param array $attributes The attributes. + * + * @return self The configuration object. + */ + public function setAttributes(array $attributes) + { + $this->attributes = $attributes; + + return $this; + } + + /** + * Sets the data mapper used by the form. + * + * @param DataMapperInterface $dataMapper + * + * @return self The configuration object. + */ + public function setDataMapper(DataMapperInterface $dataMapper = null) + { + $this->dataMapper = $dataMapper; + + return $this; + } + + /** + * Set whether the form is disabled. + * + * @param Boolean $disabled Whether the form is disabled + * + * @return self The configuration object. + */ + public function setDisabled($disabled) + { + $this->disabled = (Boolean) $disabled; + + return $this; + } + + /** + * Sets the data used for the client data when no value is bound. + * + * @param mixed $emptyData The empty data. + * + * @return self The configuration object. + */ + public function setEmptyData($emptyData) + { + $this->emptyData = $emptyData; + + return $this; + } + + /** + * Sets whether errors bubble up to the parent. + * + * @param Boolean $errorBubbling + * + * @return self The configuration object. + */ + public function setErrorBubbling($errorBubbling) + { + $this->errorBubbling = null === $errorBubbling ? null : (Boolean) $errorBubbling; + + return $this; + } + + /** + * Sets whether this field is required to be filled out when bound. + * + * @param Boolean $required + * + * @return self The configuration object. + */ + public function setRequired($required) + { + $this->required = (Boolean) $required; + + return $this; + } + + /** + * Set the types. + * + * @param array $types An array FormTypeInterface + * + * @return self The configuration object. + */ + public function setTypes(array $types) + { + $this->types = $types; + + return $this; + } + + /** + * Sets the initial data of the form. + * + * @param array $data The data of the form in application format. + * + * @return self The configuration object. + */ + public function setData($data) + { + $this->data = $data; + + return $this; + } + + /** + * Validates whether the given variable is a valid form name. + * + * @param string $name The tested form name. + * + * @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'); + } + + if (!self::isValidName($name)) { + throw new \InvalidArgumentException(sprintf( + 'The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").', + $name + )); + } + } + + /** + * 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) + { + return '' === $name || preg_match('/^[a-zA-Z0-9_][a-zA-Z0-9_\-:]*$/D', $name); + } +} diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php new file mode 100644 index 0000000000..363dae5575 --- /dev/null +++ b/src/Symfony/Component/Form/FormConfigInterface.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * The configuration of a {@link Form} object. + * + * @author Bernhard Schussek + */ +interface FormConfigInterface +{ + /** + * Returns the event dispatcher used to dispatch form events. + * + * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface The dispatcher. + */ + function getEventDispatcher(); + + /** + * Returns the name of the form used as HTTP parameter. + * + * @return string The form name. + */ + function getName(); + + /** + * Returns the form types used to construct the form. + * + * @return array An array of {@link FormTypeInterface} instances. + */ + function getTypes(); + + /** + * Returns the client transformers of the form. + * + * @return array An array of {@link DataTransformerInterface} instances. + */ + function getClientTransformers(); + + /** + * Returns the view transformers of the form. + * + * @return array An array of {@link DataTransformerInterface} instances. + */ + function getNormTransformers(); + + /** + * Returns the data mapper of the form. + * + * @return DataMapperInterface The data mapper. + */ + function getDataMapper(); + + /** + * Returns the validators of the form. + * + * @return FormValidatorInterface The form validator. + * + * @deprecated Deprecated since version 2.1, to be removed in 2.3. + */ + function getValidators(); + + /** + * Returns whether the form is required. + * + * @return Boolean Whether the form is required. + */ + function getRequired(); + + /** + * Returns whether the form is disabled. + * + * @return Boolean Whether the form is disabled. + */ + function getDisabled(); + + /** + * Returns whether errors attached to the form will bubble to its parent. + * + * @return Boolean Whether errors will bubble up. + */ + function getErrorBubbling(); + + /** + * Returns the data that should be returned when the form is empty. + * + * @return mixed|\Closure The data returned if the form is empty. + */ + function getEmptyData(); + + /** + * Returns additional attributes of the form. + * + * @return array An array of key-value combinations. + */ + function getAttributes(); + + /** + * Returns whether the attribute with the given name exists. + * + * @param string $name The attribute name. + * + * @return Boolean Whether the attribute exists. + */ + function hasAttribute($name); + + /** + * Returns the value of the given attribute. + * + * @param string $name The attribute name. + * + * @return mixed The attribute value. + */ + function getAttribute($name); + + /** + * Returns the initial data of the form. + * + * @return mixed The initial form data. + */ + function getData(); +} diff --git a/src/Symfony/Component/Form/ImmutableFormConfig.php b/src/Symfony/Component/Form/ImmutableFormConfig.php new file mode 100644 index 0000000000..e457322327 --- /dev/null +++ b/src/Symfony/Component/Form/ImmutableFormConfig.php @@ -0,0 +1,232 @@ + + * + * 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\UnexpectedTypeException; +use Symfony\Component\Form\Util\PropertyPath; + +/** + * An immutable form configuration. + * + * @author Bernhard Schussek + */ +class ImmutableFormConfig implements FormConfigInterface +{ + /** + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + private $dispatcher; + + /** + * @var string + */ + private $name; + + /** + * @var array + */ + private $types; + + /** + * @var array + */ + private $clientTransformers; + + /** + * @var array + */ + private $normTransformers; + + /** + * @var DataMapperInterface + */ + private $dataMapper; + + /** + * @var FormValidatorInterface + */ + private $validators; + + /** + * @var Boolean + */ + private $required; + + /** + * @var Boolean + */ + private $disabled; + + /** + * @var Boolean + */ + private $errorBubbling; + + /** + * @var mixed + */ + private $emptyData; + + /** + * @var array + */ + private $attributes; + + /** + * @var mixed + */ + private $data; + + /** + * Creates an immutable copy of a given configuration. + * + * @param FormConfigInterface $config The configuration to copy. + */ + public function __construct(FormConfigInterface $config) + { + $this->dispatcher = $config->getEventDispatcher(); + $this->name = $config->getName(); + $this->types = $config->getTypes(); + $this->clientTransformers = $config->getClientTransformers(); + $this->normTransformers = $config->getNormTransformers(); + $this->dataMapper = $config->getDataMapper(); + $this->validators = $config->getValidators(); + $this->required = $config->getRequired(); + $this->disabled = $config->getDisabled(); + $this->errorBubbling = $config->getErrorBubbling(); + $this->emptyData = $config->getEmptyData(); + $this->data = $config->getData(); + $this->attributes = $config->getAttributes(); + } + + /** + * {@inheritdoc} + */ + public function getEventDispatcher() + { + return $this->dispatcher; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getTypes() + { + return $this->types; + } + + /** + * {@inheritdoc} + */ + public function getClientTransformers() + { + return $this->clientTransformers; + } + + /** + * {@inheritdoc} + */ + public function getNormTransformers() + { + return $this->normTransformers; + } + + /** + * Returns the data mapper of the form. + * + * @return DataMapperInterface The data mapper. + */ + public function getDataMapper() + { + return $this->dataMapper; + } + + /** + * {@inheritdoc} + */ + public function getValidators() + { + return $this->validators; + } + + /** + * {@inheritdoc} + */ + public function getRequired() + { + return $this->required; + } + + /** + * {@inheritdoc} + */ + public function getDisabled() + { + return $this->disabled; + } + + /** + * {@inheritdoc} + */ + public function getErrorBubbling() + { + return $this->errorBubbling; + } + + /** + * {@inheritdoc} + */ + public function getEmptyData() + { + return $this->emptyData; + } + + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + /** + * {@inheritdoc} + */ + function getAttribute($name) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + } + + /** + * {@inheritdoc} + */ + public function getData() + { + return $this->data; + } +} diff --git a/src/Symfony/Component/Form/Tests/FormBuilderTest.php b/src/Symfony/Component/Form/Tests/FormBuilderTest.php index 2e4947745c..f21ccd0e14 100644 --- a/src/Symfony/Component/Form/Tests/FormBuilderTest.php +++ b/src/Symfony/Component/Form/Tests/FormBuilderTest.php @@ -35,37 +35,6 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase $this->builder = null; } - public function getHtml4Ids() - { - // The full list is tested in FormTest, since both Form and FormBuilder - // use the same implementation internally - return array( - array('#', false), - array('a ', false), - array("a\t", false), - array("a\n", false), - array('a.', false), - ); - } - - /** - * @dataProvider getHtml4Ids - */ - public function testConstructAcceptsOnlyNamesValidAsIdsInHtml4($name, $accepted) - { - try { - new FormBuilder($name, $this->factory, $this->dispatcher); - if (!$accepted) { - $this->fail(sprintf('The value "%s" should not be accepted', $name)); - } - } catch (\InvalidArgumentException $e) { - // if the value was not accepted, but should be, rethrow exception - if ($accepted) { - throw $e; - } - } - } - /** * Changing the name is not allowed, otherwise the name and property path * are not synchronized anymore @@ -111,6 +80,11 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase public function testAll() { + $this->factory->expects($this->once()) + ->method('createNamedBuilder') + ->with('text', 'foo') + ->will($this->returnValue(new FormBuilder('foo', $this->factory, $this->dispatcher))); + $this->assertCount(0, $this->builder->all()); $this->assertFalse($this->builder->has('foo')); @@ -120,9 +94,6 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase $this->assertTrue($this->builder->has('foo')); $this->assertCount(1, $children); $this->assertArrayHasKey('foo', $children); - - $foo = $children['foo']; - $this->assertEquals('text', $foo['type']); } public function testAddFormType() @@ -157,7 +128,7 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase public function testGetUnknown() { - $this->setExpectedException('Symfony\Component\Form\Exception\FormException', 'The field "foo" does not exist'); + $this->setExpectedException('Symfony\Component\Form\Exception\FormException', 'The child with the name "foo" does not exist.'); $this->builder->get('foo'); } diff --git a/src/Symfony/Component/Form/Tests/FormConfigTest.php b/src/Symfony/Component/Form/Tests/FormConfigTest.php new file mode 100644 index 0000000000..9d8a5f97e6 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/FormConfigTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests; + +/** + * @author Bernhard Schussek + */ +use Symfony\Component\Form\FormConfig; + +class FormConfigTest extends \PHPUnit_Framework_TestCase +{ + public function getHtml4Ids() + { + return array( + array('a0', true), + array('a9', true), + array('z0', true), + array('A0', true), + array('A9', true), + array('Z0', true), + array('#', false), + array('a#', false), + array('a$', false), + array('a%', false), + array('a ', false), + array("a\t", false), + array("a\n", false), + array('a-', true), + array('a_', true), + array('a:', true), + // Periods are allowed by the HTML4 spec, but disallowed by us + // because they break the generated property paths + array('a.', false), + // Contrary to the HTML4 spec, we allow names starting with a + // number, otherwise naming fields by collection indices is not + // possible. + // For root forms, leading digits will be stripped from the + // "id" attribute to produce valid HTML4. + array('0', true), + array('9', true), + // Contrary to the HTML4 spec, we allow names starting with an + // underscore, since this is already a widely used practice in + // Symfony2. + // For root forms, leading underscores will be stripped from the + // "id" attribute to produce valid HTML4. + array('_', true), + ); + } + + /** + * @dataProvider getHtml4Ids + */ + public function testSetNameAcceptsOnlyNamesValidAsIdsInHtml4($name, $accepted) + { + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + + try { + new FormConfig($name, $dispatcher); + if (!$accepted) { + $this->fail(sprintf('The value "%s" should not be accepted', $name)); + } + } catch (\InvalidArgumentException $e) { + // if the value was not accepted, but should be, rethrow exception + if ($accepted) { + throw $e; + } + } + } +} diff --git a/src/Symfony/Component/Form/Tests/FormTest.php b/src/Symfony/Component/Form/Tests/FormTest.php index b7b7cb0803..aa11b30360 100644 --- a/src/Symfony/Component/Form/Tests/FormTest.php +++ b/src/Symfony/Component/Form/Tests/FormTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Tests; use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormConfig; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormError; @@ -51,72 +52,6 @@ class FormTest extends \PHPUnit_Framework_TestCase $this->form = null; } - /** - * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException - */ - public function testConstructExpectsValidValidators() - { - $validators = array(new \stdClass()); - - new Form('name', $this->dispatcher, array(), array(), array(), null, $validators); - } - - public function getHtml4Ids() - { - return array( - array('a0', true), - array('a9', true), - array('z0', true), - array('A0', true), - array('A9', true), - array('Z0', true), - array('#', false), - array('a#', false), - array('a$', false), - array('a%', false), - array('a ', false), - array("a\t", false), - array("a\n", false), - array('a-', true), - array('a_', true), - array('a:', true), - // Periods are allowed by the HTML4 spec, but disallowed by us - // because they break the generated property paths - array('a.', false), - // Contrary to the HTML4 spec, we allow names starting with a - // number, otherwise naming fields by collection indices is not - // possible. - // For root forms, leading digits will be stripped from the - // "id" attribute to produce valid HTML4. - array('0', true), - array('9', true), - // Contrary to the HTML4 spec, we allow names starting with an - // underscore, since this is already a widely used practice in - // Symfony2. - // For root forms, leading underscores will be stripped from the - // "id" attribute to produce valid HTML4. - array('_', true), - ); - } - - /** - * @dataProvider getHtml4Ids - */ - public function testConstructAcceptsOnlyNamesValidAsIdsInHtml4($name, $accepted) - { - try { - new Form($name, $this->dispatcher); - if (!$accepted) { - $this->fail(sprintf('The value "%s" should not be accepted', $name)); - } - } catch (\InvalidArgumentException $e) { - // if the value was not accepted, but should be, rethrow exception - if ($accepted) { - throw $e; - } - } - } - public function testDataIsInitializedEmpty() { $norm = new FixedDataTransformer(array( @@ -126,7 +61,10 @@ class FormTest extends \PHPUnit_Framework_TestCase 'foo' => 'bar', )); - $form = new Form('name', $this->dispatcher, array(), array($client), array($norm)); + $config = new FormConfig('name', $this->dispatcher); + $config->appendClientTransformer($client); + $config->appendNormTransformer($norm); + $form = new Form($config); $this->assertNull($form->getData()); $this->assertSame('foo', $form->getNormData()); @@ -1259,7 +1197,7 @@ class FormTest extends \PHPUnit_Framework_TestCase /** * @expectedException Symfony\Component\Form\Exception\FormException - * @expectedExceptionMessage Form with empty name can not have parent form. + * @expectedExceptionMessage A form with an empty name cannot have a parent form. */ public function testFormCannotHaveEmptyNameNotInRootLevel() {