From b9d053edb25b60d0af9cdb54a0fe1cdf7743a4c2 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Tue, 17 Apr 2012 18:14:09 +0200 Subject: [PATCH 01/12] [Form] Moved Options classes to new OptionsParser component --- composer.json | 1 + .../Doctrine/Form/Type/DoctrineType.php | 2 +- .../Bridge/Propel1/Form/Type/ModelType.php | 2 +- src/Symfony/Component/Form/DefaultOptions.php | 320 ------------------ .../Exception/OptionDefinitionException.php | 16 - .../Form/Extension/Core/Type/ChoiceType.php | 2 +- .../Form/Extension/Core/Type/DateTimeType.php | 2 +- .../Form/Extension/Core/Type/DateType.php | 2 +- .../Form/Extension/Core/Type/FieldType.php | 1 - .../Form/Extension/Core/Type/FormType.php | 2 +- .../Form/Extension/Core/Type/TimeType.php | 2 +- .../Form/Extension/Core/Type/TimezoneType.php | 1 + src/Symfony/Component/Form/FormFactory.php | 13 +- .../Form/Tests/DefaultOptionsTest.php | 76 ----- .../Extension/Core/Type/DateTypeTest.php | 4 +- .../Component/Form/Tests/FormFactoryTest.php | 5 +- src/Symfony/Component/Form/composer.json | 3 +- .../Exception/ExceptionInterface.php} | 9 +- .../Exception/InvalidOptionsException.php | 21 ++ .../Exception/MissingOptionsException.php | 21 ++ .../Exception/OptionDefinitionException.php | 21 ++ src/Symfony/Component/OptionsParser/LICENSE | 19 ++ .../{Form => OptionsParser}/LazyOption.php | 4 +- .../{Form => OptionsParser}/Options.php | 162 +-------- .../Component/OptionsParser/OptionsParser.php | 249 ++++++++++++++ src/Symfony/Component/OptionsParser/README.md | 103 ++++++ .../OptionsParser/Tests/OptionParserTest.php | 271 +++++++++++++++ .../Tests/OptionsTest.php | 10 +- .../OptionsParser/Tests/bootstrap.php | 18 + .../Component/OptionsParser/composer.json | 30 ++ .../Component/OptionsParser/phpunit.xml.dist | 29 ++ 31 files changed, 819 insertions(+), 602 deletions(-) delete mode 100644 src/Symfony/Component/Form/DefaultOptions.php delete mode 100644 src/Symfony/Component/Form/Exception/OptionDefinitionException.php delete mode 100644 src/Symfony/Component/Form/Tests/DefaultOptionsTest.php rename src/Symfony/Component/{Form/Exception/InvalidOptionException.php => OptionsParser/Exception/ExceptionInterface.php} (55%) mode change 100644 => 100755 create mode 100644 src/Symfony/Component/OptionsParser/Exception/InvalidOptionsException.php create mode 100644 src/Symfony/Component/OptionsParser/Exception/MissingOptionsException.php create mode 100644 src/Symfony/Component/OptionsParser/Exception/OptionDefinitionException.php create mode 100644 src/Symfony/Component/OptionsParser/LICENSE rename src/Symfony/Component/{Form => OptionsParser}/LazyOption.php (96%) rename src/Symfony/Component/{Form => OptionsParser}/Options.php (56%) create mode 100644 src/Symfony/Component/OptionsParser/OptionsParser.php create mode 100644 src/Symfony/Component/OptionsParser/README.md create mode 100644 src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php rename src/Symfony/Component/{Form => OptionsParser}/Tests/OptionsTest.php (91%) create mode 100644 src/Symfony/Component/OptionsParser/Tests/bootstrap.php create mode 100644 src/Symfony/Component/OptionsParser/composer.json create mode 100644 src/Symfony/Component/OptionsParser/phpunit.xml.dist diff --git a/composer.json b/composer.json index 49a9f3fb2b..c8d46114f8 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "symfony/http-foundation": "self.version", "symfony/http-kernel": "self.version", "symfony/locale": "self.version", + "symfony/options-parser": "self.version", "symfony/process": "self.version", "symfony/routing": "self.version", "symfony/security": "self.version", diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 3bc7a7bb77..96582e66c4 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -19,7 +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; +use Symfony\Component\OptionsParser\Options; abstract class DoctrineType extends AbstractType { diff --git a/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php b/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php index de125a070f..4c57c42b55 100644 --- a/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php +++ b/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php @@ -14,8 +14,8 @@ 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; +use Symfony\Component\OptionsParser\Options; /** * ModelType class. diff --git a/src/Symfony/Component/Form/DefaultOptions.php b/src/Symfony/Component/Form/DefaultOptions.php deleted file mode 100644 index 9dba4b9e9d..0000000000 --- a/src/Symfony/Component/Form/DefaultOptions.php +++ /dev/null @@ -1,320 +0,0 @@ - - * - * 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 concrete - * 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: - * - * - * 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, - * )); - * - * - * 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: - * - * - * 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' - * )); - * - * - * 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: - * - * - * 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', - * )); - * - * - * 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. - * - * - * 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" - * )); - * - * - * The closure receives as its first parameter a container of class Options - * that contains the concrete 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. - * - * - * 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" - * )); - * - * - * @author Bernhard Schussek - */ -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))); - } - } - } -} diff --git a/src/Symfony/Component/Form/Exception/OptionDefinitionException.php b/src/Symfony/Component/Form/Exception/OptionDefinitionException.php deleted file mode 100644 index dfd87cc7e5..0000000000 --- a/src/Symfony/Component/Form/Exception/OptionDefinitionException.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * 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 -{ -} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 3e21af5ad1..6bfc8cbbcd 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -12,7 +12,6 @@ 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; @@ -27,6 +26,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransform use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToBooleanArrayTransformer; +use Symfony\Component\OptionsParser\Options; class ChoiceType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index e74acb20c9..1d8387d54b 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -21,7 +21,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransfo use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; -use Symfony\Component\Form\Options; +use Symfony\Component\OptionsParser\Options; class DateTimeType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index e67a232fec..91dfa3c3d4 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -22,7 +22,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransfo use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\ReversedTransformer; -use Symfony\Component\Form\Options; +use Symfony\Component\OptionsParser\Options; class DateType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php b/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php index 5af6791346..dbafde1f65 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; -use Symfony\Component\EventDispatcher\EventDispatcher; /** * Deprecated. You should extend FormType instead. diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 0dea6d113c..c9936e2f71 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -12,7 +12,6 @@ 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; @@ -23,6 +22,7 @@ use Symfony\Component\Form\Extension\Core\EventListener\ValidationListener; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Form\Exception\FormException; +use Symfony\Component\OptionsParser\Options; class FormType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 7fb9d2bd80..5d1fef8a3e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -20,7 +20,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransf use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Options; +use Symfony\Component\OptionsParser\Options; class TimeType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index 3e6b2baaac..c3eb421e44 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList; +use Symfony\Component\OptionsParser\Options; class TimezoneType extends AbstractType { diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php index 9529a7bea2..ee7ddb9170 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Form; use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\TypeDefinitionException; +use Symfony\Component\OptionsParser\OptionsParser; class FormFactory implements FormFactoryInterface { @@ -220,7 +221,7 @@ class FormFactory implements FormFactoryInterface $types = array(); $optionValues = array(); $knownOptions = array(); - $defaultOptions = new DefaultOptions(); + $optionsParser = new OptionsParser(); // Bottom-up determination of the type hierarchy // Start with the actual type and look for the parent type @@ -254,14 +255,14 @@ class FormFactory implements FormFactoryInterface // options. Default options of children override default options // of parents. $typeOptions = $type->getDefaultOptions(); - $defaultOptions->add($typeOptions); - $defaultOptions->addAllowedValues($type->getAllowedOptionValues()); + $optionsParser->setDefaults($typeOptions); + $optionsParser->addAllowedValues($type->getAllowedOptionValues()); $knownOptions = array_merge($knownOptions, array_keys($typeOptions)); foreach ($type->getExtensions() as $typeExtension) { $extensionOptions = $typeExtension->getDefaultOptions(); - $defaultOptions->add($extensionOptions); - $defaultOptions->addAllowedValues($typeExtension->getAllowedOptionValues()); + $optionsParser->setDefaults($extensionOptions); + $optionsParser->addAllowedValues($typeExtension->getAllowedOptionValues()); $knownOptions = array_merge($knownOptions, array_keys($extensionOptions)); } } @@ -277,7 +278,7 @@ class FormFactory implements FormFactoryInterface } // Resolve options - $options = $defaultOptions->resolve($options); + $options = $optionsParser->parse($options); for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) { $builder = $types[$i]->createBuilder($name, $this, $options); diff --git a/src/Symfony/Component/Form/Tests/DefaultOptionsTest.php b/src/Symfony/Component/Form/Tests/DefaultOptionsTest.php deleted file mode 100644 index e22131d563..0000000000 --- a/src/Symfony/Component/Form/Tests/DefaultOptionsTest.php +++ /dev/null @@ -1,76 +0,0 @@ - - * - * 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', - )); - } -} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index 90b9a264f6..a371099eb8 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -23,7 +23,7 @@ class DateTypeTest extends LocalizedTestCase } /** - * @expectedException Symfony\Component\Form\Exception\FormException + * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException */ public function testInvalidWidgetOption() { @@ -33,7 +33,7 @@ class DateTypeTest extends LocalizedTestCase } /** - * @expectedException Symfony\Component\Form\Exception\FormException + * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException */ public function testInvalidInputOption() { diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php index 4b8eb3f3a9..742a940d4b 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php @@ -279,7 +279,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\Form\Exception\InvalidOptionException + * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException */ public function testCreateNamedBuilderExpectsOptionsToExist() { @@ -292,7 +292,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\Form\Exception\InvalidOptionException + * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException */ public function testCreateNamedBuilderExpectsOptionsToBeInValidRange() { @@ -608,7 +608,6 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $this->assertEquals($parentBuilder, $builder->getParent()); } - public function testFormTypeCreatesDefaultValueForEmptyDataOption() { $factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension())); diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 80ce8df5ca..888eef4ec4 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -18,7 +18,8 @@ "require": { "php": ">=5.3.3", "symfony/event-dispatcher": "2.1.*", - "symfony/locale": "2.1.*" + "symfony/locale": "2.1.*", + "symfony/options-parser": "2.1.*" }, "require-dev": { "symfony/validator": "2.1.*", diff --git a/src/Symfony/Component/Form/Exception/InvalidOptionException.php b/src/Symfony/Component/OptionsParser/Exception/ExceptionInterface.php old mode 100644 new mode 100755 similarity index 55% rename from src/Symfony/Component/Form/Exception/InvalidOptionException.php rename to src/Symfony/Component/OptionsParser/Exception/ExceptionInterface.php index 54d782b3e1..79bc34fde7 --- a/src/Symfony/Component/Form/Exception/InvalidOptionException.php +++ b/src/Symfony/Component/OptionsParser/Exception/ExceptionInterface.php @@ -9,8 +9,13 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Form\Exception; +namespace Symfony\Component\OptionsParser\Exception; -class InvalidOptionException extends FormException +/** + * Marker interface for the Options component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface { } diff --git a/src/Symfony/Component/OptionsParser/Exception/InvalidOptionsException.php b/src/Symfony/Component/OptionsParser/Exception/InvalidOptionsException.php new file mode 100644 index 0000000000..24454d8c77 --- /dev/null +++ b/src/Symfony/Component/OptionsParser/Exception/InvalidOptionsException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsParser\Exception; + +/** + * Exception thrown when an invalid option is passed. + * + * @author Bernhard Schussek + */ +class InvalidOptionsException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/OptionsParser/Exception/MissingOptionsException.php b/src/Symfony/Component/OptionsParser/Exception/MissingOptionsException.php new file mode 100644 index 0000000000..3afb54dfc2 --- /dev/null +++ b/src/Symfony/Component/OptionsParser/Exception/MissingOptionsException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsParser\Exception; + +/** + * Exception thrown when a required option is missing. + * + * @author Bernhard Schussek + */ +class MissingOptionsException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/OptionsParser/Exception/OptionDefinitionException.php b/src/Symfony/Component/OptionsParser/Exception/OptionDefinitionException.php new file mode 100644 index 0000000000..34d317f720 --- /dev/null +++ b/src/Symfony/Component/OptionsParser/Exception/OptionDefinitionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsParser\Exception; + +/** + * Thrown when an option definition is invalid. + * + * @author Bernhard Schussek + */ +class OptionDefinitionException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/OptionsParser/LICENSE b/src/Symfony/Component/OptionsParser/LICENSE new file mode 100644 index 0000000000..cdffe7aebc --- /dev/null +++ b/src/Symfony/Component/OptionsParser/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2012 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Form/LazyOption.php b/src/Symfony/Component/OptionsParser/LazyOption.php similarity index 96% rename from src/Symfony/Component/Form/LazyOption.php rename to src/Symfony/Component/OptionsParser/LazyOption.php index 69365ec996..8d935fdf0a 100644 --- a/src/Symfony/Component/Form/LazyOption.php +++ b/src/Symfony/Component/OptionsParser/LazyOption.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Form; +namespace Symfony\Component\OptionsParser; use Closure; @@ -17,8 +17,6 @@ use Closure; * An option that is evaluated lazily using a closure. * * @author Bernhard Schussek - * - * @see DefaultOptions */ class LazyOption { diff --git a/src/Symfony/Component/Form/Options.php b/src/Symfony/Component/OptionsParser/Options.php similarity index 56% rename from src/Symfony/Component/Form/Options.php rename to src/Symfony/Component/OptionsParser/Options.php index ef2254f7a3..956616e34e 100644 --- a/src/Symfony/Component/Form/Options.php +++ b/src/Symfony/Component/OptionsParser/Options.php @@ -9,164 +9,16 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Form; +namespace Symfony\Component\OptionsParser; use ArrayAccess; use Iterator; use OutOfBoundsException; -use Symfony\Component\Form\Exception\OptionDefinitionException; +use Symfony\Component\OptionsParser\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: - * - * - * 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, - * )); - * - * - * 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: - * - * - * 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' - * )); - * - * - * 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: - * - * - * 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', - * )); - * - * - * 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. - * - * - * 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" - * )); - * - * - * 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. - * - * - * 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" - * )); - * - * * @author Bernhard Schussek */ class Options implements ArrayAccess, Iterator @@ -312,16 +164,6 @@ class Options implements ArrayAccess, Iterator 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() */ diff --git a/src/Symfony/Component/OptionsParser/OptionsParser.php b/src/Symfony/Component/OptionsParser/OptionsParser.php new file mode 100644 index 0000000000..3a5d6cc85a --- /dev/null +++ b/src/Symfony/Component/OptionsParser/OptionsParser.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsParser; + +use Symfony\Component\OptionsParser\Exception\OptionDefinitionException; +use Symfony\Component\OptionsParser\Exception\InvalidOptionsException; +use Symfony\Component\OptionsParser\Exception\MissingOptionsException; + +/** + * Helper for merging default and concrete option values. + * + * @author Bernhard Schussek + */ +class OptionsParser +{ + /** + * The default option values. + * @var Options + */ + private $defaultOptions; + + /** + * The options known by the parser. + * @var array + */ + private $knownOptions = array(); + + /** + * The options required to be passed to parse(). + * @var array + */ + private $requiredOptions = array(); + + /** + * A list of accepted values for each option. + * @var array + */ + private $allowedValues = array(); + + /** + * Creates a new instance. + */ + public function __construct() + { + $this->defaultOptions = new Options(); + } + + /** + * Sets default option values. + * + * @param array $options A list of option names as keys and default values + * as values. The option values may be closures + * of the following signatures: + * + * - function (Options $options) + * - function (Options $options, $previousValue) + */ + public function setDefaults(array $defaultValues) + { + foreach ($defaultValues as $option => $value) { + $this->defaultOptions[$option] = $value; + $this->knownOptions[$option] = true; + } + } + + /** + * Sets optional options. + * + * This method is identical to `setDefaults`, only that no default values + * are configured for the options. If these options are not passed to + * parse(), they will be missing in the final options array. This can be + * helpful if you want to determine whether an option has been set or not. + * + * @param array $optionNames A list of option names. + * + * @throws OptionDefinitionException When trying to pass default values. + */ + public function setOptional(array $optionNames) + { + foreach ($optionNames as $key => $option) { + if (!is_int($key)) { + throw new OptionDefinitionException('You should not pass default values to setOptional()'); + } + + $this->knownOptions[$option] = true; + } + } + + /** + * Sets required options. + * + * If these options are not passed to parse(), an exception will be thrown. + * + * @param array $optionNames A list of option names. + * + * @throws OptionDefinitionException When trying to pass default values. + */ + public function setRequired(array $optionNames) + { + foreach ($optionNames as $key => $option) { + if (!is_int($key)) { + throw new OptionDefinitionException('You should not pass default values to setRequired()'); + } + + $this->knownOptions[$option] = true; + $this->requiredOptions[$option] = true; + } + } + + /** + * Sets 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 InvalidOptionsException If an option has not been defined for + * which an allowed value is set. + */ + public function setAllowedValues(array $allowedValues) + { + $this->validateOptionNames(array_keys($allowedValues)); + + $this->allowedValues = array_replace($this->allowedValues, $allowedValues); + } + + /** + * Adds allowed values for a list of options. + * + * The values are merged with the allowed values defined previously. + * + * @param array $allowedValues A list of option names as keys and arrays + * with values acceptable for that option as + * values. + * + * @throws InvalidOptionsException 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); + } + + /** + * Returns the combination of the default and the passed options. + * + * @param array $options The custom option values. + * + * @return array A list of options and their values. + * + * @throws InvalidOptionsException If any of the passed options has not + * been defined or does not contain an + * allowed value. + * @throws MissingOptionsException If a required option is missing. + * @throws OptionDefinitionException If a cyclic dependency is detected + * between two lazy options. + */ + public function parse(array $options) + { + $this->validateOptionNames(array_keys($options)); + + // Make sure this method can be called multiple times + $combinedOptions = clone $this->defaultOptions; + + // Override options set by the user + foreach ($options as $option => $value) { + $combinedOptions[$option] = $value; + } + + // Resolve options + $combinedOptions = iterator_to_array($combinedOptions); + + // Validate against allowed values + $this->validateOptionValues($combinedOptions); + + return $combinedOptions; + } + + /** + * Validates that the given option names exist and throws an exception + * otherwise. + * + * @param array $optionNames A list of option names. + * + * @throws InvalidOptionsException If any of the options has not been + * defined. + */ + private function validateOptionNames(array $optionNames) + { + ksort($this->knownOptions); + + $knownOptions = array_keys($this->knownOptions); + $diff = array_diff($optionNames, $knownOptions); + + sort($diff); + + if (count($diff) > 1) { + throw new InvalidOptionsException(sprintf('The options "%s" do not exist. Known options are: "%s"', implode('", "', $diff), implode('", "', $knownOptions))); + } + + if (count($diff) > 0) { + throw new InvalidOptionsException(sprintf('The option "%s" does not exist. Known options are: "%s"', current($diff), implode('", "', $knownOptions))); + } + + ksort($this->requiredOptions); + + $requiredOptions = array_keys($this->requiredOptions); + $diff = array_diff($requiredOptions, $optionNames); + + sort($diff); + + if (count($diff) > 1) { + throw new MissingOptionsException(sprintf('The options "%s" are missing.', implode('", "', $diff))); + } + + if (count($diff) > 0) { + throw new MissingOptionsException(sprintf('The option "%s" is missing.', current($diff))); + } + } + + /** + * Validates that the given option values match the allowed values and + * throws an exception otherwise. + * + * @param array $options A list of option values. + * + * @throws InvalidOptionsException 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 InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', $allowedValues))); + } + } + } +} diff --git a/src/Symfony/Component/OptionsParser/README.md b/src/Symfony/Component/OptionsParser/README.md new file mode 100644 index 0000000000..6ae6920bb0 --- /dev/null +++ b/src/Symfony/Component/OptionsParser/README.md @@ -0,0 +1,103 @@ +OptionsParser Component +====================== + +OptionsParser helps to configure objects with option arrays. + +It supports default values on different levels of your class hierarchy, +required options and lazy options where the default value depends on the +concrete value of a different option. + +The following example demonstrates a Person class with two required options +"firstName" and "lastName" and two optional options "age" and "gender", where +the default value of "gender" is derived from the passed first name, if +possible. + + use Symfony\Component\OptionsParser\OptionsParser; + use Symfony\Component\OptionsParser\Options; + + class Person + { + protected $options; + + public function __construct(array $options = array()) + { + $parser = new OptionsParser(); + $this->setOptions($parser); + + $this->options = $parser->parse($options); + } + + protected function setOptions(OptionsParser $parser) + { + $parser->setRequired(array( + 'firstName', + 'lastName', + 'age', + )); + + $parser->setDefaults(array( + 'age' => null, + 'gender' => function (Options $options) { + if (self::isKnownMaleName($options['firstName'])) { + return 'male'; + } + + return 'female'; + }, + )); + + $parser->setAllowedValues(array( + 'gender' => array('male', 'female'), + )); + } + } + +We can now easily instantiate a Person object: + + // 'gender' is implicitely set to 'female' + $person = new Person(array( + 'firstName' => 'Jane', + 'lastName' => 'Doe', + )); + +We can also override the default values of the optional options: + + $person = new Person(array( + 'firstName' => 'Abdullah', + 'lastName' => 'Mogashi', + 'gender' => 'male', + 'age' => 30, + )); + +Options can be added or changed in subclasses by overriding the `setOptions` +method: + + use Symfony\Component\OptionsParser\OptionsParser; + use Symfony\Component\OptionsParser\Options; + + class Employee extends Person + { + protected function setOptions(OptionsParser $parser) + { + parent::setOptions($parser); + + $parser->setRequired(array( + 'birthDate', + )); + + $parser->setDefaults(array( + // $previousValue contains the default value configured in the + // parent class + 'age' => function (Options $options, $previousValue) { + return self::configureAgeFromBirthDate($options['birthDate']); + } + )); + } + } + +Resources +--------- + +You can run the unit tests with the following command: + + phpunit -c src/Symfony/Component/OptionsParser/ \ No newline at end of file diff --git a/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php b/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php new file mode 100644 index 0000000000..6f6e416b40 --- /dev/null +++ b/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php @@ -0,0 +1,271 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsParser\Tests; + +use Symfony\Component\OptionsParser\OptionsParser; +use Symfony\Component\OptionsParser\Options; + +class OptionsParserTest extends \PHPUnit_Framework_TestCase +{ + private $options; + + protected function setUp() + { + $this->parser = new OptionsParser(); + } + + public function testParse() + { + $this->parser->setDefaults(array( + 'one' => '1', + 'two' => '2', + )); + + $options = array( + 'two' => '20', + ); + + $this->assertEquals(array( + 'one' => '1', + 'two' => '20', + ), $this->parser->parse($options)); + } + + public function testParseLazy() + { + $this->parser->setDefaults(array( + 'one' => '1', + 'two' => function (Options $options) { + return '20'; + }, + )); + + $this->assertEquals(array( + 'one' => '1', + 'two' => '20', + ), $this->parser->parse(array())); + } + + public function testParseLazyDependencyOnOptional() + { + $this->parser->setDefaults(array( + 'one' => '1', + 'two' => function (Options $options) { + return $options['one'] . '2'; + }, + )); + + $options = array( + 'one' => '10', + ); + + $this->assertEquals(array( + 'one' => '10', + 'two' => '102', + ), $this->parser->parse($options)); + } + + public function testParseLazyDependencyOnMissingOptionalWithoutDefault() + { + $test = $this; + + $this->parser->setOptional(array( + 'one', + )); + + $this->parser->setDefaults(array( + 'two' => function (Options $options) use ($test) { + $test->assertFalse(isset($options['one'])); + + return '2'; + }, + )); + + $options = array( + ); + + $this->assertEquals(array( + 'two' => '2', + ), $this->parser->parse($options)); + } + + public function testParseLazyDependencyOnOptionalWithoutDefault() + { + $test = $this; + + $this->parser->setOptional(array( + 'one', + )); + + $this->parser->setDefaults(array( + 'two' => function (Options $options) use ($test) { + $test->assertTrue(isset($options['one'])); + + return $options['one'] . '2'; + }, + )); + + $options = array( + 'one' => '10', + ); + + $this->assertEquals(array( + 'one' => '10', + 'two' => '102', + ), $this->parser->parse($options)); + } + + public function testParseLazyDependencyOnRequired() + { + $this->parser->setRequired(array( + 'one', + )); + $this->parser->setDefaults(array( + 'two' => function (Options $options) { + return $options['one'] . '2'; + }, + )); + + $options = array( + 'one' => '10', + ); + + $this->assertEquals(array( + 'one' => '10', + 'two' => '102', + ), $this->parser->parse($options)); + } + + /** + * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException + */ + public function testParseFailsIfNonExistingOption() + { + $this->parser->setDefaults(array( + 'one' => '1', + )); + + $this->parser->setRequired(array( + 'two', + )); + + $this->parser->setOptional(array( + 'three', + )); + + $this->parser->parse(array( + 'foo' => 'bar', + )); + } + + /** + * @expectedException Symfony\Component\OptionsParser\Exception\MissingOptionsException + */ + public function testParseFailsIfMissingRequiredOption() + { + $this->parser->setRequired(array( + 'one', + )); + + $this->parser->setDefaults(array( + 'two' => '2', + )); + + $this->parser->parse(array( + 'two' => '20', + )); + } + + public function testParseSucceedsIfOptionValueAllowed() + { + $this->parser->setDefaults(array( + 'one' => '1', + )); + + $this->parser->setAllowedValues(array( + 'one' => array('1', 'one'), + )); + + $options = array( + 'one' => 'one', + ); + + $this->assertEquals(array( + 'one' => 'one', + ), $this->parser->parse($options)); + } + + public function testParseSucceedsIfOptionValueAllowed2() + { + $this->parser->setDefaults(array( + 'one' => '1', + 'two' => '2', + )); + + $this->parser->addAllowedValues(array( + 'one' => array('1'), + 'two' => array('2'), + )); + $this->parser->addAllowedValues(array( + 'one' => array('one'), + 'two' => array('two'), + )); + + $options = array( + 'one' => '1', + 'two' => 'two', + ); + + $this->assertEquals(array( + 'one' => '1', + 'two' => 'two', + ), $this->parser->parse($options)); + } + + /** + * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException + */ + public function testParseFailsIfOptionValueNotAllowed() + { + $this->parser->setDefaults(array( + 'one' => '1', + )); + + $this->parser->setAllowedValues(array( + 'one' => array('1', 'one'), + )); + + $this->parser->parse(array( + 'one' => '2', + )); + } + + /** + * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException + */ + public function testSetRequiredFailsIfDefaultIsPassed() + { + $this->parser->setRequired(array( + 'one' => '1', + )); + } + + /** + * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException + */ + public function testSetOptionalFailsIfDefaultIsPassed() + { + $this->parser->setOptional(array( + 'one' => '1', + )); + } +} diff --git a/src/Symfony/Component/Form/Tests/OptionsTest.php b/src/Symfony/Component/OptionsParser/Tests/OptionsTest.php similarity index 91% rename from src/Symfony/Component/Form/Tests/OptionsTest.php rename to src/Symfony/Component/OptionsParser/Tests/OptionsTest.php index 2590adb396..0d4ab9209b 100644 --- a/src/Symfony/Component/Form/Tests/OptionsTest.php +++ b/src/Symfony/Component/OptionsParser/Tests/OptionsTest.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Form\Tests; +namespace Symfony\Component\OptionsParser\Tests; -use Symfony\Component\Form\Options; +use Symfony\Component\OptionsParser\Options; class OptionsTest extends \PHPUnit_Framework_TestCase { @@ -50,7 +50,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\Form\Exception\OptionDefinitionException + * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException */ public function testSetNotSupportedAfterGet() { @@ -60,7 +60,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\Form\Exception\OptionDefinitionException + * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException */ public function testUnsetNotSupportedAfterGet() { @@ -151,7 +151,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\Form\Exception\OptionDefinitionException + * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException */ public function testLazyOptionDisallowCyclicDependencies() { diff --git a/src/Symfony/Component/OptionsParser/Tests/bootstrap.php b/src/Symfony/Component/OptionsParser/Tests/bootstrap.php new file mode 100644 index 0000000000..547252172d --- /dev/null +++ b/src/Symfony/Component/OptionsParser/Tests/bootstrap.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +spl_autoload_register(function ($class) { + if (0 === strpos(ltrim($class, '/'), 'Symfony\Component\OptionsParser')) { + if (file_exists($file = __DIR__.'/../'.substr(str_replace('\\', '/', $class), strlen('Symfony\Component\OptionsParser')).'.php')) { + require_once $file; + } + } +}); diff --git a/src/Symfony/Component/OptionsParser/composer.json b/src/Symfony/Component/OptionsParser/composer.json new file mode 100644 index 0000000000..6ea87546f9 --- /dev/null +++ b/src/Symfony/Component/OptionsParser/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/options-parser", + "type": "library", + "description": "Symfony OptionsParser Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\OptionsParser": "" } + }, + "target-dir": "Symfony/Component/OptionsParser", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + } +} diff --git a/src/Symfony/Component/OptionsParser/phpunit.xml.dist b/src/Symfony/Component/OptionsParser/phpunit.xml.dist new file mode 100644 index 0000000000..a10fc7a8df --- /dev/null +++ b/src/Symfony/Component/OptionsParser/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + + + + From 04522ca4ed80b27a864fee29bb4d84c74a853e79 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 23 Apr 2012 12:10:43 +0200 Subject: [PATCH 02/12] [OptionsParser] Added method replaceDefaults() --- .../Component/OptionsParser/OptionsParser.php | 23 +++++++++++++++++++ .../OptionsParser/Tests/OptionParserTest.php | 21 +++++++++++++++++ .../OptionsParser/Tests/OptionsTest.php | 12 +++++----- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/OptionsParser/OptionsParser.php b/src/Symfony/Component/OptionsParser/OptionsParser.php index 3a5d6cc85a..444994ee1b 100644 --- a/src/Symfony/Component/OptionsParser/OptionsParser.php +++ b/src/Symfony/Component/OptionsParser/OptionsParser.php @@ -72,6 +72,29 @@ class OptionsParser } } + /** + * Replaces default option values. + * + * Old defaults are erased, which means that closures passed here can't + * access the previous default value. This may be useful to improve + * performance if the previous default value is calculated by an expensive + * closure. + * + * @param array $options A list of option names as keys and default values + * as values. The option values may be closures + * of the following signature: + * + * - function (Options $options) + */ + public function replaceDefaults(array $defaultValues) + { + foreach ($defaultValues as $option => $value) { + unset($this->defaultOptions[$option]); + $this->defaultOptions[$option] = $value; + $this->knownOptions[$option] = true; + } + } + /** * Sets optional options. * diff --git a/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php b/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php index 6f6e416b40..70740e629f 100644 --- a/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php +++ b/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php @@ -145,6 +145,27 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase ), $this->parser->parse($options)); } + public function testParseLazyReplaceDefaults() + { + $test = $this; + + $this->parser->setDefaults(array( + 'one' => function (Options $options) use ($test) { + $test->fail('Previous closure should not be executed'); + }, + )); + + $this->parser->replaceDefaults(array( + 'one' => function (Options $options, $previousValue) { + return '1'; + }, + )); + + $this->assertEquals(array( + 'one' => '1', + ), $this->parser->parse(array())); + } + /** * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException */ diff --git a/src/Symfony/Component/OptionsParser/Tests/OptionsTest.php b/src/Symfony/Component/OptionsParser/Tests/OptionsTest.php index 0d4ab9209b..058a738369 100644 --- a/src/Symfony/Component/OptionsParser/Tests/OptionsTest.php +++ b/src/Symfony/Component/OptionsParser/Tests/OptionsTest.php @@ -80,7 +80,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase $this->assertEquals('dynamic', $this->options['foo']); } - public function testLazyOptionWithEagerCurrentValue() + public function testLazyOptionWithEagerPreviousValue() { $test = $this; @@ -88,8 +88,8 @@ class OptionsTest extends \PHPUnit_Framework_TestCase $this->options['foo'] = 'bar'; // defined by subclass - $this->options['foo'] = function (Options $options, $currentValue) use ($test) { - $test->assertEquals('bar', $currentValue); + $this->options['foo'] = function (Options $options, $previousValue) use ($test) { + $test->assertEquals('bar', $previousValue); return 'dynamic'; }; @@ -97,7 +97,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase $this->assertEquals('dynamic', $this->options['foo']); } - public function testLazyOptionWithLazyCurrentValue() + public function testLazyOptionWithLazyPreviousValue() { $test = $this; @@ -107,8 +107,8 @@ class OptionsTest extends \PHPUnit_Framework_TestCase }; // defined by subclass - $this->options['foo'] = function (Options $options, $currentValue) use ($test) { - $test->assertEquals('bar', $currentValue); + $this->options['foo'] = function (Options $options, $previousValue) use ($test) { + $test->assertEquals('bar', $previousValue); return 'dynamic'; }; From 256b7081a423caeb404c9ec838b6ee21cc7e5bab Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 10 May 2012 16:37:03 +0200 Subject: [PATCH 03/12] [OptionsParser] Renamed OptionsParser to OptionsResolver --- composer.json | 2 +- .../Doctrine/Form/Type/DoctrineType.php | 2 +- .../Bridge/Propel1/Form/Type/ModelType.php | 2 +- .../Form/Extension/Core/Type/ChoiceType.php | 2 +- .../Form/Extension/Core/Type/DateTimeType.php | 2 +- .../Form/Extension/Core/Type/DateType.php | 2 +- .../Form/Extension/Core/Type/FormType.php | 2 +- .../Form/Extension/Core/Type/TimeType.php | 2 +- .../Form/Extension/Core/Type/TimezoneType.php | 2 +- src/Symfony/Component/Form/FormFactory.php | 14 +-- .../Extension/Core/Type/DateTypeTest.php | 4 +- .../Component/Form/Tests/FormFactoryTest.php | 4 +- src/Symfony/Component/Form/composer.json | 2 +- .../Exception/ExceptionInterface.php | 2 +- .../Exception/InvalidOptionsException.php | 2 +- .../Exception/MissingOptionsException.php | 2 +- .../Exception/OptionDefinitionException.php | 2 +- .../LICENSE | 0 .../LazyOption.php | 2 +- .../Options.php | 4 +- .../OptionsResolver.php} | 20 +-- .../README.md | 49 ++++---- .../Tests/OptionResolverTest.php} | 118 +++++++++--------- .../Tests/OptionsTest.php | 10 +- .../Tests/bootstrap.php | 4 +- .../composer.json | 8 +- .../phpunit.xml.dist | 2 +- 27 files changed, 134 insertions(+), 133 deletions(-) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/Exception/ExceptionInterface.php (87%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/Exception/InvalidOptionsException.php (89%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/Exception/MissingOptionsException.php (89%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/Exception/OptionDefinitionException.php (88%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/LICENSE (100%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/LazyOption.php (97%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/Options.php (97%) rename src/Symfony/Component/{OptionsParser/OptionsParser.php => OptionsResolver/OptionsResolver.php} (93%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/README.md (61%) rename src/Symfony/Component/{OptionsParser/Tests/OptionParserTest.php => OptionsResolver/Tests/OptionResolverTest.php} (55%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/Tests/OptionsTest.php (91%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/Tests/bootstrap.php (82%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/composer.json (70%) rename src/Symfony/Component/{OptionsParser => OptionsResolver}/phpunit.xml.dist (91%) diff --git a/composer.json b/composer.json index c8d46114f8..cef5e7035d 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ "symfony/http-foundation": "self.version", "symfony/http-kernel": "self.version", "symfony/locale": "self.version", - "symfony/options-parser": "self.version", + "symfony/options-resolver": "self.version", "symfony/process": "self.version", "symfony/routing": "self.version", "symfony/security": "self.version", diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 96582e66c4..c4b6b8a518 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -19,7 +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\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; abstract class DoctrineType extends AbstractType { diff --git a/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php b/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php index 4c57c42b55..3e2c7b4f78 100644 --- a/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php +++ b/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php @@ -15,7 +15,7 @@ use Symfony\Bridge\Propel1\Form\ChoiceList\ModelChoiceList; use Symfony\Bridge\Propel1\Form\DataTransformer\CollectionToArrayTransformer; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; /** * ModelType class. diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 6bfc8cbbcd..f5991a9bbc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -26,7 +26,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransform use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToBooleanArrayTransformer; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; class ChoiceType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 1d8387d54b..d3d37fa442 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -21,7 +21,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransfo use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; class DateTimeType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 91dfa3c3d4..dbc63b1c28 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -22,7 +22,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransfo use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\ReversedTransformer; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; class DateType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index c9936e2f71..a00524af19 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -22,7 +22,7 @@ use Symfony\Component\Form\Extension\Core\EventListener\ValidationListener; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Form\Exception\FormException; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; class FormType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 5d1fef8a3e..1d263c3850 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -20,7 +20,7 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransf use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; use Symfony\Component\Form\FormView; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; class TimeType extends AbstractType { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index c3eb421e44..61e7afdaf8 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -13,7 +13,7 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; class TimezoneType extends AbstractType { diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php index ee7ddb9170..5b75795e78 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -14,7 +14,7 @@ namespace Symfony\Component\Form; use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\TypeDefinitionException; -use Symfony\Component\OptionsParser\OptionsParser; +use Symfony\Component\OptionsResolver\OptionsResolver; class FormFactory implements FormFactoryInterface { @@ -221,7 +221,7 @@ class FormFactory implements FormFactoryInterface $types = array(); $optionValues = array(); $knownOptions = array(); - $optionsParser = new OptionsParser(); + $optionsResolver = new OptionsResolver(); // Bottom-up determination of the type hierarchy // Start with the actual type and look for the parent type @@ -255,14 +255,14 @@ class FormFactory implements FormFactoryInterface // options. Default options of children override default options // of parents. $typeOptions = $type->getDefaultOptions(); - $optionsParser->setDefaults($typeOptions); - $optionsParser->addAllowedValues($type->getAllowedOptionValues()); + $optionsResolver->setDefaults($typeOptions); + $optionsResolver->addAllowedValues($type->getAllowedOptionValues()); $knownOptions = array_merge($knownOptions, array_keys($typeOptions)); foreach ($type->getExtensions() as $typeExtension) { $extensionOptions = $typeExtension->getDefaultOptions(); - $optionsParser->setDefaults($extensionOptions); - $optionsParser->addAllowedValues($typeExtension->getAllowedOptionValues()); + $optionsResolver->setDefaults($extensionOptions); + $optionsResolver->addAllowedValues($typeExtension->getAllowedOptionValues()); $knownOptions = array_merge($knownOptions, array_keys($extensionOptions)); } } @@ -278,7 +278,7 @@ class FormFactory implements FormFactoryInterface } // Resolve options - $options = $optionsParser->parse($options); + $options = $optionsResolver->resolve($options); for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) { $builder = $types[$i]->createBuilder($name, $this, $options); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index a371099eb8..41aaf6b953 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -23,7 +23,7 @@ class DateTypeTest extends LocalizedTestCase } /** - * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException + * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException */ public function testInvalidWidgetOption() { @@ -33,7 +33,7 @@ class DateTypeTest extends LocalizedTestCase } /** - * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException + * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException */ public function testInvalidInputOption() { diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php index 742a940d4b..40c5137df4 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php @@ -279,7 +279,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException + * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException */ public function testCreateNamedBuilderExpectsOptionsToExist() { @@ -292,7 +292,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException + * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException */ public function testCreateNamedBuilderExpectsOptionsToBeInValidRange() { diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 888eef4ec4..d02b335fa9 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -19,7 +19,7 @@ "php": ">=5.3.3", "symfony/event-dispatcher": "2.1.*", "symfony/locale": "2.1.*", - "symfony/options-parser": "2.1.*" + "symfony/options-resolver": "2.1.*" }, "require-dev": { "symfony/validator": "2.1.*", diff --git a/src/Symfony/Component/OptionsParser/Exception/ExceptionInterface.php b/src/Symfony/Component/OptionsResolver/Exception/ExceptionInterface.php similarity index 87% rename from src/Symfony/Component/OptionsParser/Exception/ExceptionInterface.php rename to src/Symfony/Component/OptionsResolver/Exception/ExceptionInterface.php index 79bc34fde7..4224f4e3e9 100755 --- a/src/Symfony/Component/OptionsParser/Exception/ExceptionInterface.php +++ b/src/Symfony/Component/OptionsResolver/Exception/ExceptionInterface.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser\Exception; +namespace Symfony\Component\OptionsResolver\Exception; /** * Marker interface for the Options component. diff --git a/src/Symfony/Component/OptionsParser/Exception/InvalidOptionsException.php b/src/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.php similarity index 89% rename from src/Symfony/Component/OptionsParser/Exception/InvalidOptionsException.php rename to src/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.php index 24454d8c77..2e7ea1bc0c 100644 --- a/src/Symfony/Component/OptionsParser/Exception/InvalidOptionsException.php +++ b/src/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser\Exception; +namespace Symfony\Component\OptionsResolver\Exception; /** * Exception thrown when an invalid option is passed. diff --git a/src/Symfony/Component/OptionsParser/Exception/MissingOptionsException.php b/src/Symfony/Component/OptionsResolver/Exception/MissingOptionsException.php similarity index 89% rename from src/Symfony/Component/OptionsParser/Exception/MissingOptionsException.php rename to src/Symfony/Component/OptionsResolver/Exception/MissingOptionsException.php index 3afb54dfc2..8544dfb2ee 100644 --- a/src/Symfony/Component/OptionsParser/Exception/MissingOptionsException.php +++ b/src/Symfony/Component/OptionsResolver/Exception/MissingOptionsException.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser\Exception; +namespace Symfony\Component\OptionsResolver\Exception; /** * Exception thrown when a required option is missing. diff --git a/src/Symfony/Component/OptionsParser/Exception/OptionDefinitionException.php b/src/Symfony/Component/OptionsResolver/Exception/OptionDefinitionException.php similarity index 88% rename from src/Symfony/Component/OptionsParser/Exception/OptionDefinitionException.php rename to src/Symfony/Component/OptionsResolver/Exception/OptionDefinitionException.php index 34d317f720..11617fe1a1 100644 --- a/src/Symfony/Component/OptionsParser/Exception/OptionDefinitionException.php +++ b/src/Symfony/Component/OptionsResolver/Exception/OptionDefinitionException.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser\Exception; +namespace Symfony\Component\OptionsResolver\Exception; /** * Thrown when an option definition is invalid. diff --git a/src/Symfony/Component/OptionsParser/LICENSE b/src/Symfony/Component/OptionsResolver/LICENSE similarity index 100% rename from src/Symfony/Component/OptionsParser/LICENSE rename to src/Symfony/Component/OptionsResolver/LICENSE diff --git a/src/Symfony/Component/OptionsParser/LazyOption.php b/src/Symfony/Component/OptionsResolver/LazyOption.php similarity index 97% rename from src/Symfony/Component/OptionsParser/LazyOption.php rename to src/Symfony/Component/OptionsResolver/LazyOption.php index 8d935fdf0a..ba1fd8219c 100644 --- a/src/Symfony/Component/OptionsParser/LazyOption.php +++ b/src/Symfony/Component/OptionsResolver/LazyOption.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser; +namespace Symfony\Component\OptionsResolver; use Closure; diff --git a/src/Symfony/Component/OptionsParser/Options.php b/src/Symfony/Component/OptionsResolver/Options.php similarity index 97% rename from src/Symfony/Component/OptionsParser/Options.php rename to src/Symfony/Component/OptionsResolver/Options.php index 956616e34e..78ebf30535 100644 --- a/src/Symfony/Component/OptionsParser/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser; +namespace Symfony\Component\OptionsResolver; use ArrayAccess; use Iterator; use OutOfBoundsException; -use Symfony\Component\OptionsParser\Exception\OptionDefinitionException; +use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; /** * Container for resolving inter-dependent options. diff --git a/src/Symfony/Component/OptionsParser/OptionsParser.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php similarity index 93% rename from src/Symfony/Component/OptionsParser/OptionsParser.php rename to src/Symfony/Component/OptionsResolver/OptionsResolver.php index 444994ee1b..2f4d8d2031 100644 --- a/src/Symfony/Component/OptionsParser/OptionsParser.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -9,18 +9,18 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser; +namespace Symfony\Component\OptionsResolver; -use Symfony\Component\OptionsParser\Exception\OptionDefinitionException; -use Symfony\Component\OptionsParser\Exception\InvalidOptionsException; -use Symfony\Component\OptionsParser\Exception\MissingOptionsException; +use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; /** * Helper for merging default and concrete option values. * * @author Bernhard Schussek */ -class OptionsParser +class OptionsResolver { /** * The default option values. @@ -29,13 +29,13 @@ class OptionsParser private $defaultOptions; /** - * The options known by the parser. + * The options known by the resolver. * @var array */ private $knownOptions = array(); /** - * The options required to be passed to parse(). + * The options required to be passed to resolve(). * @var array */ private $requiredOptions = array(); @@ -100,7 +100,7 @@ class OptionsParser * * This method is identical to `setDefaults`, only that no default values * are configured for the options. If these options are not passed to - * parse(), they will be missing in the final options array. This can be + * resolve(), they will be missing in the final options array. This can be * helpful if you want to determine whether an option has been set or not. * * @param array $optionNames A list of option names. @@ -121,7 +121,7 @@ class OptionsParser /** * Sets required options. * - * If these options are not passed to parse(), an exception will be thrown. + * If these options are not passed to resolve(), an exception will be thrown. * * @param array $optionNames A list of option names. * @@ -189,7 +189,7 @@ class OptionsParser * @throws OptionDefinitionException If a cyclic dependency is detected * between two lazy options. */ - public function parse(array $options) + public function resolve(array $options) { $this->validateOptionNames(array_keys($options)); diff --git a/src/Symfony/Component/OptionsParser/README.md b/src/Symfony/Component/OptionsResolver/README.md similarity index 61% rename from src/Symfony/Component/OptionsParser/README.md rename to src/Symfony/Component/OptionsResolver/README.md index 6ae6920bb0..5e0f08ce45 100644 --- a/src/Symfony/Component/OptionsParser/README.md +++ b/src/Symfony/Component/OptionsResolver/README.md @@ -1,19 +1,19 @@ -OptionsParser Component +OptionsResolver Component ====================== -OptionsParser helps to configure objects with option arrays. +OptionsResolver helps at configuring objects with option arrays. It supports default values on different levels of your class hierarchy, -required options and lazy options where the default value depends on the -concrete value of a different option. +option constraints (required vs. optional, allowed values) and lazy options +whose default value depends on the value of another option. The following example demonstrates a Person class with two required options "firstName" and "lastName" and two optional options "age" and "gender", where the default value of "gender" is derived from the passed first name, if -possible. +possible, and may only be one of "male" and "female". - use Symfony\Component\OptionsParser\OptionsParser; - use Symfony\Component\OptionsParser\Options; + use Symfony\Component\OptionsResolver\OptionsResolver; + use Symfony\Component\OptionsResolver\Options; class Person { @@ -21,21 +21,20 @@ possible. public function __construct(array $options = array()) { - $parser = new OptionsParser(); - $this->setOptions($parser); + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); - $this->options = $parser->parse($options); + $this->options = $resolver->resolve($options); } - protected function setOptions(OptionsParser $parser) + protected function configure(OptionsResolver $resolver) { - $parser->setRequired(array( + $resolver->setRequired(array( 'firstName', 'lastName', - 'age', )); - $parser->setDefaults(array( + $resolver->setDefaults(array( 'age' => null, 'gender' => function (Options $options) { if (self::isKnownMaleName($options['firstName'])) { @@ -46,7 +45,7 @@ possible. }, )); - $parser->setAllowedValues(array( + $resolver->setAllowedValues(array( 'gender' => array('male', 'female'), )); } @@ -69,35 +68,37 @@ We can also override the default values of the optional options: 'age' => 30, )); -Options can be added or changed in subclasses by overriding the `setOptions` +Options can be added or changed in subclasses by overriding the `configure` method: - use Symfony\Component\OptionsParser\OptionsParser; - use Symfony\Component\OptionsParser\Options; + use Symfony\Component\OptionsResolver\OptionsResolver; + use Symfony\Component\OptionsResolver\Options; class Employee extends Person { - protected function setOptions(OptionsParser $parser) + protected function configure(OptionsResolver $resolver) { - parent::setOptions($parser); + parent::configure($resolver); - $parser->setRequired(array( + $resolver->setRequired(array( 'birthDate', )); - $parser->setDefaults(array( + $resolver->setDefaults(array( // $previousValue contains the default value configured in the // parent class 'age' => function (Options $options, $previousValue) { - return self::configureAgeFromBirthDate($options['birthDate']); + return self::calculateAge($options['birthDate']); } )); } } + + Resources --------- You can run the unit tests with the following command: - phpunit -c src/Symfony/Component/OptionsParser/ \ No newline at end of file + phpunit diff --git a/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php similarity index 55% rename from src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php rename to src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php index 70740e629f..c3acc6ceb3 100644 --- a/src/Symfony/Component/OptionsParser/Tests/OptionParserTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php @@ -9,23 +9,23 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser\Tests; +namespace Symfony\Component\OptionsResolver\Tests; -use Symfony\Component\OptionsParser\OptionsParser; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\OptionsResolver\Options; -class OptionsParserTest extends \PHPUnit_Framework_TestCase +class OptionsResolverTest extends \PHPUnit_Framework_TestCase { private $options; protected function setUp() { - $this->parser = new OptionsParser(); + $this->resolver = new OptionsResolver(); } - public function testParse() + public function testResolve() { - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'one' => '1', 'two' => '2', )); @@ -37,12 +37,12 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'one' => '1', 'two' => '20', - ), $this->parser->parse($options)); + ), $this->resolver->resolve($options)); } - public function testParseLazy() + public function testResolveLazy() { - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'one' => '1', 'two' => function (Options $options) { return '20'; @@ -52,12 +52,12 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'one' => '1', 'two' => '20', - ), $this->parser->parse(array())); + ), $this->resolver->resolve(array())); } - public function testParseLazyDependencyOnOptional() + public function testResolveLazyDependencyOnOptional() { - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'one' => '1', 'two' => function (Options $options) { return $options['one'] . '2'; @@ -71,18 +71,18 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'one' => '10', 'two' => '102', - ), $this->parser->parse($options)); + ), $this->resolver->resolve($options)); } - public function testParseLazyDependencyOnMissingOptionalWithoutDefault() + public function testResolveLazyDependencyOnMissingOptionalWithoutDefault() { $test = $this; - $this->parser->setOptional(array( + $this->resolver->setOptional(array( 'one', )); - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'two' => function (Options $options) use ($test) { $test->assertFalse(isset($options['one'])); @@ -95,18 +95,18 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'two' => '2', - ), $this->parser->parse($options)); + ), $this->resolver->resolve($options)); } - public function testParseLazyDependencyOnOptionalWithoutDefault() + public function testResolveLazyDependencyOnOptionalWithoutDefault() { $test = $this; - $this->parser->setOptional(array( + $this->resolver->setOptional(array( 'one', )); - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'two' => function (Options $options) use ($test) { $test->assertTrue(isset($options['one'])); @@ -121,15 +121,15 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'one' => '10', 'two' => '102', - ), $this->parser->parse($options)); + ), $this->resolver->resolve($options)); } - public function testParseLazyDependencyOnRequired() + public function testResolveLazyDependencyOnRequired() { - $this->parser->setRequired(array( + $this->resolver->setRequired(array( 'one', )); - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'two' => function (Options $options) { return $options['one'] . '2'; }, @@ -142,20 +142,20 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'one' => '10', 'two' => '102', - ), $this->parser->parse($options)); + ), $this->resolver->resolve($options)); } - public function testParseLazyReplaceDefaults() + public function testResolveLazyReplaceDefaults() { $test = $this; - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'one' => function (Options $options) use ($test) { $test->fail('Previous closure should not be executed'); }, )); - $this->parser->replaceDefaults(array( + $this->resolver->replaceDefaults(array( 'one' => function (Options $options, $previousValue) { return '1'; }, @@ -163,56 +163,56 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'one' => '1', - ), $this->parser->parse(array())); + ), $this->resolver->resolve(array())); } /** - * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException + * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException */ - public function testParseFailsIfNonExistingOption() + public function testResolveFailsIfNonExistingOption() { - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'one' => '1', )); - $this->parser->setRequired(array( + $this->resolver->setRequired(array( 'two', )); - $this->parser->setOptional(array( + $this->resolver->setOptional(array( 'three', )); - $this->parser->parse(array( + $this->resolver->resolve(array( 'foo' => 'bar', )); } /** - * @expectedException Symfony\Component\OptionsParser\Exception\MissingOptionsException + * @expectedException Symfony\Component\OptionsResolver\Exception\MissingOptionsException */ - public function testParseFailsIfMissingRequiredOption() + public function testResolveFailsIfMissingRequiredOption() { - $this->parser->setRequired(array( + $this->resolver->setRequired(array( 'one', )); - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'two' => '2', )); - $this->parser->parse(array( + $this->resolver->resolve(array( 'two' => '20', )); } - public function testParseSucceedsIfOptionValueAllowed() + public function testResolveSucceedsIfOptionValueAllowed() { - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'one' => '1', )); - $this->parser->setAllowedValues(array( + $this->resolver->setAllowedValues(array( 'one' => array('1', 'one'), )); @@ -222,21 +222,21 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'one' => 'one', - ), $this->parser->parse($options)); + ), $this->resolver->resolve($options)); } - public function testParseSucceedsIfOptionValueAllowed2() + public function testResolveSucceedsIfOptionValueAllowed2() { - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'one' => '1', 'two' => '2', )); - $this->parser->addAllowedValues(array( + $this->resolver->addAllowedValues(array( 'one' => array('1'), 'two' => array('2'), )); - $this->parser->addAllowedValues(array( + $this->resolver->addAllowedValues(array( 'one' => array('one'), 'two' => array('two'), )); @@ -249,43 +249,43 @@ class OptionsParserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'one' => '1', 'two' => 'two', - ), $this->parser->parse($options)); + ), $this->resolver->resolve($options)); } /** - * @expectedException Symfony\Component\OptionsParser\Exception\InvalidOptionsException + * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException */ - public function testParseFailsIfOptionValueNotAllowed() + public function testResolveFailsIfOptionValueNotAllowed() { - $this->parser->setDefaults(array( + $this->resolver->setDefaults(array( 'one' => '1', )); - $this->parser->setAllowedValues(array( + $this->resolver->setAllowedValues(array( 'one' => array('1', 'one'), )); - $this->parser->parse(array( + $this->resolver->resolve(array( 'one' => '2', )); } /** - * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException + * @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException */ public function testSetRequiredFailsIfDefaultIsPassed() { - $this->parser->setRequired(array( + $this->resolver->setRequired(array( 'one' => '1', )); } /** - * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException + * @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException */ public function testSetOptionalFailsIfDefaultIsPassed() { - $this->parser->setOptional(array( + $this->resolver->setOptional(array( 'one' => '1', )); } diff --git a/src/Symfony/Component/OptionsParser/Tests/OptionsTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php similarity index 91% rename from src/Symfony/Component/OptionsParser/Tests/OptionsTest.php rename to src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php index 058a738369..f1c70acc0f 100644 --- a/src/Symfony/Component/OptionsParser/Tests/OptionsTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\OptionsParser\Tests; +namespace Symfony\Component\OptionsResolver\Tests; -use Symfony\Component\OptionsParser\Options; +use Symfony\Component\OptionsResolver\Options; class OptionsTest extends \PHPUnit_Framework_TestCase { @@ -50,7 +50,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException + * @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException */ public function testSetNotSupportedAfterGet() { @@ -60,7 +60,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException + * @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException */ public function testUnsetNotSupportedAfterGet() { @@ -151,7 +151,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\OptionsParser\Exception\OptionDefinitionException + * @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException */ public function testLazyOptionDisallowCyclicDependencies() { diff --git a/src/Symfony/Component/OptionsParser/Tests/bootstrap.php b/src/Symfony/Component/OptionsResolver/Tests/bootstrap.php similarity index 82% rename from src/Symfony/Component/OptionsParser/Tests/bootstrap.php rename to src/Symfony/Component/OptionsResolver/Tests/bootstrap.php index 547252172d..328ecb2d1e 100644 --- a/src/Symfony/Component/OptionsParser/Tests/bootstrap.php +++ b/src/Symfony/Component/OptionsResolver/Tests/bootstrap.php @@ -10,8 +10,8 @@ */ spl_autoload_register(function ($class) { - if (0 === strpos(ltrim($class, '/'), 'Symfony\Component\OptionsParser')) { - if (file_exists($file = __DIR__.'/../'.substr(str_replace('\\', '/', $class), strlen('Symfony\Component\OptionsParser')).'.php')) { + if (0 === strpos(ltrim($class, '/'), 'Symfony\Component\OptionsResolver')) { + if (file_exists($file = __DIR__.'/../'.substr(str_replace('\\', '/', $class), strlen('Symfony\Component\OptionsResolver')).'.php')) { require_once $file; } } diff --git a/src/Symfony/Component/OptionsParser/composer.json b/src/Symfony/Component/OptionsResolver/composer.json similarity index 70% rename from src/Symfony/Component/OptionsParser/composer.json rename to src/Symfony/Component/OptionsResolver/composer.json index 6ea87546f9..bd74964548 100644 --- a/src/Symfony/Component/OptionsParser/composer.json +++ b/src/Symfony/Component/OptionsResolver/composer.json @@ -1,7 +1,7 @@ { - "name": "symfony/options-parser", + "name": "symfony/options-resolver", "type": "library", - "description": "Symfony OptionsParser Component", + "description": "Symfony OptionsResolver Component", "keywords": [], "homepage": "http://symfony.com", "license": "MIT", @@ -19,9 +19,9 @@ "php": ">=5.3.2" }, "autoload": { - "psr-0": { "Symfony\\Component\\OptionsParser": "" } + "psr-0": { "Symfony\\Component\\OptionsResolver": "" } }, - "target-dir": "Symfony/Component/OptionsParser", + "target-dir": "Symfony/Component/OptionsResolver", "extra": { "branch-alias": { "dev-master": "2.1-dev" diff --git a/src/Symfony/Component/OptionsParser/phpunit.xml.dist b/src/Symfony/Component/OptionsResolver/phpunit.xml.dist similarity index 91% rename from src/Symfony/Component/OptionsParser/phpunit.xml.dist rename to src/Symfony/Component/OptionsResolver/phpunit.xml.dist index a10fc7a8df..639bb3fbc6 100644 --- a/src/Symfony/Component/OptionsParser/phpunit.xml.dist +++ b/src/Symfony/Component/OptionsResolver/phpunit.xml.dist @@ -12,7 +12,7 @@ bootstrap="Tests/bootstrap.php" > - + ./Tests/ From 95454f5f6b7a3c929cdb6ff8c838f3f436d281ac Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 10 May 2012 19:26:05 +0200 Subject: [PATCH 04/12] [OptionsResolver] Fixed typos --- .../OptionsResolver/OptionsResolver.php | 25 ++++++++++--------- .../Component/OptionsResolver/README.md | 4 +-- .../Tests/OptionResolverTest.php | 5 +++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 2f4d8d2031..1f7c14a46b 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -57,12 +57,12 @@ class OptionsResolver /** * Sets default option values. * - * @param array $options A list of option names as keys and default values - * as values. The option values may be closures - * of the following signatures: + * @param array $defaultValues A list of option names as keys and default values + * as values. The option values may be closures + * of the following signatures: * - * - function (Options $options) - * - function (Options $options, $previousValue) + * - function (Options $options) + * - function (Options $options, $previousValue) */ public function setDefaults(array $defaultValues) { @@ -80,11 +80,11 @@ class OptionsResolver * performance if the previous default value is calculated by an expensive * closure. * - * @param array $options A list of option names as keys and default values - * as values. The option values may be closures - * of the following signature: + * @param array $defaultValues A list of option names as keys and default values + * as values. The option values may be closures + * of the following signature: * - * - function (Options $options) + * - function (Options $options) */ public function replaceDefaults(array $defaultValues) { @@ -214,10 +214,11 @@ class OptionsResolver * Validates that the given option names exist and throws an exception * otherwise. * - * @param array $optionNames A list of option names. + * @param array $optionNames A list of option names. * - * @throws InvalidOptionsException If any of the options has not been - * defined. + * @throws InvalidOptionsException If any of the options has not been + * defined. + * @throws MissingOptionsException If a required option is missing. */ private function validateOptionNames(array $optionNames) { diff --git a/src/Symfony/Component/OptionsResolver/README.md b/src/Symfony/Component/OptionsResolver/README.md index 5e0f08ce45..d0bfae6ef6 100644 --- a/src/Symfony/Component/OptionsResolver/README.md +++ b/src/Symfony/Component/OptionsResolver/README.md @@ -22,7 +22,7 @@ possible, and may only be one of "male" and "female". public function __construct(array $options = array()) { $resolver = new OptionsResolver(); - $this->configureOptions($resolver); + $this->configure($resolver); $this->options = $resolver->resolve($options); } @@ -53,7 +53,7 @@ possible, and may only be one of "male" and "female". We can now easily instantiate a Person object: - // 'gender' is implicitely set to 'female' + // 'gender' is implicitly set to 'female' $person = new Person(array( 'firstName' => 'Jane', 'lastName' => 'Doe', diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php index c3acc6ceb3..84b69c1f5b 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php @@ -16,7 +16,10 @@ use Symfony\Component\OptionsResolver\Options; class OptionsResolverTest extends \PHPUnit_Framework_TestCase { - private $options; + /** + * @var OptionsResolver + */ + private $resolver; protected function setUp() { From 876fd9ba1745e5fcb863cea19ffde26502f61df9 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 11 May 2012 09:41:52 +0200 Subject: [PATCH 05/12] [OptionsResolver] Implemented fluid interface --- .../OptionsResolver/OptionsResolver.php | 24 +++++++++++++++++++ .../Tests/OptionResolverTest.php | 19 +++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 1f7c14a46b..a9513a5f35 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -63,6 +63,8 @@ class OptionsResolver * * - function (Options $options) * - function (Options $options, $previousValue) + * + * @return OptionsResolver The resolver instance. */ public function setDefaults(array $defaultValues) { @@ -70,6 +72,8 @@ class OptionsResolver $this->defaultOptions[$option] = $value; $this->knownOptions[$option] = true; } + + return $this; } /** @@ -85,6 +89,8 @@ class OptionsResolver * of the following signature: * * - function (Options $options) + * + * @return OptionsResolver The resolver instance. */ public function replaceDefaults(array $defaultValues) { @@ -93,6 +99,8 @@ class OptionsResolver $this->defaultOptions[$option] = $value; $this->knownOptions[$option] = true; } + + return $this; } /** @@ -105,6 +113,8 @@ class OptionsResolver * * @param array $optionNames A list of option names. * + * @return OptionsResolver The resolver instance. + * * @throws OptionDefinitionException When trying to pass default values. */ public function setOptional(array $optionNames) @@ -116,6 +126,8 @@ class OptionsResolver $this->knownOptions[$option] = true; } + + return $this; } /** @@ -125,6 +137,8 @@ class OptionsResolver * * @param array $optionNames A list of option names. * + * @return OptionsResolver The resolver instance. + * * @throws OptionDefinitionException When trying to pass default values. */ public function setRequired(array $optionNames) @@ -137,6 +151,8 @@ class OptionsResolver $this->knownOptions[$option] = true; $this->requiredOptions[$option] = true; } + + return $this; } /** @@ -146,6 +162,8 @@ class OptionsResolver * with values acceptable for that option as * values. * + * @return OptionsResolver The resolver instance. + * * @throws InvalidOptionsException If an option has not been defined for * which an allowed value is set. */ @@ -154,6 +172,8 @@ class OptionsResolver $this->validateOptionNames(array_keys($allowedValues)); $this->allowedValues = array_replace($this->allowedValues, $allowedValues); + + return $this; } /** @@ -165,6 +185,8 @@ class OptionsResolver * with values acceptable for that option as * values. * + * @return OptionsResolver The resolver instance. + * * @throws InvalidOptionsException If an option has not been defined for * which an allowed value is set. */ @@ -173,6 +195,8 @@ class OptionsResolver $this->validateOptionNames(array_keys($allowedValues)); $this->allowedValues = array_merge_recursive($this->allowedValues, $allowedValues); + + return $this; } /** diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php index 84b69c1f5b..72e68a4261 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php @@ -292,4 +292,23 @@ class OptionsResolverTest extends \PHPUnit_Framework_TestCase 'one' => '1', )); } + + public function testFluidInterface() + { + $this->resolver->setDefaults(array('one' => '1')) + ->replaceDefaults(array('one' => '2')) + ->setAllowedValues(array('one' => array('1', '2'))) + ->addAllowedValues(array('one' => array('3'))) + ->setRequired(array('two')) + ->setOptional(array('three')); + + $options = array( + 'two' => '2', + ); + + $this->assertEquals(array( + 'one' => '2', + 'two' => '2', + ), $this->resolver->resolve($options)); + } } From 9c76750cb8aea89ea51b28fbda8fb6081a50f434 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 11 May 2012 10:31:19 +0200 Subject: [PATCH 06/12] [OptionsResolver] Fixed doc and block nesting --- .../Component/OptionsResolver/Options.php | 10 +++++----- .../OptionsResolver/OptionsResolver.php | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 78ebf30535..dffdc113c7 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -165,7 +165,7 @@ class Options implements ArrayAccess, Iterator } /** - * @see Iterator::current() + * {@inheritdoc} */ public function current() { @@ -173,7 +173,7 @@ class Options implements ArrayAccess, Iterator } /** - * @see Iterator::next() + * {@inheritdoc} */ public function next() { @@ -181,7 +181,7 @@ class Options implements ArrayAccess, Iterator } /** - * @see Iterator::key() + * {@inheritdoc} */ public function key() { @@ -189,7 +189,7 @@ class Options implements ArrayAccess, Iterator } /** - * @see Iterator::valid() + * {@inheritdoc} */ public function valid() { @@ -197,7 +197,7 @@ class Options implements ArrayAccess, Iterator } /** - * @see Iterator::rewind() + * {@inheritdoc} */ public function rewind() { diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index a9513a5f35..1e738af6a2 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -253,11 +253,11 @@ class OptionsResolver sort($diff); - if (count($diff) > 1) { - throw new InvalidOptionsException(sprintf('The options "%s" do not exist. Known options are: "%s"', implode('", "', $diff), implode('", "', $knownOptions))); - } - if (count($diff) > 0) { + if (count($diff) > 1) { + throw new InvalidOptionsException(sprintf('The options "%s" do not exist. Known options are: "%s"', implode('", "', $diff), implode('", "', $knownOptions))); + } + throw new InvalidOptionsException(sprintf('The option "%s" does not exist. Known options are: "%s"', current($diff), implode('", "', $knownOptions))); } @@ -268,11 +268,11 @@ class OptionsResolver sort($diff); - if (count($diff) > 1) { - throw new MissingOptionsException(sprintf('The options "%s" are missing.', implode('", "', $diff))); - } - if (count($diff) > 0) { + if (count($diff) > 1) { + throw new MissingOptionsException(sprintf('The options "%s" are missing.', implode('", "', $diff))); + } + throw new MissingOptionsException(sprintf('The option "%s" is missing.', current($diff))); } } From 6ce68b1b05e7da6b2f7f495e03b0d2915bc9a719 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 11 May 2012 10:32:02 +0200 Subject: [PATCH 07/12] [OptionsResolver] Removed reference to non-existing property --- src/Symfony/Component/OptionsResolver/Options.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index dffdc113c7..d168864e98 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -160,7 +160,6 @@ class Options implements ArrayAccess, Iterator } unset($this->options[$option]); - unset($this->allowedValues[$option]); unset($this->lock[$option]); } From 16f7d20dff9ff70418e7c4c19cefe02c8329f6f5 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 11 May 2012 15:37:27 +0200 Subject: [PATCH 08/12] [OptionsResolver] Improved implementation and clarity of the Options class --- .../Component/OptionsResolver/Options.php | 359 ++++++++++++++---- .../OptionsResolver/OptionsResolver.php | 15 +- .../Tests/OptionResolverTest.php | 3 + .../OptionsResolver/Tests/OptionsTest.php | 151 +++++--- 4 files changed, 397 insertions(+), 131 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index d168864e98..272858cf20 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -12,8 +12,10 @@ namespace Symfony\Component\OptionsResolver; use ArrayAccess; +use Closure; use Iterator; use OutOfBoundsException; +use Countable; use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; /** @@ -21,7 +23,7 @@ use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; * * @author Bernhard Schussek */ -class Options implements ArrayAccess, Iterator +class Options implements ArrayAccess, Iterator, Countable { /** * A list of option values and LazyOption instances. @@ -29,6 +31,12 @@ class Options implements ArrayAccess, Iterator */ private $options = array(); + /** + * A list storing the names of all LazyOption instances as keys. + * @var array + */ + private $lazy = array(); + /** * A list of Boolean locks for each LazyOption. * @var array @@ -36,13 +44,150 @@ class Options implements ArrayAccess, Iterator private $lock = array(); /** - * Whether the options have already been resolved. + * Whether at least one option has already been read. * - * Once resolved, no new options can be added or changed anymore. + * Once reading, the options cannot be changed anymore. This is + * necessary in order to avoid inconsistencies during the resolving + * process. If any option is changed after reading, all evaluated + * lazy options that depend on this option would become invalid. * * @var Boolean */ - private $resolved = false; + private $reading = false; + + /** + * Sets the value of a given option. + * + * You can set lazy options by passing a closure with the following + * signature: + * + * + * function (Options $options) + * + * + * This closure will be evaluated once the option is read using + * {@link get()}. The closure has access to the resolved values of + * other options through the passed {@link Options} instance. + * + * @param string $option The name of the option. + * @param mixed $value The value of the option. + * + * @throws OptionDefinitionException If options have already been read. + * Once options are read, the container + * becomes immutable. + */ + public function set($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->reading) { + throw new OptionDefinitionException('Options cannot be set anymore once options have been read.'); + } + + // Setting is equivalent to overloading while discarding the previous + // option value + unset($this->options[$option]); + + $this->overload($option, $value); + } + + /** + * Replaces the contents of the container with the given options. + * + * This method is a shortcut for {@link clear()} with subsequent + * calls to {@link set()}. + * + * @param array $options The options to set. + * + * @throws OptionDefinitionException If options have already been read. + * Once options are read, the container + * becomes immutable. + */ + public function replace(array $options) + { + if ($this->reading) { + throw new OptionDefinitionException('Options cannot be replaced anymore once options have been read.'); + } + + $this->options = array(); + foreach ($options as $option => $value) { + $this->options[$option] = $value; + } + } + + /** + * Overloads the value of a given option. + * + * Contrary to {@link set()}, this method keeps the previous default + * value of the option so that you can access it if you pass a closure. + * Passed closures should have the following signature: + * + * + * function (Options $options, $previousValue) + * + * + * The second parameter passed to the closure is the previous default + * value of the option. + * + * @param string $option The option name. + * @param mixed $value The option value. + * + * @throws OptionDefinitionException If options have already been read. + * Once options are read, the container + * becomes immutable. + */ + public function overload($option, $value) + { + if ($this->reading) { + throw new OptionDefinitionException('Options cannot be overloaded anymore once options have been read.'); + } + + $newValue = $value; + + // If an option is a closure that should be evaluated lazily, store it + // inside a LazyOption instance. + if ($this->isEvaluatedLazily($value)) { + $currentValue = isset($this->options[$option]) ? $this->options[$option] : null; + $newValue = new LazyOption($value, $currentValue); + + // Store locks for lazy options to detect cyclic dependencies + $this->lock[$option] = false; + + // Store which options are lazy for more efficient resolving + $this->lazy[$option] = true; + } + + $this->options[$option] = $newValue; + } + + /** + * Returns the value of the given option. + * + * If the option was a lazy option, it is evaluated now. + * + * @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. + */ + public function get($option) + { + $this->reading = true; + + if (!array_key_exists($option, $this->options)) { + throw new OutOfBoundsException('The option "' . $option . '" does not exist.'); + } + + if (isset($this->lazy[$option])) { + $this->resolve($option); + } + + return $this->options[$option]; + } /** * Returns whether the given option exists. @@ -50,18 +195,84 @@ class Options implements ArrayAccess, Iterator * @param string $option The option name. * * @return Boolean Whether the option exists. + */ + public function has($option) + { + return isset($this->options[$option]); + } + + /** + * Removes the 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. + */ + public function remove($option) + { + if ($this->reading) { + throw new OptionDefinitionException('Options cannot be removed anymore once options have been read.'); + } + + unset($this->options[$option]); + unset($this->lock[$option]); + } + + /** + * Removes all options. + * + * @throws OptionDefinitionException If options have already been read. + * Once options are read, the container + * becomes immutable. + */ + public function clear() + { + if ($this->reading) { + throw new OptionDefinitionException('Options cannot be cleared anymore once options have been read.'); + } + + $this->options = array(); + } + + /** + * Returns the values of all options. + * + * Lazy options are evaluated at this point. + * + * @return array The option values. + */ + public function all() + { + $this->reading = true; + + // Create a copy because resolve() modifies the array + $lazy = $this->lazy; + + foreach ($lazy as $option => $isLazy) { + $this->resolve($option); + } + + return $this->options; + } + + /** + * Equivalent to {@link has()}. + * + * @param string $option The option name. + * + * @return Boolean Whether the option exists. * * @see ArrayAccess::offsetExists() */ public function offsetExists($option) { - return isset($this->options[$option]); + return $this->has($option); } /** - * Returns the value of the given option. - * - * After reading an option for the first time, this object becomes + * Equivalent to {@link get()}. * * @param string $option The option name. * @@ -75,31 +286,11 @@ class Options implements ArrayAccess, Iterator */ 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]; + return $this->get($option); } /** - * Sets the value of a given option. + * Equivalent to {@link set()}. * * @param string $option The name of the option. * @param mixed $value The value of the option. May be a closure with a @@ -109,41 +300,15 @@ class Options implements ArrayAccess, Iterator * 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; + $this->set($option, $value); } /** - * Removes an option with the given name. + * Equivalent to {@link remove()}. * * @param string $option The option name. * @@ -155,12 +320,7 @@ class Options implements ArrayAccess, Iterator */ public function offsetUnset($option) { - if ($this->resolved) { - throw new OptionDefinitionException('Options cannot be unset after reading options'); - } - - unset($this->options[$option]); - unset($this->lock[$option]); + $this->remove($option); } /** @@ -202,4 +362,75 @@ class Options implements ArrayAccess, Iterator { reset($this->options); } + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->options); + } + + /** + * Evaluates the given option if it is a lazy option. + * + * The evaluated value is written into the options array. The closure for + * evaluating the option is discarded afterwards. + * + * @param string $option The option to evaluate. + * + * @throws OptionDefinitionException If the option has a cyclic dependency + * on another option. + */ + private function resolve($option) + { + 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; + + // The option now isn't lazy anymore + unset($this->lazy[$option]); + } + } + + /** + * Returns whether the option is a lazy option closure. + * + * Lazy option closure expect an {@link Options} instance + * in their first parameter. + * + * @param mixed $value The option value to test. + * + * @return Boolean Whether it is a lazy option closure. + */ + private static function isEvaluatedLazily($value) + { + if (!$value instanceof Closure) { + return false; + } + + $reflClosure = new \ReflectionFunction($value); + $params = $reflClosure->getParameters(); + + if (count($params) < 1) { + return false; + } + + if (null === $params[0]->getClass()) { + return false; + } + + return __CLASS__ === $params[0]->getClass()->getName(); + } } diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 1e738af6a2..3d1782d978 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -69,7 +69,7 @@ class OptionsResolver public function setDefaults(array $defaultValues) { foreach ($defaultValues as $option => $value) { - $this->defaultOptions[$option] = $value; + $this->defaultOptions->overload($option, $value); $this->knownOptions[$option] = true; } @@ -95,8 +95,7 @@ class OptionsResolver public function replaceDefaults(array $defaultValues) { foreach ($defaultValues as $option => $value) { - unset($this->defaultOptions[$option]); - $this->defaultOptions[$option] = $value; + $this->defaultOptions->set($option, $value); $this->knownOptions[$option] = true; } @@ -204,7 +203,7 @@ class OptionsResolver * * @param array $options The custom option values. * - * @return array A list of options and their values. + * @return array A list of options and their values. * * @throws InvalidOptionsException If any of the passed options has not * been defined or does not contain an @@ -222,16 +221,16 @@ class OptionsResolver // Override options set by the user foreach ($options as $option => $value) { - $combinedOptions[$option] = $value; + $combinedOptions->set($option, $value); } // Resolve options - $combinedOptions = iterator_to_array($combinedOptions); + $resolvedOptions = $combinedOptions->all(); // Validate against allowed values - $this->validateOptionValues($combinedOptions); + $this->validateOptionValues($resolvedOptions); - return $combinedOptions; + return $resolvedOptions; } /** diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php index 72e68a4261..699c0606db 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php @@ -87,6 +87,7 @@ class OptionsResolverTest extends \PHPUnit_Framework_TestCase $this->resolver->setDefaults(array( 'two' => function (Options $options) use ($test) { + /* @var \PHPUnit_Framework_TestCase $test */ $test->assertFalse(isset($options['one'])); return '2'; @@ -111,6 +112,7 @@ class OptionsResolverTest extends \PHPUnit_Framework_TestCase $this->resolver->setDefaults(array( 'two' => function (Options $options) use ($test) { + /* @var \PHPUnit_Framework_TestCase $test */ $test->assertTrue(isset($options['one'])); return $options['one'] . '2'; @@ -154,6 +156,7 @@ class OptionsResolverTest extends \PHPUnit_Framework_TestCase $this->resolver->setDefaults(array( 'one' => function (Options $options) use ($test) { + /* @var \PHPUnit_Framework_TestCase $test */ $test->fail('Previous closure should not be executed'); }, )); diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php index f1c70acc0f..594151ff6b 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php @@ -15,6 +15,9 @@ use Symfony\Component\OptionsResolver\Options; class OptionsTest extends \PHPUnit_Framework_TestCase { + /** + * @var Options + */ private $options; protected function setUp() @@ -41,12 +44,20 @@ class OptionsTest extends \PHPUnit_Framework_TestCase $this->assertEquals(0, $this->options['foo']); } + public function testCountable() + { + $this->options->set('foo', 0); + $this->options->set('bar', 1); + + $this->assertCount(2, $this->options); + } + /** * @expectedException \OutOfBoundsException */ public function testGetNonExisting() { - $this->options['foo']; + $this->options->get('foo'); } /** @@ -54,115 +65,137 @@ class OptionsTest extends \PHPUnit_Framework_TestCase */ public function testSetNotSupportedAfterGet() { - $this->options['foo'] = 'bar'; - $this->options['foo']; - $this->options['foo'] = 'baz'; + $this->options->set('foo', 'bar'); + $this->options->get('foo'); + $this->options->set('foo', 'baz'); } /** * @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException */ - public function testUnsetNotSupportedAfterGet() + public function testRemoveNotSupportedAfterGet() { - $this->options['foo'] = 'bar'; - $this->options['foo']; - unset($this->options['foo']); + $this->options->set('foo', 'bar'); + $this->options->get('foo'); + $this->options->remove('foo'); } - public function testLazyOption() + public function testSetLazyOption() { $test = $this; - $this->options['foo'] = function (Options $options) use ($test) { + $this->options->set('foo', function (Options $options) use ($test) { return 'dynamic'; - }; + }); - $this->assertEquals('dynamic', $this->options['foo']); + $this->assertEquals('dynamic', $this->options->get('foo')); } - public function testLazyOptionWithEagerPreviousValue() + public function testSetDiscardsPreviousValue() { $test = $this; // defined by superclass - $this->options['foo'] = 'bar'; + $this->options->set('foo', 'bar'); // defined by subclass - $this->options['foo'] = function (Options $options, $previousValue) use ($test) { - $test->assertEquals('bar', $previousValue); + $this->options->set('foo', function (Options $options, $previousValue) use ($test) { + /* @var \PHPUnit_Framework_TestCase $test */ + $test->assertNull($previousValue); - return 'dynamic'; - }; + return 'dynamic'; + }); - $this->assertEquals('dynamic', $this->options['foo']); + $this->assertEquals('dynamic', $this->options->get('foo')); } - public function testLazyOptionWithLazyPreviousValue() + public function testOverloadKeepsPreviousValue() { $test = $this; // defined by superclass - $this->options['foo'] = function (Options $options) { - return 'bar'; - }; + $this->options->set('foo', 'bar'); // defined by subclass - $this->options['foo'] = function (Options $options, $previousValue) use ($test) { - $test->assertEquals('bar', $previousValue); - - 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']); + $this->options->overload('foo', function (Options $options, $previousValue) use ($test) { + /* @var \PHPUnit_Framework_TestCase $test */ + $test->assertEquals('bar', $previousValue); return 'dynamic'; - }; + }); - $this->assertEquals('bar', $this->options['foo']); - $this->assertEquals('dynamic', $this->options['bam']); + $this->assertEquals('dynamic', $this->options->get('foo')); } - public function testLazyOptionWithLazyDependency() + public function testPreviousValueIsEvaluatedIfLazy() { $test = $this; - $this->options['foo'] = function (Options $options) { + // defined by superclass + $this->options->set('foo', function (Options $options) { return 'bar'; - }; + }); - $this->options['bam'] = function (Options $options) use ($test) { - $test->assertEquals('bar', $options['foo']); + // defined by subclass + $this->options->overload('foo', function (Options $options, $previousValue) use ($test) { + /* @var \PHPUnit_Framework_TestCase $test */ + $test->assertEquals('bar', $previousValue); return 'dynamic'; - }; + }); - $this->assertEquals('bar', $this->options['foo']); - $this->assertEquals('dynamic', $this->options['bam']); + $this->assertEquals('dynamic', $this->options->get('foo')); + } + + public function testLazyOptionCanAccessOtherOptions() + { + $test = $this; + + $this->options->set('foo', 'bar'); + + $this->options->set('bam', function (Options $options) use ($test) { + /* @var \PHPUnit_Framework_TestCase $test */ + $test->assertEquals('bar', $options->get('foo')); + + return 'dynamic'; + }); + + $this->assertEquals('bar', $this->options->get('foo')); + $this->assertEquals('dynamic', $this->options->get('bam')); + } + + public function testLazyOptionCanAccessOtherLazyOptions() + { + $test = $this; + + $this->options->set('foo', function (Options $options) { + return 'bar'; + }); + + $this->options->set('bam', function (Options $options) use ($test) { + /* @var \PHPUnit_Framework_TestCase $test */ + $test->assertEquals('bar', $options->get('foo')); + + return 'dynamic'; + }); + + $this->assertEquals('bar', $this->options->get('foo')); + $this->assertEquals('dynamic', $this->options->get('bam')); } /** * @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException */ - public function testLazyOptionDisallowCyclicDependencies() + public function testFailForCyclicDependencies() { - $this->options['foo'] = function (Options $options) { - $options['bam']; - }; + $this->options->set('foo', function (Options $options) { + $options->get('bam'); + }); - $this->options['bam'] = function (Options $options) { - $options['foo']; - }; + $this->options->set('bam', function (Options $options) { + $options->get('foo'); + }); - $this->options['foo']; + $this->options->get('foo'); } } From 2b46975e322b55e255f5cc7bb1148c8d90a53458 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 11 May 2012 15:53:40 +0200 Subject: [PATCH 09/12] [OptionsResolver] Fixed Options::replace() method --- .../Component/OptionsResolver/Options.php | 3 ++- .../OptionsResolver/Tests/OptionsTest.php | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 272858cf20..61d01c918a 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -111,8 +111,9 @@ class Options implements ArrayAccess, Iterator, Countable } $this->options = array(); + foreach ($options as $option => $value) { - $this->options[$option] = $value; + $this->set($option, $value); } } diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php index 594151ff6b..ba74910fcd 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php @@ -198,4 +198,21 @@ class OptionsTest extends \PHPUnit_Framework_TestCase $this->options->get('foo'); } + + public function testReplaceClearsAndSets() + { + $this->options->set('one', '1'); + + $this->options->replace(array( + 'two' => '2', + 'three' => function (Options $options) { + return '2' === $options['two'] ? '3' : 'foo'; + } + )); + + $this->assertEquals(array( + 'two' => '2', + 'three' => '3', + ), $this->options->all()); + } } From d60626efd5a17fcca018e8d6edeb693de90f53ef Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 May 2012 18:53:26 +0200 Subject: [PATCH 10/12] [OptionsResolver] Fixed clear() and remove() method in Options class --- .../OptionsResolver/Exception/ExceptionInterface.php | 0 src/Symfony/Component/OptionsResolver/Options.php | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) mode change 100755 => 100644 src/Symfony/Component/OptionsResolver/Exception/ExceptionInterface.php diff --git a/src/Symfony/Component/OptionsResolver/Exception/ExceptionInterface.php b/src/Symfony/Component/OptionsResolver/Exception/ExceptionInterface.php old mode 100755 new mode 100644 diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 61d01c918a..25e47677f3 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -111,7 +111,7 @@ class Options implements ArrayAccess, Iterator, Countable } $this->options = array(); - + foreach ($options as $option => $value) { $this->set($option, $value); } @@ -219,6 +219,7 @@ class Options implements ArrayAccess, Iterator, Countable unset($this->options[$option]); unset($this->lock[$option]); + unset($this->lazy[$option]); } /** @@ -235,6 +236,8 @@ class Options implements ArrayAccess, Iterator, Countable } $this->options = array(); + $this->lock = array(); + $this->lazy = array(); } /** From 1c5f6c76c10fe5daadeea11150ebf3c606c4f3e7 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 May 2012 19:34:05 +0200 Subject: [PATCH 11/12] [OptionsResolver] Fixed issues mentioned in the PR comments --- .../Component/OptionsResolver/Options.php | 33 +++++++++---------- .../OptionsResolver/OptionsResolver.php | 6 ++-- .../Component/OptionsResolver/README.md | 2 +- .../Component/OptionsResolver/composer.json | 2 +- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 25e47677f3..49c2eef62c 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -11,11 +11,6 @@ namespace Symfony\Component\OptionsResolver; -use ArrayAccess; -use Closure; -use Iterator; -use OutOfBoundsException; -use Countable; use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; /** @@ -23,7 +18,7 @@ use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; * * @author Bernhard Schussek */ -class Options implements ArrayAccess, Iterator, Countable +class Options implements \ArrayAccess, \Iterator, \Countable { /** * A list of option values and LazyOption instances. @@ -46,9 +41,9 @@ class Options implements ArrayAccess, Iterator, Countable /** * Whether at least one option has already been read. * - * Once reading, the options cannot be changed anymore. This is + * Once read, the options cannot be changed anymore. This is * necessary in order to avoid inconsistencies during the resolving - * process. If any option is changed after reading, all evaluated + * process. If any option is changed after being read, all evaluated * lazy options that depend on this option would become invalid. * * @var Boolean @@ -146,6 +141,10 @@ class Options implements ArrayAccess, Iterator, Countable $newValue = $value; + // Reset lazy flag and locks by default + unset($this->lock[$option]); + unset($this->lazy[$option]); + // If an option is a closure that should be evaluated lazily, store it // inside a LazyOption instance. if ($this->isEvaluatedLazily($value)) { @@ -171,7 +170,7 @@ class Options implements ArrayAccess, Iterator, Countable * * @return mixed The option value. * - * @throws OutOfBoundsException If the option does not exist. + * @throws \OutOfBoundsException If the option does not exist. * @throws OptionDefinitionException If a cyclic dependency is detected * between two lazy options. */ @@ -180,7 +179,7 @@ class Options implements ArrayAccess, Iterator, Countable $this->reading = true; if (!array_key_exists($option, $this->options)) { - throw new OutOfBoundsException('The option "' . $option . '" does not exist.'); + throw new \OutOfBoundsException('The option "' . $option . '" does not exist.'); } if (isset($this->lazy[$option])) { @@ -268,7 +267,7 @@ class Options implements ArrayAccess, Iterator, Countable * * @return Boolean Whether the option exists. * - * @see ArrayAccess::offsetExists() + * @see \ArrayAccess::offsetExists() */ public function offsetExists($option) { @@ -282,11 +281,11 @@ class Options implements ArrayAccess, Iterator, Countable * * @return mixed The option value. * - * @throws OutOfBoundsException If the option does not exist. + * @throws \OutOfBoundsException If the option does not exist. * @throws OptionDefinitionException If a cyclic dependency is detected * between two lazy options. * - * @see ArrayAccess::offsetGet() + * @see \ArrayAccess::offsetGet() */ public function offsetGet($option) { @@ -304,7 +303,7 @@ class Options implements ArrayAccess, Iterator, Countable * Once options are read, the container * becomes immutable. * - * @see ArrayAccess::offsetSet() + * @see \ArrayAccess::offsetSet() */ public function offsetSet($option, $value) { @@ -320,7 +319,7 @@ class Options implements ArrayAccess, Iterator, Countable * Once options are read, the container * becomes immutable. * - * @see ArrayAccess::offsetUnset() + * @see \ArrayAccess::offsetUnset() */ public function offsetUnset($option) { @@ -418,9 +417,9 @@ class Options implements ArrayAccess, Iterator, Countable * * @return Boolean Whether it is a lazy option closure. */ - private static function isEvaluatedLazily($value) + static private function isEvaluatedLazily($value) { - if (!$value instanceof Closure) { + if (!$value instanceof \Closure) { return false; } diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 3d1782d978..be0eb03855 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -269,10 +269,12 @@ class OptionsResolver if (count($diff) > 0) { if (count($diff) > 1) { - throw new MissingOptionsException(sprintf('The options "%s" are missing.', implode('", "', $diff))); + throw new MissingOptionsException(sprintf('The required options "%s" are missing.', + implode('", + "', $diff))); } - throw new MissingOptionsException(sprintf('The option "%s" is missing.', current($diff))); + throw new MissingOptionsException(sprintf('The required option "%s" is missing.', current($diff))); } } diff --git a/src/Symfony/Component/OptionsResolver/README.md b/src/Symfony/Component/OptionsResolver/README.md index d0bfae6ef6..c2280011e4 100644 --- a/src/Symfony/Component/OptionsResolver/README.md +++ b/src/Symfony/Component/OptionsResolver/README.md @@ -1,5 +1,5 @@ OptionsResolver Component -====================== +========================= OptionsResolver helps at configuring objects with option arrays. diff --git a/src/Symfony/Component/OptionsResolver/composer.json b/src/Symfony/Component/OptionsResolver/composer.json index bd74964548..391eed1a6f 100644 --- a/src/Symfony/Component/OptionsResolver/composer.json +++ b/src/Symfony/Component/OptionsResolver/composer.json @@ -2,7 +2,7 @@ "name": "symfony/options-resolver", "type": "library", "description": "Symfony OptionsResolver Component", - "keywords": [], + "keywords": ["options", "config", "configuration"], "homepage": "http://symfony.com", "license": "MIT", "authors": [ From 95727ff5e72b84065a24da94d6ffdb2543ba5471 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Tue, 15 May 2012 10:12:07 +0200 Subject: [PATCH 12/12] [OptionsResolver] Updated PHP requirements to 5.3.3 --- src/Symfony/Component/OptionsResolver/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/OptionsResolver/composer.json b/src/Symfony/Component/OptionsResolver/composer.json index 391eed1a6f..dace3e381c 100644 --- a/src/Symfony/Component/OptionsResolver/composer.json +++ b/src/Symfony/Component/OptionsResolver/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=5.3.2" + "php": ">=5.3.3" }, "autoload": { "psr-0": { "Symfony\\Component\\OptionsResolver": "" }