[Form] Fixed option support in Form component
This commit is contained in:
parent
70d49c3c2c
commit
b7330456b6
|
@ -256,7 +256,6 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
|
|||
* forms now don't create an empty object anymore if they are completely
|
||||
empty and not required. The empty value for such forms is null.
|
||||
* added constant Guess::VERY_HIGH_CONFIDENCE
|
||||
* FormType::getDefaultOptions() now sees default options defined by parent types
|
||||
* [BC BREAK] FormType::getParent() does not see default options anymore
|
||||
* [BC BREAK] The methods `add`, `remove`, `setParent`, `bind` and `setData`
|
||||
in class Form now throw an exception if the form is already bound
|
||||
|
@ -266,6 +265,8 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
|
|||
"single_text" unless "with_seconds" is set to true
|
||||
* checkboxes of in an expanded multiple-choice field don't include the choice
|
||||
in their name anymore. Their names terminate with "[]" now.
|
||||
* [BC BREAK] FormType::getDefaultOptions() and FormType::getAllowedOptionValues()
|
||||
don't receive an options array anymore.
|
||||
|
||||
### HttpFoundation
|
||||
|
||||
|
|
|
@ -282,7 +282,7 @@
|
|||
```
|
||||
|
||||
* The options passed to the `getParent()` method of form types no longer
|
||||
contain default options.
|
||||
contain default options. They only contain the options passed by the user.
|
||||
|
||||
You should check if options exist before attempting to read their value.
|
||||
|
||||
|
@ -303,6 +303,42 @@
|
|||
return isset($options['widget']) && 'single_text' === $options['widget'] ? 'text' : 'choice';
|
||||
}
|
||||
```
|
||||
|
||||
* The methods `getDefaultOptions()` and `getAllowedOptionValues()` of form
|
||||
types no longer receive an option array.
|
||||
|
||||
You can specify options that depend on other options using closures instead.
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
public function getDefaultOptions(array $options)
|
||||
{
|
||||
$defaultOptions = array();
|
||||
|
||||
if ($options['multiple']) {
|
||||
$defaultOptions['empty_data'] = array();
|
||||
}
|
||||
|
||||
return $defaultOptions;
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
$return array(
|
||||
'empty_data' => function (Options $options, $previousValue) {
|
||||
return $options['multiple'] ? array() : $previousValue;
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The second argument `$previousValue` does not have to be specified if not
|
||||
needed.
|
||||
|
||||
* The `add()`, `remove()`, `setParent()`, `bind()` and `setData()` methods in
|
||||
the Form class now throw an exception if the form is already bound.
|
||||
|
|
|
@ -19,6 +19,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
|
|||
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
|
||||
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Options;
|
||||
|
||||
abstract class DoctrineType extends AbstractType
|
||||
{
|
||||
|
@ -42,27 +43,25 @@ abstract class DoctrineType extends AbstractType
|
|||
}
|
||||
}
|
||||
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
$defaultOptions = array(
|
||||
'em' => null,
|
||||
'class' => null,
|
||||
'property' => null,
|
||||
'query_builder' => null,
|
||||
'loader' => null,
|
||||
'group_by' => null,
|
||||
);
|
||||
$registry = $this->registry;
|
||||
$type = $this;
|
||||
|
||||
$options = array_replace($defaultOptions, $options);
|
||||
$loader = function (Options $options) use ($type, $registry) {
|
||||
if (null !== $options['query_builder']) {
|
||||
$manager = $registry->getManager($options['em']);
|
||||
|
||||
if (!isset($options['choice_list'])) {
|
||||
$manager = $this->registry->getManager($options['em']);
|
||||
|
||||
if (isset($options['query_builder'])) {
|
||||
$options['loader'] = $this->getLoader($manager, $options);
|
||||
return $type->getLoader($manager, $options['query_builder'], $options['class']);
|
||||
}
|
||||
|
||||
$defaultOptions['choice_list'] = new EntityChoiceList(
|
||||
return null;
|
||||
};
|
||||
|
||||
$choiceList = function (Options $options) use ($registry) {
|
||||
$manager = $registry->getManager($options['em']);
|
||||
|
||||
return new EntityChoiceList(
|
||||
$manager,
|
||||
$options['class'],
|
||||
$options['property'],
|
||||
|
@ -70,9 +69,17 @@ abstract class DoctrineType extends AbstractType
|
|||
$options['choices'],
|
||||
$options['group_by']
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return $defaultOptions;
|
||||
return array(
|
||||
'em' => null,
|
||||
'class' => null,
|
||||
'property' => null,
|
||||
'query_builder' => null,
|
||||
'loader' => $loader,
|
||||
'choice_list' => $choiceList,
|
||||
'group_by' => null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +89,7 @@ abstract class DoctrineType extends AbstractType
|
|||
* @param array $options
|
||||
* @return EntityLoaderInterface
|
||||
*/
|
||||
abstract protected function getLoader(ObjectManager $manager, array $options);
|
||||
abstract public function getLoader(ObjectManager $manager, $queryBuilder, $class);
|
||||
|
||||
public function getParent(array $options)
|
||||
{
|
||||
|
|
|
@ -23,12 +23,12 @@ class EntityType extends DoctrineType
|
|||
* @param array $options
|
||||
* @return ORMQueryBuilderLoader
|
||||
*/
|
||||
protected function getLoader(ObjectManager $manager, array $options)
|
||||
public function getLoader(ObjectManager $manager, $queryBuilder, $class)
|
||||
{
|
||||
return new ORMQueryBuilderLoader(
|
||||
$options['query_builder'],
|
||||
$queryBuilder,
|
||||
$manager,
|
||||
$options['class']
|
||||
$class
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Symfony\Bridge\Propel1\Form\Type;
|
|||
use Symfony\Bridge\Propel1\Form\ChoiceList\ModelChoiceList;
|
||||
use Symfony\Bridge\Propel1\Form\DataTransformer\CollectionToArrayTransformer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Options;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
|
||||
/**
|
||||
|
@ -30,8 +31,18 @@ class ModelType extends AbstractType
|
|||
}
|
||||
}
|
||||
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
$choiceList = function (Options $options) {
|
||||
return new ModelChoiceList(
|
||||
$options['class'],
|
||||
$options['property'],
|
||||
$options['choices'],
|
||||
$options['query'],
|
||||
$options['group_by']
|
||||
);
|
||||
};
|
||||
|
||||
$defaultOptions = array(
|
||||
'template' => 'choice',
|
||||
'multiple' => false,
|
||||
|
@ -40,23 +51,10 @@ class ModelType extends AbstractType
|
|||
'property' => null,
|
||||
'query' => null,
|
||||
'choices' => null,
|
||||
'choice_list' => $choiceList,
|
||||
'group_by' => null,
|
||||
'by_reference' => false,
|
||||
);
|
||||
|
||||
$options = array_replace($defaultOptions, $options);
|
||||
|
||||
if (!isset($options['choice_list'])) {
|
||||
$defaultOptions['choice_list'] = new ModelChoiceList(
|
||||
$options['class'],
|
||||
$options['property'],
|
||||
$options['choices'],
|
||||
$options['query'],
|
||||
$options['group_by']
|
||||
);
|
||||
}
|
||||
|
||||
return $defaultOptions;
|
||||
}
|
||||
|
||||
public function getParent(array $options)
|
||||
|
|
|
@ -76,7 +76,7 @@ class UserLoginFormType extends AbstractType
|
|||
/**
|
||||
* @see Symfony\Component\Form\AbstractType::getDefaultOptions()
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
/* Note: the form's intention must correspond to that for the form login
|
||||
* listener in order for the CSRF token to validate successfully.
|
||||
|
|
|
@ -96,7 +96,7 @@ abstract class AbstractType implements FormTypeInterface
|
|||
*
|
||||
* @return array The default options
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ abstract class AbstractType implements FormTypeInterface
|
|||
*
|
||||
* @return array The allowed option values
|
||||
*/
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ abstract class AbstractTypeExtension implements FormTypeExtensionInterface
|
|||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ abstract class AbstractTypeExtension implements FormTypeExtensionInterface
|
|||
*
|
||||
* @return array The allowed option values
|
||||
*/
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,320 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form;
|
||||
|
||||
use Symfony\Component\Form\Exception\OptionDefinitionException;
|
||||
use Symfony\Component\Form\Exception\InvalidOptionException;
|
||||
|
||||
/**
|
||||
* Helper for specifying and resolving inter-dependent options.
|
||||
*
|
||||
* Options are a common pattern for initializing classes in PHP. Avoiding the
|
||||
* problems related to this approach is however a non-trivial task. Usually,
|
||||
* both classes and subclasses should be able to set default option values.
|
||||
* These default options should be overridden by the options passed to the
|
||||
* constructor. Last but not least, the (default) values of some options may
|
||||
* depend on the values of other options, which themselves may depend on other
|
||||
* options and so on.
|
||||
*
|
||||
* DefaultOptions resolves these problems. It allows you to:
|
||||
*
|
||||
* - Define default option values
|
||||
* - Define options in layers that correspond to your class hierarchy. Each
|
||||
* layer may depend on the default value set in the higher layers.
|
||||
* - Define default values for options that depend on the <em>concrete</em>
|
||||
* values of other options.
|
||||
* - Resolve the concrete option values by passing the options set by the
|
||||
* user.
|
||||
*
|
||||
* You can use it in your classes by implementing the following pattern:
|
||||
*
|
||||
* <code>
|
||||
* class Car
|
||||
* {
|
||||
* protected $options;
|
||||
*
|
||||
* public function __construct(array $options)
|
||||
* {
|
||||
* $defaultOptions = new DefaultOptions();
|
||||
* $this->addDefaultOptions($defaultOptions);
|
||||
*
|
||||
* $this->options = $defaultOptions->resolve($options);
|
||||
* }
|
||||
*
|
||||
* protected function addDefaultOptions(DefaultOptions $options)
|
||||
* {
|
||||
* $options->add(array(
|
||||
* 'make' => 'VW',
|
||||
* 'year' => '1999',
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $car = new Car(array(
|
||||
* 'make' => 'Mercedes',
|
||||
* 'year' => 2005,
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* By calling add(), new default options are added to the container. The method
|
||||
* resolve() accepts an array of options passed by the user that are matched
|
||||
* against the defined options. If any option is not recognized, an exception
|
||||
* is thrown. Finally, resolve() returns the merged default and user options.
|
||||
*
|
||||
* You can now easily add or override options in subclasses:
|
||||
*
|
||||
* <code>
|
||||
* class Renault extends Car
|
||||
* {
|
||||
* protected function addDefaultOptions(DefaultOptions $options)
|
||||
* {
|
||||
* parent::addDefaultOptions($options);
|
||||
*
|
||||
* $options->add(array(
|
||||
* 'make' => 'Renault',
|
||||
* 'gear' => 'auto',
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $renault = new Renault(array(
|
||||
* 'year' => 1997,
|
||||
* 'gear' => 'manual'
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* IMPORTANT: parent::addDefaultOptions() must always be called before adding
|
||||
* new default options!
|
||||
*
|
||||
* In the previous example, it makes sense to restrict the option "gear" to
|
||||
* a set of allowed values:
|
||||
*
|
||||
* <code>
|
||||
* class Renault extends Car
|
||||
* {
|
||||
* protected function addDefaultOptions(DefaultOptions $options)
|
||||
* {
|
||||
* // ... like above ...
|
||||
*
|
||||
* $options->addAllowedValues(array(
|
||||
* 'gear' => array('auto', 'manual'),
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Fails!
|
||||
* $renault = new Renault(array(
|
||||
* 'gear' => 'v6',
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* Now it is impossible to pass a value in the "gear" option that is not
|
||||
* expected.
|
||||
*
|
||||
* Last but not least, you can define options that depend on other options.
|
||||
* For example, depending on the "make" you could preset the country that the
|
||||
* car is registered in.
|
||||
*
|
||||
* <code>
|
||||
* class Car
|
||||
* {
|
||||
* protected function addDefaultOptions(DefaultOptions $options)
|
||||
* {
|
||||
* $options->add(array(
|
||||
* 'make' => 'VW',
|
||||
* 'year' => '1999',
|
||||
* 'country' => function (Options $options) {
|
||||
* if ('VW' === $options['make']) {
|
||||
* return 'DE';
|
||||
* }
|
||||
*
|
||||
* return null;
|
||||
* },
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $car = new Car(array(
|
||||
* 'make' => 'VW', // => "country" is "DE"
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* The closure receives as its first parameter a container of class Options
|
||||
* that contains the <em>concrete</em> options determined upon resolving. The
|
||||
* closure is executed once resolve() is called.
|
||||
*
|
||||
* The closure also receives a second parameter $previousValue that contains the
|
||||
* value defined by the parent layer of the hierarchy. If the option has not
|
||||
* been defined in any parent layer, the second parameter is NULL.
|
||||
*
|
||||
* <code>
|
||||
* class Renault extends Car
|
||||
* {
|
||||
* protected function addDefaultOptions(DefaultOptions $options)
|
||||
* {
|
||||
* $options->add(array(
|
||||
* 'country' => function (Options $options, $previousValue) {
|
||||
* if ('Renault' === $options['make']) {
|
||||
* return 'FR';
|
||||
* }
|
||||
*
|
||||
* // return default value defined in Car
|
||||
* return $previousValue;
|
||||
* },
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $renault = new Renault(array(
|
||||
* 'make' => 'VW', // => "country" is still "DE"
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class DefaultOptions
|
||||
{
|
||||
/**
|
||||
* The container resolving the options.
|
||||
* @var Options
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* A list of accepted values for each option.
|
||||
* @var array
|
||||
*/
|
||||
private $allowedValues = array();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->options = new Options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default options.
|
||||
*
|
||||
* @param array $options A list of option names as keys and option values
|
||||
* as values. The option values may be closures
|
||||
* of the following signatures:
|
||||
*
|
||||
* - function (Options $options)
|
||||
* - function (Options $options, $previousValue)
|
||||
*/
|
||||
public function add(array $options)
|
||||
{
|
||||
foreach ($options as $option => $value) {
|
||||
$this->options[$option] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds allowed values for a list of options.
|
||||
*
|
||||
* @param array $allowedValues A list of option names as keys and arrays
|
||||
* with values acceptable for that option as
|
||||
* values.
|
||||
*
|
||||
* @throws InvalidOptionException If an option has not been defined for
|
||||
* which an allowed value is set.
|
||||
*/
|
||||
public function addAllowedValues(array $allowedValues)
|
||||
{
|
||||
$this->validateOptionNames(array_keys($allowedValues));
|
||||
|
||||
$this->allowedValues = array_merge_recursive($this->allowedValues, $allowedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the final option values by merging default options with user
|
||||
* options.
|
||||
*
|
||||
* @param array $userOptions The options passed by the user.
|
||||
*
|
||||
* @return array A list of options and their final values.
|
||||
*
|
||||
* @throws InvalidOptionException If any of the passed options has not
|
||||
* been defined or does not contain an
|
||||
* allowed value.
|
||||
* @throws OptionDefinitionException If a cyclic dependency is detected
|
||||
* between option closures.
|
||||
*/
|
||||
public function resolve(array $userOptions)
|
||||
{
|
||||
// Make sure this method can be called multiple times
|
||||
$options = clone $this->options;
|
||||
|
||||
$this->validateOptionNames(array_keys($userOptions));
|
||||
|
||||
// Override options set by the user
|
||||
foreach ($userOptions as $option => $value) {
|
||||
$options[$option] = $value;
|
||||
}
|
||||
|
||||
// Resolve options
|
||||
$options = iterator_to_array($options);
|
||||
|
||||
// Validate against allowed values
|
||||
$this->validateOptionValues($options);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the given option names exist and throws an exception
|
||||
* otherwise.
|
||||
*
|
||||
* @param array $optionNames A list of option names.
|
||||
*
|
||||
* @throws InvalidOptionException If any of the options has not been
|
||||
* defined.
|
||||
*/
|
||||
private function validateOptionNames(array $optionNames)
|
||||
{
|
||||
$knownOptions = $this->options->getNames();
|
||||
$diff = array_diff($optionNames, $knownOptions);
|
||||
|
||||
if (count($diff) > 0) {
|
||||
sort($knownOptions);
|
||||
sort($diff);
|
||||
}
|
||||
|
||||
if (count($diff) > 1) {
|
||||
throw new InvalidOptionException(sprintf('The options "%s" do not exist. Known options are: "%s"', implode('", "', $diff), implode('", "', $knownOptions)));
|
||||
}
|
||||
|
||||
if (count($diff) > 0) {
|
||||
throw new InvalidOptionException(sprintf('The option "%s" does not exist. Known options are: "%s"', current($diff), implode('", "', $knownOptions)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the given option values match the allowed values and
|
||||
* throws an exception otherwise.
|
||||
*
|
||||
* @param array $options A list of option values.
|
||||
*
|
||||
* @throws InvalidOptionException If any of the values does not match the
|
||||
* allowed values of the option.
|
||||
*/
|
||||
private function validateOptionValues(array $options)
|
||||
{
|
||||
foreach ($this->allowedValues as $option => $allowedValues) {
|
||||
if (!in_array($options[$option], $allowedValues, true)) {
|
||||
throw new InvalidOptionException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', $allowedValues)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Exception;
|
||||
|
||||
class InvalidOptionException extends FormException
|
||||
{
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Exception;
|
||||
|
||||
class InvalidOptionsException extends FormException
|
||||
{
|
||||
private $options;
|
||||
|
||||
public function __construct($message, array $options)
|
||||
{
|
||||
parent::__construct($message);
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Exception;
|
||||
|
||||
class MissingOptionsException extends FormException
|
||||
{
|
||||
private $options;
|
||||
|
||||
public function __construct($message, array $options)
|
||||
{
|
||||
parent::__construct($message);
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Exception;
|
||||
|
||||
class OptionDefinitionException extends FormException
|
||||
{
|
||||
}
|
|
@ -18,7 +18,7 @@ class BirthdayType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'years' => range(date('Y') - 120, date('Y')),
|
||||
|
|
|
@ -44,7 +44,7 @@ class CheckboxType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'value' => '1',
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Options;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
|
@ -152,10 +153,15 @@ class ChoiceType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
$multiple = isset($options['multiple']) && $options['multiple'];
|
||||
$expanded = isset($options['expanded']) && $options['expanded'];
|
||||
$emptyData = function (Options $options) {
|
||||
if ($options['multiple'] || $options['expanded']) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
return array(
|
||||
'multiple' => false,
|
||||
|
@ -163,8 +169,8 @@ class ChoiceType extends AbstractType
|
|||
'choice_list' => null,
|
||||
'choices' => null,
|
||||
'preferred_choices' => array(),
|
||||
'empty_data' => $multiple || $expanded ? array() : '',
|
||||
'empty_value' => $multiple || $expanded || !isset($options['empty_value']) ? null : '',
|
||||
'empty_data' => $emptyData,
|
||||
'empty_value' => null,
|
||||
'error_bubbling' => false,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ class CollectionType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'allow_add' => false,
|
||||
|
|
|
@ -20,7 +20,7 @@ class CountryType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'choices' => Locale::getDisplayCountries(\Locale::getDefault()),
|
||||
|
|
|
@ -128,7 +128,7 @@ class DateTimeType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'input' => 'datetime',
|
||||
|
@ -164,7 +164,7 @@ class DateTimeType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array(
|
||||
'input' => array(
|
||||
|
|
|
@ -161,7 +161,7 @@ class DateType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'years' => range(date('Y') - 5, date('Y') + 5),
|
||||
|
@ -188,7 +188,7 @@ class DateType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array(
|
||||
'input' => array(
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Options;
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
|
@ -129,11 +130,38 @@ class FieldType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
$defaultOptions = array(
|
||||
// Derive "data_class" option from passed "data" object
|
||||
$dataClass = function (Options $options) {
|
||||
if (is_object($options['data'])) {
|
||||
return get_class($options['data']);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Derive "empty_data" closure from "data_class" option
|
||||
$emptyData = function (Options $options) {
|
||||
$class = $options['data_class'];
|
||||
|
||||
if (null !== $class) {
|
||||
return function (FormInterface $form) use ($class) {
|
||||
if ($form->isEmpty() && !$form->isRequired()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $class();
|
||||
};
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
return array(
|
||||
'data' => null,
|
||||
'data_class' => null,
|
||||
'data_class' => $dataClass,
|
||||
'empty_data' => $emptyData,
|
||||
'trim' => true,
|
||||
'required' => true,
|
||||
'read_only' => false,
|
||||
|
@ -150,28 +178,6 @@ class FieldType extends AbstractType
|
|||
'invalid_message_parameters' => array(),
|
||||
'translation_domain' => 'messages',
|
||||
);
|
||||
|
||||
$class = isset($options['data_class']) ? $options['data_class'] : null;
|
||||
|
||||
// If no data class is set explicitly and an object is passed as data,
|
||||
// use the class of that object as data class
|
||||
if (!$class && isset($options['data']) && is_object($options['data'])) {
|
||||
$defaultOptions['data_class'] = $class = get_class($options['data']);
|
||||
}
|
||||
|
||||
if ($class) {
|
||||
$defaultOptions['empty_data'] = function (FormInterface $form) use ($class) {
|
||||
if ($form->isEmpty() && !$form->isRequired()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $class();
|
||||
};
|
||||
} else {
|
||||
$defaultOptions['empty_data'] = '';
|
||||
}
|
||||
|
||||
return $defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Options;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
|
@ -50,20 +51,23 @@ class FormType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
$defaultOptions = array(
|
||||
$emptyData = function (Options $options, $currentValue) {
|
||||
if (empty($options['data_class'])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $currentValue;
|
||||
};
|
||||
|
||||
return array(
|
||||
'empty_data' => $emptyData,
|
||||
'virtual' => false,
|
||||
// Errors in forms bubble by default, so that form errors will
|
||||
// end up as global errors in the root form
|
||||
'error_bubbling' => true,
|
||||
);
|
||||
|
||||
if (empty($options['data_class'])) {
|
||||
$defaultOptions['empty_data'] = array();
|
||||
}
|
||||
|
||||
return $defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@ class HiddenType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
// hidden fields cannot have a required attribute
|
||||
|
|
|
@ -33,7 +33,7 @@ class IntegerType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
// default precision is locale specific (usually around 3)
|
||||
|
@ -47,7 +47,7 @@ class IntegerType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array(
|
||||
'rounding_mode' => array(
|
||||
|
|
|
@ -20,7 +20,7 @@ class LanguageType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'choices' => Locale::getDisplayLanguages(\Locale::getDefault()),
|
||||
|
|
|
@ -20,7 +20,7 @@ class LocaleType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'choices' => Locale::getDisplayLocales(\Locale::getDefault()),
|
||||
|
|
|
@ -48,7 +48,7 @@ class MoneyType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'precision' => 2,
|
||||
|
|
|
@ -32,7 +32,7 @@ class NumberType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
// default precision is locale specific (usually around 3)
|
||||
|
@ -45,7 +45,7 @@ class NumberType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array(
|
||||
'rounding_mode' => array(
|
||||
|
|
|
@ -39,7 +39,7 @@ class PasswordType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'always_empty' => true,
|
||||
|
|
|
@ -28,7 +28,7 @@ class PercentType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'precision' => 0,
|
||||
|
@ -39,7 +39,7 @@ class PercentType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array(
|
||||
'type' => array(
|
||||
|
|
|
@ -39,7 +39,7 @@ class RepeatedType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'type' => 'text',
|
||||
|
|
|
@ -134,7 +134,7 @@ class TimeType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'hours' => range(0, 23),
|
||||
|
@ -161,7 +161,7 @@ class TimeType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array(
|
||||
'input' => array(
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Options;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
|
||||
|
||||
class TimezoneType extends AbstractType
|
||||
|
@ -20,20 +21,16 @@ class TimezoneType extends AbstractType
|
|||
* Stores the available timezone choices
|
||||
* @var array
|
||||
*/
|
||||
static protected $timezones;
|
||||
static private $timezones;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
$defaultOptions = array();
|
||||
|
||||
if (empty($options['choice_list']) && empty($options['choices'])) {
|
||||
$defaultOptions['choices'] = self::getTimezones();
|
||||
}
|
||||
|
||||
return $defaultOptions;
|
||||
return array(
|
||||
'choices' => self::getTimezones(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +59,7 @@ class TimezoneType extends AbstractType
|
|||
*
|
||||
* @return array The timezone choices
|
||||
*/
|
||||
static private function getTimezones()
|
||||
static public function getTimezones()
|
||||
{
|
||||
if (null === static::$timezones) {
|
||||
static::$timezones = array();
|
||||
|
|
|
@ -28,7 +28,7 @@ class UrlType extends AbstractType
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'default_protocol' => 'http',
|
||||
|
|
|
@ -15,7 +15,7 @@ use Symfony\Component\Form\AbstractTypeExtension;
|
|||
|
||||
class ChoiceTypeCsrfExtension extends AbstractTypeExtension
|
||||
{
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array('csrf_protection' => false);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ class CsrfType extends AbstractType
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'csrf_provider' => $this->csrfProvider,
|
||||
|
|
|
@ -15,7 +15,7 @@ use Symfony\Component\Form\AbstractTypeExtension;
|
|||
|
||||
class DateTypeCsrfExtension extends AbstractTypeExtension
|
||||
{
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array('csrf_protection' => false);
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'csrf_protection' => $this->enabled,
|
||||
|
|
|
@ -15,7 +15,7 @@ use Symfony\Component\Form\AbstractTypeExtension;
|
|||
|
||||
class RepeatedTypeCsrfExtension extends AbstractTypeExtension
|
||||
{
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array('csrf_protection' => false);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use Symfony\Component\Form\AbstractTypeExtension;
|
|||
|
||||
class TimeTypeCsrfExtension extends AbstractTypeExtension
|
||||
{
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array('csrf_protection' => false);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class FieldTypeValidatorExtension extends AbstractTypeExtension
|
|||
->addValidator(new DelegatingValidator($this->validator));
|
||||
}
|
||||
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'validation_groups' => null,
|
||||
|
|
|
@ -219,9 +219,9 @@ class FormFactory implements FormFactoryInterface
|
|||
|
||||
$builder = null;
|
||||
$types = array();
|
||||
$defaultOptions = array();
|
||||
$optionValues = array();
|
||||
$passedOptions = $options;
|
||||
$knownOptions = array();
|
||||
$defaultOptions = new DefaultOptions();
|
||||
|
||||
// Bottom-up determination of the type hierarchy
|
||||
// Start with the actual type and look for the parent type
|
||||
|
@ -249,52 +249,36 @@ class FormFactory implements FormFactoryInterface
|
|||
$type = $type->getParent($options);
|
||||
}
|
||||
|
||||
// Top-down determination of the options and default options
|
||||
// Top-down determination of the 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));
|
||||
$optionValues = array_merge_recursive($optionValues, $type->getAllowedOptionValues($options));
|
||||
$typeOptions = $type->getDefaultOptions();
|
||||
$defaultOptions->add($typeOptions);
|
||||
$defaultOptions->addAllowedValues($type->getAllowedOptionValues());
|
||||
$knownOptions = array_merge($knownOptions, array_keys($typeOptions));
|
||||
|
||||
foreach ($type->getExtensions() as $typeExtension) {
|
||||
$defaultOptions = array_replace($defaultOptions, $typeExtension->getDefaultOptions($options));
|
||||
$optionValues = array_merge_recursive($optionValues, $typeExtension->getAllowedOptionValues($options));
|
||||
$extensionOptions = $typeExtension->getDefaultOptions();
|
||||
$defaultOptions->add($extensionOptions);
|
||||
$defaultOptions->addAllowedValues($typeExtension->getAllowedOptionValues());
|
||||
$knownOptions = array_merge($knownOptions, array_keys($extensionOptions));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Resolve concrete type
|
||||
$type = end($types);
|
||||
$knownOptions = array_keys($defaultOptions);
|
||||
|
||||
// Validate options required by the factory
|
||||
$diff = array_diff(self::$requiredOptions, $knownOptions);
|
||||
|
||||
if (count($diff) > 0) {
|
||||
throw new TypeDefinitionException(sprintf('Type "%s" should support the option(s) "%s"', $type->getName(), implode('", "', $diff)));
|
||||
}
|
||||
|
||||
$diff = array_diff(array_keys($passedOptions), $knownOptions);
|
||||
|
||||
if (count($diff) > 1) {
|
||||
throw new CreationException(sprintf('The options "%s" do not exist. Known options are: "%s"', implode('", "', $diff), implode('", "', $knownOptions)));
|
||||
}
|
||||
|
||||
if (count($diff) > 0) {
|
||||
throw new CreationException(sprintf('The option "%s" does not exist. Known options are: "%s"', current($diff), implode('", "', $knownOptions)));
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
// Resolve options
|
||||
$options = $defaultOptions->resolve($options);
|
||||
|
||||
for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) {
|
||||
$builder = $types[$i]->createBuilder($name, $this, $options);
|
||||
|
|
|
@ -59,7 +59,7 @@ interface FormTypeExtensionInterface
|
|||
*
|
||||
* @return array
|
||||
*/
|
||||
function getDefaultOptions(array $options);
|
||||
function getDefaultOptions();
|
||||
|
||||
/**
|
||||
* Returns the allowed option values for each option (if any).
|
||||
|
@ -68,7 +68,7 @@ interface FormTypeExtensionInterface
|
|||
*
|
||||
* @return array The allowed option values
|
||||
*/
|
||||
function getAllowedOptionValues(array $options);
|
||||
function getAllowedOptionValues();
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -79,7 +79,7 @@ interface FormTypeInterface
|
|||
*
|
||||
* @return array The default options
|
||||
*/
|
||||
function getDefaultOptions(array $options);
|
||||
function getDefaultOptions();
|
||||
|
||||
/**
|
||||
* Returns the allowed option values for each option (if any).
|
||||
|
@ -88,7 +88,7 @@ interface FormTypeInterface
|
|||
*
|
||||
* @return array The allowed option values
|
||||
*/
|
||||
function getAllowedOptionValues(array $options);
|
||||
function getAllowedOptionValues();
|
||||
|
||||
/**
|
||||
* Returns the name of the parent type.
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* An option that is evaluated lazily using a closure.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @see DefaultOptions
|
||||
*/
|
||||
class LazyOption
|
||||
{
|
||||
/**
|
||||
* The underlying closure.
|
||||
* @var Closure
|
||||
*/
|
||||
private $closure;
|
||||
|
||||
/**
|
||||
* The previous default value of the option.
|
||||
* @var mixed
|
||||
*/
|
||||
private $previousValue;
|
||||
|
||||
/**
|
||||
* Creates a new lazy option.
|
||||
*
|
||||
* @param Closure $closure The closure used for initializing the
|
||||
* option value.
|
||||
* @param mixed $previousValue The previous value of the option. This
|
||||
* value is passed to the closure when it is
|
||||
* evaluated.
|
||||
*
|
||||
* @see evaluate()
|
||||
*/
|
||||
public function __construct(Closure $closure, $previousValue)
|
||||
{
|
||||
$this->closure = $closure;
|
||||
$this->previousValue = $previousValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the underyling closure and returns its result.
|
||||
*
|
||||
* The given Options instance is passed to the closure as first argument.
|
||||
* The previous default value set in the constructor is passed as second
|
||||
* argument.
|
||||
*
|
||||
* @param Options $options The container with all concrete options.
|
||||
*
|
||||
* @return mixed The result of the closure.
|
||||
*/
|
||||
public function evaluate(Options $options)
|
||||
{
|
||||
if ($this->previousValue instanceof self) {
|
||||
$this->previousValue = $this->previousValue->evaluate($options);
|
||||
}
|
||||
|
||||
return $this->closure->__invoke($options, $this->previousValue);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form;
|
||||
|
||||
use ArrayAccess;
|
||||
use Iterator;
|
||||
use OutOfBoundsException;
|
||||
use Symfony\Component\Form\Exception\OptionDefinitionException;
|
||||
|
||||
/**
|
||||
* Container for resolving inter-dependent options.
|
||||
*
|
||||
* Options are a common pattern for resolved classes in PHP. Avoiding the
|
||||
* problems related to this approach is however a non-trivial task. Usually,
|
||||
* both classes and subclasses should be able to set default option values.
|
||||
* These default options should be overridden by the options passed to the
|
||||
* constructor. Last but not least, the (default) values of some options may
|
||||
* depend on the values of other options, which themselves may depend on other
|
||||
* options.
|
||||
*
|
||||
* This class resolves these problems. You can use it in your classes by
|
||||
* implementing the following pattern:
|
||||
*
|
||||
* <code>
|
||||
* class Car
|
||||
* {
|
||||
* protected $options;
|
||||
*
|
||||
* public function __construct(array $options)
|
||||
* {
|
||||
* $_options = new Options();
|
||||
* $this->addDefaultOptions($_options);
|
||||
*
|
||||
* $this->options = $_options->resolve($options);
|
||||
* }
|
||||
*
|
||||
* protected function addDefaultOptions(Options $options)
|
||||
* {
|
||||
* $options->add(array(
|
||||
* 'make' => 'VW',
|
||||
* 'year' => '1999',
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $car = new Car(array(
|
||||
* 'make' => 'Mercedes',
|
||||
* 'year' => 2005,
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* By calling add(), new default options are added to the container. The method
|
||||
* resolve() accepts an array of options passed by the user that are matched
|
||||
* against the allowed options. If any option is not recognized, an exception
|
||||
* is thrown. Finally, resolve() returns the merged default and user options.
|
||||
*
|
||||
* You can now easily add or override options in subclasses:
|
||||
*
|
||||
* <code>
|
||||
* class Renault extends Car
|
||||
* {
|
||||
* protected function addDefaultOptions(Options $options)
|
||||
* {
|
||||
* parent::addDefaultOptions($options);
|
||||
*
|
||||
* $options->add(array(
|
||||
* 'make' => 'Renault',
|
||||
* 'gear' => 'auto',
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $renault = new Renault(array(
|
||||
* 'year' => 1997,
|
||||
* 'gear' => 'manual'
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* IMPORTANT: parent::addDefaultOptions() must always be called before adding
|
||||
* new options!
|
||||
*
|
||||
* In the previous example, it makes sense to restrict the option "gear" to
|
||||
* a set of allowed values:
|
||||
*
|
||||
* <code>
|
||||
* class Renault extends Car
|
||||
* {
|
||||
* protected function addDefaultOptions(Options $options)
|
||||
* {
|
||||
* // ... like above ...
|
||||
*
|
||||
* $options->addAllowedValues(array(
|
||||
* 'gear' => array('auto', 'manual'),
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Fails!
|
||||
* $renault = new Renault(array(
|
||||
* 'gear' => 'v6',
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* Now it is impossible to pass a value in the "gear" option that is not
|
||||
* expected.
|
||||
*
|
||||
* Last but not least, you can define options that depend on other options.
|
||||
* For example, depending on the "make" you could preset the country that the
|
||||
* car is registered in.
|
||||
*
|
||||
* <code>
|
||||
* class Car
|
||||
* {
|
||||
* protected function addDefaultOptions(Options $options)
|
||||
* {
|
||||
* $options->add(array(
|
||||
* 'make' => 'VW',
|
||||
* 'year' => '1999',
|
||||
* 'country' => function (Options $options) {
|
||||
* if ('VW' === $options['make']) {
|
||||
* return 'DE';
|
||||
* }
|
||||
*
|
||||
* return null;
|
||||
* },
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $car = new Car(array(
|
||||
* 'make' => 'VW', // => "country" is "DE"
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* When overriding an option with a closure in subclasses, you can make use of
|
||||
* the second parameter $parentValue in which the value defined by the parent
|
||||
* class is stored.
|
||||
*
|
||||
* <code>
|
||||
* class Renault extends Car
|
||||
* {
|
||||
* protected function addDefaultOptions(Options $options)
|
||||
* {
|
||||
* $options->add(array(
|
||||
* 'country' => function (Options $options, $parentValue) {
|
||||
* if ('Renault' === $options['make']) {
|
||||
* return 'FR';
|
||||
* }
|
||||
*
|
||||
* return $parentValue;
|
||||
* },
|
||||
* ));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $renault = new Renault(array(
|
||||
* 'make' => 'VW', // => "country" is still "DE"
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class Options implements ArrayAccess, Iterator
|
||||
{
|
||||
/**
|
||||
* A list of option values and LazyOption instances.
|
||||
* @var array
|
||||
*/
|
||||
private $options = array();
|
||||
|
||||
/**
|
||||
* A list of Boolean locks for each LazyOption.
|
||||
* @var array
|
||||
*/
|
||||
private $lock = array();
|
||||
|
||||
/**
|
||||
* Whether the options have already been resolved.
|
||||
*
|
||||
* Once resolved, no new options can be added or changed anymore.
|
||||
*
|
||||
* @var Boolean
|
||||
*/
|
||||
private $resolved = false;
|
||||
|
||||
/**
|
||||
* Returns whether the given option exists.
|
||||
*
|
||||
* @param string $option The option name.
|
||||
*
|
||||
* @return Boolean Whether the option exists.
|
||||
*
|
||||
* @see ArrayAccess::offsetExists()
|
||||
*/
|
||||
public function offsetExists($option)
|
||||
{
|
||||
return isset($this->options[$option]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the given option.
|
||||
*
|
||||
* After reading an option for the first time, this object becomes
|
||||
*
|
||||
* @param string $option The option name.
|
||||
*
|
||||
* @return mixed The option value.
|
||||
*
|
||||
* @throws OutOfBoundsException If the option does not exist.
|
||||
* @throws OptionDefinitionException If a cyclic dependency is detected
|
||||
* between two lazy options.
|
||||
*
|
||||
* @see ArrayAccess::offsetGet()
|
||||
*/
|
||||
public function offsetGet($option)
|
||||
{
|
||||
if (!array_key_exists($option, $this->options)) {
|
||||
throw new OutOfBoundsException('The option "' . $option . '" does not exist');
|
||||
}
|
||||
|
||||
$this->resolved = true;
|
||||
|
||||
if ($this->options[$option] instanceof LazyOption) {
|
||||
if ($this->lock[$option]) {
|
||||
$conflicts = array_keys(array_filter($this->lock, function ($locked) {
|
||||
return $locked;
|
||||
}));
|
||||
|
||||
throw new OptionDefinitionException('The options "' . implode('", "', $conflicts) . '" have a cyclic dependency');
|
||||
}
|
||||
|
||||
$this->lock[$option] = true;
|
||||
$this->options[$option] = $this->options[$option]->evaluate($this);
|
||||
$this->lock[$option] = false;
|
||||
}
|
||||
|
||||
return $this->options[$option];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a given option.
|
||||
*
|
||||
* @param string $option The name of the option.
|
||||
* @param mixed $value The value of the option. May be a closure with a
|
||||
* signature as defined in DefaultOptions::add().
|
||||
*
|
||||
* @throws OptionDefinitionException If options have already been read.
|
||||
* Once options are read, the container
|
||||
* becomes immutable.
|
||||
*
|
||||
* @see DefaultOptions::add()
|
||||
* @see ArrayAccess::offsetSet()
|
||||
*/
|
||||
public function offsetSet($option, $value)
|
||||
{
|
||||
// Setting is not possible once an option is read, because then lazy
|
||||
// options could manipulate the state of the object, leading to
|
||||
// inconsistent results.
|
||||
if ($this->resolved) {
|
||||
throw new OptionDefinitionException('Options cannot be set after reading options');
|
||||
}
|
||||
|
||||
$newValue = $value;
|
||||
|
||||
// If an option is a closure that should be evaluated lazily, store it
|
||||
// inside a LazyOption instance.
|
||||
if ($newValue instanceof \Closure) {
|
||||
$reflClosure = new \ReflectionFunction($newValue);
|
||||
$params = $reflClosure->getParameters();
|
||||
$isLazyOption = count($params) >= 1 && null !== $params[0]->getClass() && __CLASS__ === $params[0]->getClass()->getName();
|
||||
|
||||
if ($isLazyOption) {
|
||||
$currentValue = isset($this->options[$option]) ? $this->options[$option] : null;
|
||||
$newValue = new LazyOption($newValue, $currentValue);
|
||||
}
|
||||
|
||||
// Store locks for lazy options to detect cyclic dependencies
|
||||
$this->lock[$option] = false;
|
||||
}
|
||||
|
||||
$this->options[$option] = $newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an option with the given name.
|
||||
*
|
||||
* @param string $option The option name.
|
||||
*
|
||||
* @throws OptionDefinitionException If options have already been read.
|
||||
* Once options are read, the container
|
||||
* becomes immutable.
|
||||
*
|
||||
* @see ArrayAccess::offsetUnset()
|
||||
*/
|
||||
public function offsetUnset($option)
|
||||
{
|
||||
if ($this->resolved) {
|
||||
throw new OptionDefinitionException('Options cannot be unset after reading options');
|
||||
}
|
||||
|
||||
unset($this->options[$option]);
|
||||
unset($this->allowedValues[$option]);
|
||||
unset($this->lock[$option]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of all defined options.
|
||||
*
|
||||
* @return array An array of option names.
|
||||
*/
|
||||
public function getNames()
|
||||
{
|
||||
return array_keys($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Iterator::current()
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->offsetGet($this->key());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Iterator::next()
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
next($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Iterator::key()
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return key($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Iterator::valid()
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return null !== $this->key();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Iterator::rewind()
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
reset($this->options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests;
|
||||
|
||||
use Symfony\Component\Form\DefaultOptions;
|
||||
use Symfony\Component\Form\Options;
|
||||
|
||||
class DefaultOptionsTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $options;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->options = new DefaultOptions();
|
||||
}
|
||||
|
||||
public function testResolve()
|
||||
{
|
||||
$this->options->add(array(
|
||||
'foo' => 'bar',
|
||||
'bam' => function (Options $options) {
|
||||
return 'baz';
|
||||
},
|
||||
));
|
||||
|
||||
$result = array(
|
||||
'foo' => 'fee',
|
||||
'bam' => 'baz',
|
||||
);
|
||||
|
||||
$this->assertEquals($result, $this->options->resolve(array(
|
||||
'foo' => 'fee',
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\InvalidOptionException
|
||||
*/
|
||||
public function testResolveFailsIfNonExistingOption()
|
||||
{
|
||||
$this->options->add(array(
|
||||
'foo' => 'bar',
|
||||
));
|
||||
|
||||
$this->options->resolve(array(
|
||||
'non_existing' => 'option',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\InvalidOptionException
|
||||
*/
|
||||
public function testResolveFailsIfOptionValueNotAllowed()
|
||||
{
|
||||
$this->options->add(array(
|
||||
'foo' => 'bar',
|
||||
));
|
||||
|
||||
$this->options->addAllowedValues(array(
|
||||
'foo' => array('bar', 'baz'),
|
||||
));
|
||||
|
||||
$this->options->resolve(array(
|
||||
'foo' => 'bam',
|
||||
));
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ class FooType extends AbstractType
|
|||
return new FormBuilder($name, $factory, new EventDispatcher());
|
||||
}
|
||||
|
||||
public function getDefaultOptions(array $options)
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'data' => null,
|
||||
|
@ -44,7 +44,7 @@ class FooType extends AbstractType
|
|||
);
|
||||
}
|
||||
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array(
|
||||
'a_or_b' => array('a', 'b'),
|
||||
|
|
|
@ -21,7 +21,7 @@ class FooTypeBarExtension extends AbstractTypeExtension
|
|||
$builder->setAttribute('bar', 'x');
|
||||
}
|
||||
|
||||
public function getAllowedOptionValues(array $options)
|
||||
public function getAllowedOptionValues()
|
||||
{
|
||||
return array(
|
||||
'a_or_b' => array('c'),
|
||||
|
|
|
@ -277,7 +277,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\CreationException
|
||||
* @expectedException Symfony\Component\Form\Exception\InvalidOptionException
|
||||
*/
|
||||
public function testCreateNamedBuilderExpectsOptionsToExist()
|
||||
{
|
||||
|
@ -290,7 +290,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\CreationException
|
||||
* @expectedException Symfony\Component\Form\Exception\InvalidOptionException
|
||||
*/
|
||||
public function testCreateNamedBuilderExpectsOptionsToBeInValidRange()
|
||||
{
|
||||
|
@ -511,11 +511,13 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension()));
|
||||
|
||||
$this->setExpectedException('Symfony\Component\Form\Exception\CreationException',
|
||||
'The options "invalid", "unknown" do not exist. Known options are: "data", "data_class", ' .
|
||||
'"trim", "required", "read_only", "disabled", "max_length", "pattern", "property_path", "by_reference", ' .
|
||||
'"error_bubbling", "error_mapping", "label", "attr", "invalid_message", "invalid_message_parameters", ' .
|
||||
'"translation_domain", "empty_data"'
|
||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidOptionException',
|
||||
'The options "invalid", "unknown" do not exist. Known options are: ' .
|
||||
'"attr", "by_reference", "data", "data_class", "disabled", ' .
|
||||
'"empty_data", "error_bubbling", "error_mapping", "invalid_message", ' .
|
||||
'"invalid_message_parameters", "label", "max_length", "pattern", ' .
|
||||
'"property_path", "read_only", "required", "translation_domain", ' .
|
||||
'"trim"'
|
||||
);
|
||||
$factory->createNamedBuilder($type, "text", "value", array("invalid" => "opt", "unknown" => "opt"));
|
||||
}
|
||||
|
@ -526,11 +528,13 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension()));
|
||||
|
||||
$this->setExpectedException('Symfony\Component\Form\Exception\CreationException',
|
||||
'The option "unknown" does not exist. Known options are: "data", "data_class", ' .
|
||||
'"trim", "required", "read_only", "disabled", "max_length", "pattern", "property_path", "by_reference", ' .
|
||||
'"error_bubbling", "error_mapping", "label", "attr", "invalid_message", "invalid_message_parameters", ' .
|
||||
'"translation_domain", "empty_data"'
|
||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidOptionException',
|
||||
'The option "unknown" does not exist. Known options are: "attr", ' .
|
||||
'"by_reference", "data", "data_class", "disabled", "empty_data", ' .
|
||||
'"error_bubbling", "error_mapping", "invalid_message", ' .
|
||||
'"invalid_message_parameters", "label", "max_length", "pattern", ' .
|
||||
'"property_path", "read_only", "required", "translation_domain", ' .
|
||||
'"trim"'
|
||||
);
|
||||
$factory->createNamedBuilder($type, "text", "value", array("unknown" => "opt"));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests;
|
||||
|
||||
use Symfony\Component\Form\Options;
|
||||
|
||||
class OptionsTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $options;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->options = new Options();
|
||||
}
|
||||
|
||||
public function testArrayAccess()
|
||||
{
|
||||
$this->assertFalse(isset($this->options['foo']));
|
||||
$this->assertFalse(isset($this->options['bar']));
|
||||
|
||||
$this->options['foo'] = 0;
|
||||
$this->options['bar'] = 1;
|
||||
|
||||
$this->assertTrue(isset($this->options['foo']));
|
||||
$this->assertTrue(isset($this->options['bar']));
|
||||
|
||||
unset($this->options['bar']);
|
||||
|
||||
|
||||
$this->assertTrue(isset($this->options['foo']));
|
||||
$this->assertFalse(isset($this->options['bar']));
|
||||
$this->assertEquals(0, $this->options['foo']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testGetNonExisting()
|
||||
{
|
||||
$this->options['foo'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\OptionDefinitionException
|
||||
*/
|
||||
public function testSetNotSupportedAfterGet()
|
||||
{
|
||||
$this->options['foo'] = 'bar';
|
||||
$this->options['foo'];
|
||||
$this->options['foo'] = 'baz';
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\OptionDefinitionException
|
||||
*/
|
||||
public function testUnsetNotSupportedAfterGet()
|
||||
{
|
||||
$this->options['foo'] = 'bar';
|
||||
$this->options['foo'];
|
||||
unset($this->options['foo']);
|
||||
}
|
||||
|
||||
public function testLazyOption()
|
||||
{
|
||||
$test = $this;
|
||||
|
||||
$this->options['foo'] = function (Options $options) use ($test) {
|
||||
return 'dynamic';
|
||||
};
|
||||
|
||||
$this->assertEquals('dynamic', $this->options['foo']);
|
||||
}
|
||||
|
||||
public function testLazyOptionWithEagerCurrentValue()
|
||||
{
|
||||
$test = $this;
|
||||
|
||||
// defined by superclass
|
||||
$this->options['foo'] = 'bar';
|
||||
|
||||
// defined by subclass
|
||||
$this->options['foo'] = function (Options $options, $currentValue) use ($test) {
|
||||
$test->assertEquals('bar', $currentValue);
|
||||
|
||||
return 'dynamic';
|
||||
};
|
||||
|
||||
$this->assertEquals('dynamic', $this->options['foo']);
|
||||
}
|
||||
|
||||
public function testLazyOptionWithLazyCurrentValue()
|
||||
{
|
||||
$test = $this;
|
||||
|
||||
// defined by superclass
|
||||
$this->options['foo'] = function (Options $options) {
|
||||
return 'bar';
|
||||
};
|
||||
|
||||
// defined by subclass
|
||||
$this->options['foo'] = function (Options $options, $currentValue) use ($test) {
|
||||
$test->assertEquals('bar', $currentValue);
|
||||
|
||||
return 'dynamic';
|
||||
};
|
||||
|
||||
$this->assertEquals('dynamic', $this->options['foo']);
|
||||
}
|
||||
|
||||
public function testLazyOptionWithEagerDependency()
|
||||
{
|
||||
$test = $this;
|
||||
|
||||
$this->options['foo'] = 'bar';
|
||||
|
||||
$this->options['bam'] = function (Options $options) use ($test) {
|
||||
$test->assertEquals('bar', $options['foo']);
|
||||
|
||||
return 'dynamic';
|
||||
};
|
||||
|
||||
$this->assertEquals('bar', $this->options['foo']);
|
||||
$this->assertEquals('dynamic', $this->options['bam']);
|
||||
}
|
||||
|
||||
public function testLazyOptionWithLazyDependency()
|
||||
{
|
||||
$test = $this;
|
||||
|
||||
$this->options['foo'] = function (Options $options) {
|
||||
return 'bar';
|
||||
};
|
||||
|
||||
$this->options['bam'] = function (Options $options) use ($test) {
|
||||
$test->assertEquals('bar', $options['foo']);
|
||||
|
||||
return 'dynamic';
|
||||
};
|
||||
|
||||
$this->assertEquals('bar', $this->options['foo']);
|
||||
$this->assertEquals('dynamic', $this->options['bam']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Form\Exception\OptionDefinitionException
|
||||
*/
|
||||
public function testLazyOptionDisallowCyclicDependencies()
|
||||
{
|
||||
$this->options['foo'] = function (Options $options) {
|
||||
$options['bam'];
|
||||
};
|
||||
|
||||
$this->options['bam'] = function (Options $options) {
|
||||
$options['foo'];
|
||||
};
|
||||
|
||||
$this->options['foo'];
|
||||
}
|
||||
}
|
Reference in New Issue