aboutsummaryrefslogtreecommitdiffstats
path: root/library/symfony/options-resolver
diff options
context:
space:
mode:
authorAndrew Manning <tamanning@zoho.com>2016-05-01 22:29:51 -0400
committerAndrew Manning <tamanning@zoho.com>2016-05-01 22:29:51 -0400
commitc2d15e6c3bd8a29bae89d184a999ddac15fcb807 (patch)
tree96cbbfa98f131d830fbfd154f82e37383bb37bd8 /library/symfony/options-resolver
parentb1ae4d776c7c093c7f3ff6905e1a5302fbd5e3f6 (diff)
downloadvolse-hubzilla-c2d15e6c3bd8a29bae89d184a999ddac15fcb807.tar.gz
volse-hubzilla-c2d15e6c3bd8a29bae89d184a999ddac15fcb807.tar.bz2
volse-hubzilla-c2d15e6c3bd8a29bae89d184a999ddac15fcb807.zip
New plugin repo is cloned to /store/pluginrepos/REPONAME for analysis
Diffstat (limited to 'library/symfony/options-resolver')
-rw-r--r--library/symfony/options-resolver/CHANGELOG.md46
-rw-r--r--library/symfony/options-resolver/Exception/AccessException.php22
-rw-r--r--library/symfony/options-resolver/Exception/ExceptionInterface.php21
-rw-r--r--library/symfony/options-resolver/Exception/InvalidArgumentException.php21
-rw-r--r--library/symfony/options-resolver/Exception/InvalidOptionsException.php23
-rw-r--r--library/symfony/options-resolver/Exception/MissingOptionsException.php23
-rw-r--r--library/symfony/options-resolver/Exception/NoSuchOptionException.php26
-rw-r--r--library/symfony/options-resolver/Exception/OptionDefinitionException.php21
-rw-r--r--library/symfony/options-resolver/Exception/UndefinedOptionsException.php24
-rw-r--r--library/symfony/options-resolver/LICENSE19
-rw-r--r--library/symfony/options-resolver/Options.php22
-rw-r--r--library/symfony/options-resolver/OptionsResolver.php1218
-rw-r--r--library/symfony/options-resolver/OptionsResolverInterface.php212
-rw-r--r--library/symfony/options-resolver/README.md20
-rw-r--r--library/symfony/options-resolver/Tests/LegacyOptionsResolverTest.php733
-rw-r--r--library/symfony/options-resolver/Tests/LegacyOptionsTest.php337
-rw-r--r--library/symfony/options-resolver/Tests/OptionsResolver2Dot6Test.php1550
-rw-r--r--library/symfony/options-resolver/phpunit.xml.dist29
18 files changed, 4367 insertions, 0 deletions
diff --git a/library/symfony/options-resolver/CHANGELOG.md b/library/symfony/options-resolver/CHANGELOG.md
new file mode 100644
index 000000000..5f6d15b2c
--- /dev/null
+++ b/library/symfony/options-resolver/CHANGELOG.md
@@ -0,0 +1,46 @@
+CHANGELOG
+=========
+
+2.6.0
+-----
+
+ * deprecated OptionsResolverInterface
+ * [BC BREAK] removed "array" type hint from OptionsResolverInterface methods
+ setRequired(), setAllowedValues(), addAllowedValues(), setAllowedTypes() and
+ addAllowedTypes()
+ * added OptionsResolver::setDefault()
+ * added OptionsResolver::hasDefault()
+ * added OptionsResolver::setNormalizer()
+ * added OptionsResolver::isRequired()
+ * added OptionsResolver::getRequiredOptions()
+ * added OptionsResolver::isMissing()
+ * added OptionsResolver::getMissingOptions()
+ * added OptionsResolver::setDefined()
+ * added OptionsResolver::isDefined()
+ * added OptionsResolver::getDefinedOptions()
+ * added OptionsResolver::remove()
+ * added OptionsResolver::clear()
+ * deprecated OptionsResolver::replaceDefaults()
+ * deprecated OptionsResolver::setOptional() in favor of setDefined()
+ * deprecated OptionsResolver::isKnown() in favor of isDefined()
+ * [BC BREAK] OptionsResolver::isRequired() returns true now if a required
+ option has a default value set
+ * [BC BREAK] merged Options into OptionsResolver and turned Options into an
+ interface
+ * deprecated Options::overload() (now in OptionsResolver)
+ * deprecated Options::set() (now in OptionsResolver)
+ * deprecated Options::get() (now in OptionsResolver)
+ * deprecated Options::has() (now in OptionsResolver)
+ * deprecated Options::replace() (now in OptionsResolver)
+ * [BC BREAK] Options::get() (now in OptionsResolver) can only be used within
+ lazy option/normalizer closures now
+ * [BC BREAK] removed Traversable interface from Options since using within
+ lazy option/normalizer closures resulted in exceptions
+ * [BC BREAK] removed Options::all() since using within lazy option/normalizer
+ closures resulted in exceptions
+ * [BC BREAK] OptionDefinitionException now extends LogicException instead of
+ RuntimeException
+ * [BC BREAK] normalizers are not executed anymore for unset options
+ * normalizers are executed after validating the options now
+ * [BC BREAK] an UndefinedOptionsException is now thrown instead of an
+ InvalidOptionsException when non-existing options are passed
diff --git a/library/symfony/options-resolver/Exception/AccessException.php b/library/symfony/options-resolver/Exception/AccessException.php
new file mode 100644
index 000000000..c12b68064
--- /dev/null
+++ b/library/symfony/options-resolver/Exception/AccessException.php
@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+/**
+ * Thrown when trying to read an option outside of or write it inside of
+ * {@link \Symfony\Component\OptionsResolver\Options::resolve()}.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class AccessException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/library/symfony/options-resolver/Exception/ExceptionInterface.php b/library/symfony/options-resolver/Exception/ExceptionInterface.php
new file mode 100644
index 000000000..b62bb51d4
--- /dev/null
+++ b/library/symfony/options-resolver/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+/**
+ * Marker interface for all exceptions thrown by the OptionsResolver component.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface ExceptionInterface
+{
+}
diff --git a/library/symfony/options-resolver/Exception/InvalidArgumentException.php b/library/symfony/options-resolver/Exception/InvalidArgumentException.php
new file mode 100644
index 000000000..6d421d68b
--- /dev/null
+++ b/library/symfony/options-resolver/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+/**
+ * Thrown when an argument is invalid.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/library/symfony/options-resolver/Exception/InvalidOptionsException.php b/library/symfony/options-resolver/Exception/InvalidOptionsException.php
new file mode 100644
index 000000000..6fd4f125f
--- /dev/null
+++ b/library/symfony/options-resolver/Exception/InvalidOptionsException.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+/**
+ * Thrown when the value of an option does not match its validation rules.
+ *
+ * You should make sure a valid value is passed to the option.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class InvalidOptionsException extends InvalidArgumentException
+{
+}
diff --git a/library/symfony/options-resolver/Exception/MissingOptionsException.php b/library/symfony/options-resolver/Exception/MissingOptionsException.php
new file mode 100644
index 000000000..faa487f16
--- /dev/null
+++ b/library/symfony/options-resolver/Exception/MissingOptionsException.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+/**
+ * Exception thrown when a required option is missing.
+ *
+ * Add the option to the passed options array.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class MissingOptionsException extends InvalidArgumentException
+{
+}
diff --git a/library/symfony/options-resolver/Exception/NoSuchOptionException.php b/library/symfony/options-resolver/Exception/NoSuchOptionException.php
new file mode 100644
index 000000000..4c3280f4c
--- /dev/null
+++ b/library/symfony/options-resolver/Exception/NoSuchOptionException.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+/**
+ * Thrown when trying to read an option that has no value set.
+ *
+ * When accessing optional options from within a lazy option or normalizer you should first
+ * check whether the optional option is set. You can do this with `isset($options['optional'])`.
+ * In contrast to the {@link UndefinedOptionsException}, this is a runtime exception that can
+ * occur when evaluating lazy options.
+ *
+ * @author Tobias Schultze <http://tobion.de>
+ */
+class NoSuchOptionException extends \OutOfBoundsException implements ExceptionInterface
+{
+}
diff --git a/library/symfony/options-resolver/Exception/OptionDefinitionException.php b/library/symfony/options-resolver/Exception/OptionDefinitionException.php
new file mode 100644
index 000000000..e8e339d44
--- /dev/null
+++ b/library/symfony/options-resolver/Exception/OptionDefinitionException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+/**
+ * Thrown when two lazy options have a cyclic dependency.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class OptionDefinitionException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/library/symfony/options-resolver/Exception/UndefinedOptionsException.php b/library/symfony/options-resolver/Exception/UndefinedOptionsException.php
new file mode 100644
index 000000000..6ca3fce47
--- /dev/null
+++ b/library/symfony/options-resolver/Exception/UndefinedOptionsException.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Exception;
+
+/**
+ * Exception thrown when an undefined option is passed.
+ *
+ * You should remove the options in question from your code or define them
+ * beforehand.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class UndefinedOptionsException extends InvalidArgumentException
+{
+}
diff --git a/library/symfony/options-resolver/LICENSE b/library/symfony/options-resolver/LICENSE
new file mode 100644
index 000000000..43028bc60
--- /dev/null
+++ b/library/symfony/options-resolver/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2015 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/library/symfony/options-resolver/Options.php b/library/symfony/options-resolver/Options.php
new file mode 100644
index 000000000..d444ec423
--- /dev/null
+++ b/library/symfony/options-resolver/Options.php
@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver;
+
+/**
+ * Contains resolved option values.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Tobias Schultze <http://tobion.de>
+ */
+interface Options extends \ArrayAccess, \Countable
+{
+}
diff --git a/library/symfony/options-resolver/OptionsResolver.php b/library/symfony/options-resolver/OptionsResolver.php
new file mode 100644
index 000000000..e0578af71
--- /dev/null
+++ b/library/symfony/options-resolver/OptionsResolver.php
@@ -0,0 +1,1218 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver;
+
+use Symfony\Component\OptionsResolver\Exception\AccessException;
+use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
+use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
+use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException;
+use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException;
+use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
+
+/**
+ * Validates options and merges them with default values.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Tobias Schultze <http://tobion.de>
+ */
+class OptionsResolver implements Options, OptionsResolverInterface
+{
+ /**
+ * The fully qualified name of the {@link Options} interface.
+ *
+ * @internal
+ */
+ const OPTIONS_INTERFACE = 'Symfony\\Component\\OptionsResolver\\Options';
+
+ /**
+ * The names of all defined options.
+ *
+ * @var array
+ */
+ private $defined = array();
+
+ /**
+ * The default option values.
+ *
+ * @var array
+ */
+ private $defaults = array();
+
+ /**
+ * The names of required options.
+ *
+ * @var array
+ */
+ private $required = array();
+
+ /**
+ * The resolved option values.
+ *
+ * @var array
+ */
+ private $resolved = array();
+
+ /**
+ * A list of normalizer closures.
+ *
+ * @var \Closure[]
+ */
+ private $normalizers = array();
+
+ /**
+ * A list of accepted values for each option.
+ *
+ * @var array
+ */
+ private $allowedValues = array();
+
+ /**
+ * A list of accepted types for each option.
+ *
+ * @var array
+ */
+ private $allowedTypes = array();
+
+ /**
+ * A list of closures for evaluating lazy options.
+ *
+ * @var array
+ */
+ private $lazy = array();
+
+ /**
+ * A list of lazy options whose closure is currently being called.
+ *
+ * This list helps detecting circular dependencies between lazy options.
+ *
+ * @var array
+ */
+ private $calling = array();
+
+ /**
+ * Whether the instance is locked for reading.
+ *
+ * Once locked, the options cannot be changed anymore. This is
+ * necessary in order to avoid inconsistencies during the resolving
+ * process. If any option is changed after being read, all evaluated
+ * lazy options that depend on this option would become invalid.
+ *
+ * @var bool
+ */
+ private $locked = false;
+
+ private static $typeAliases = array(
+ 'boolean' => 'bool',
+ 'integer' => 'int',
+ 'double' => 'float',
+ );
+
+ /**
+ * Sets the default value of a given option.
+ *
+ * If the default value should be set based on other options, you can pass
+ * a closure with the following signature:
+ *
+ * function (Options $options) {
+ * // ...
+ * }
+ *
+ * The closure will be evaluated when {@link resolve()} is called. The
+ * closure has access to the resolved values of other options through the
+ * passed {@link Options} instance:
+ *
+ * function (Options $options) {
+ * if (isset($options['port'])) {
+ * // ...
+ * }
+ * }
+ *
+ * If you want to access the previously set default value, add a second
+ * argument to the closure's signature:
+ *
+ * $options->setDefault('name', 'Default Name');
+ *
+ * $options->setDefault('name', function (Options $options, $previousValue) {
+ * // 'Default Name' === $previousValue
+ * });
+ *
+ * This is mostly useful if the configuration of the {@link Options} object
+ * is spread across different locations of your code, such as base and
+ * sub-classes.
+ *
+ * @param string $option The name of the option
+ * @param mixed $value The default value of the option
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function setDefault($option, $value)
+ {
+ // Setting is not possible once resolving starts, because then lazy
+ // options could manipulate the state of the object, leading to
+ // inconsistent results.
+ if ($this->locked) {
+ throw new AccessException('Default values cannot be set from a lazy option or normalizer.');
+ }
+
+ // If an option is a closure that should be evaluated lazily, store it
+ // in the "lazy" property.
+ if ($value instanceof \Closure) {
+ $reflClosure = new \ReflectionFunction($value);
+ $params = $reflClosure->getParameters();
+
+ if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && self::OPTIONS_INTERFACE === $class->name) {
+ // Initialize the option if no previous value exists
+ if (!isset($this->defaults[$option])) {
+ $this->defaults[$option] = null;
+ }
+
+ // Ignore previous lazy options if the closure has no second parameter
+ if (!isset($this->lazy[$option]) || !isset($params[1])) {
+ $this->lazy[$option] = array();
+ }
+
+ // Store closure for later evaluation
+ $this->lazy[$option][] = $value;
+ $this->defined[$option] = true;
+
+ // Make sure the option is processed
+ unset($this->resolved[$option]);
+
+ return $this;
+ }
+ }
+
+ // This option is not lazy anymore
+ unset($this->lazy[$option]);
+
+ // Yet undefined options can be marked as resolved, because we only need
+ // to resolve options with lazy closures, normalizers or validation
+ // rules, none of which can exist for undefined options
+ // If the option was resolved before, update the resolved value
+ if (!isset($this->defined[$option]) || array_key_exists($option, $this->resolved)) {
+ $this->resolved[$option] = $value;
+ }
+
+ $this->defaults[$option] = $value;
+ $this->defined[$option] = true;
+
+ return $this;
+ }
+
+ /**
+ * Sets a list of default values.
+ *
+ * @param array $defaults The default values to set
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function setDefaults(array $defaults)
+ {
+ foreach ($defaults as $option => $value) {
+ $this->setDefault($option, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns whether a default value is set for an option.
+ *
+ * Returns true if {@link setDefault()} was called for this option.
+ * An option is also considered set if it was set to null.
+ *
+ * @param string $option The option name
+ *
+ * @return bool Whether a default value is set
+ */
+ public function hasDefault($option)
+ {
+ return array_key_exists($option, $this->defaults);
+ }
+
+ /**
+ * Marks one or more options as required.
+ *
+ * @param string|string[] $optionNames One or more option names
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function setRequired($optionNames)
+ {
+ if ($this->locked) {
+ throw new AccessException('Options cannot be made required from a lazy option or normalizer.');
+ }
+
+ foreach ((array) $optionNames as $option) {
+ $this->defined[$option] = true;
+ $this->required[$option] = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns whether an option is required.
+ *
+ * An option is required if it was passed to {@link setRequired()}.
+ *
+ * @param string $option The name of the option
+ *
+ * @return bool Whether the option is required
+ */
+ public function isRequired($option)
+ {
+ return isset($this->required[$option]);
+ }
+
+ /**
+ * Returns the names of all required options.
+ *
+ * @return string[] The names of the required options
+ *
+ * @see isRequired()
+ */
+ public function getRequiredOptions()
+ {
+ return array_keys($this->required);
+ }
+
+ /**
+ * Returns whether an option is missing a default value.
+ *
+ * An option is missing if it was passed to {@link setRequired()}, but not
+ * to {@link setDefault()}. This option must be passed explicitly to
+ * {@link resolve()}, otherwise an exception will be thrown.
+ *
+ * @param string $option The name of the option
+ *
+ * @return bool Whether the option is missing
+ */
+ public function isMissing($option)
+ {
+ return isset($this->required[$option]) && !array_key_exists($option, $this->defaults);
+ }
+
+ /**
+ * Returns the names of all options missing a default value.
+ *
+ * @return string[] The names of the missing options
+ *
+ * @see isMissing()
+ */
+ public function getMissingOptions()
+ {
+ return array_keys(array_diff_key($this->required, $this->defaults));
+ }
+
+ /**
+ * Defines a valid option name.
+ *
+ * Defines an option name without setting a default value. The option will
+ * be accepted when passed to {@link resolve()}. When not passed, the
+ * option will not be included in the resolved options.
+ *
+ * @param string|string[] $optionNames One or more option names
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function setDefined($optionNames)
+ {
+ if ($this->locked) {
+ throw new AccessException('Options cannot be defined from a lazy option or normalizer.');
+ }
+
+ foreach ((array) $optionNames as $option) {
+ $this->defined[$option] = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns whether an option is defined.
+ *
+ * Returns true for any option passed to {@link setDefault()},
+ * {@link setRequired()} or {@link setDefined()}.
+ *
+ * @param string $option The option name
+ *
+ * @return bool Whether the option is defined
+ */
+ public function isDefined($option)
+ {
+ return isset($this->defined[$option]);
+ }
+
+ /**
+ * Returns the names of all defined options.
+ *
+ * @return string[] The names of the defined options
+ *
+ * @see isDefined()
+ */
+ public function getDefinedOptions()
+ {
+ return array_keys($this->defined);
+ }
+
+ /**
+ * Sets the normalizer for an option.
+ *
+ * The normalizer should be a closure with the following signature:
+ *
+ * ```php
+ * function (Options $options, $value) {
+ * // ...
+ * }
+ * ```
+ *
+ * The closure is invoked when {@link resolve()} is called. The closure
+ * has access to the resolved values of other options through the passed
+ * {@link Options} instance.
+ *
+ * The second parameter passed to the closure is the value of
+ * the option.
+ *
+ * The resolved option value is set to the return value of the closure.
+ *
+ * @param string $option The option name
+ * @param \Closure $normalizer The normalizer
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws UndefinedOptionsException If the option is undefined
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function setNormalizer($option, \Closure $normalizer)
+ {
+ if ($this->locked) {
+ throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.');
+ }
+
+ if (!isset($this->defined[$option])) {
+ throw new UndefinedOptionsException(sprintf(
+ 'The option "%s" does not exist. Defined options are: "%s".',
+ $option,
+ implode('", "', array_keys($this->defined))
+ ));
+ }
+
+ $this->normalizers[$option] = $normalizer;
+
+ // Make sure the option is processed
+ unset($this->resolved[$option]);
+
+ return $this;
+ }
+
+ /**
+ * Sets the normalizers for an array of options.
+ *
+ * @param array $normalizers An array of closures
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws UndefinedOptionsException If the option is undefined
+ * @throws AccessException If called from a lazy option or normalizer
+ *
+ * @see setNormalizer()
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function setNormalizers(array $normalizers)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use setNormalizer() instead.', E_USER_DEPRECATED);
+
+ foreach ($normalizers as $option => $normalizer) {
+ $this->setNormalizer($option, $normalizer);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets allowed values for an option.
+ *
+ * Instead of passing values, you may also pass a closures with the
+ * following signature:
+ *
+ * function ($value) {
+ * // return true or false
+ * }
+ *
+ * The closure receives the value as argument and should return true to
+ * accept the value and false to reject the value.
+ *
+ * @param string $option The option name
+ * @param mixed $allowedValues One or more acceptable values/closures
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws UndefinedOptionsException If the option is undefined
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function setAllowedValues($option, $allowedValues = null)
+ {
+ if ($this->locked) {
+ throw new AccessException('Allowed values cannot be set from a lazy option or normalizer.');
+ }
+
+ // BC
+ if (is_array($option) && null === $allowedValues) {
+ @trigger_error('Calling the '.__METHOD__.' method with an array of options is deprecated since version 2.6 and will be removed in 3.0. Use the new signature with a single option instead.', E_USER_DEPRECATED);
+
+ foreach ($option as $optionName => $optionValues) {
+ $this->setAllowedValues($optionName, $optionValues);
+ }
+
+ return $this;
+ }
+
+ if (!isset($this->defined[$option])) {
+ throw new UndefinedOptionsException(sprintf(
+ 'The option "%s" does not exist. Defined options are: "%s".',
+ $option,
+ implode('", "', array_keys($this->defined))
+ ));
+ }
+
+ $this->allowedValues[$option] = is_array($allowedValues) ? $allowedValues : array($allowedValues);
+
+ // Make sure the option is processed
+ unset($this->resolved[$option]);
+
+ return $this;
+ }
+
+ /**
+ * Adds allowed values for an option.
+ *
+ * The values are merged with the allowed values defined previously.
+ *
+ * Instead of passing values, you may also pass a closures with the
+ * following signature:
+ *
+ * function ($value) {
+ * // return true or false
+ * }
+ *
+ * The closure receives the value as argument and should return true to
+ * accept the value and false to reject the value.
+ *
+ * @param string $option The option name
+ * @param mixed $allowedValues One or more acceptable values/closures
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws UndefinedOptionsException If the option is undefined
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function addAllowedValues($option, $allowedValues = null)
+ {
+ if ($this->locked) {
+ throw new AccessException('Allowed values cannot be added from a lazy option or normalizer.');
+ }
+
+ // BC
+ if (is_array($option) && null === $allowedValues) {
+ @trigger_error('Calling the '.__METHOD__.' method with an array of options is deprecated since version 2.6 and will be removed in 3.0. Use the new signature with a single option instead.', E_USER_DEPRECATED);
+
+ foreach ($option as $optionName => $optionValues) {
+ $this->addAllowedValues($optionName, $optionValues);
+ }
+
+ return $this;
+ }
+
+ if (!isset($this->defined[$option])) {
+ throw new UndefinedOptionsException(sprintf(
+ 'The option "%s" does not exist. Defined options are: "%s".',
+ $option,
+ implode('", "', array_keys($this->defined))
+ ));
+ }
+
+ if (!is_array($allowedValues)) {
+ $allowedValues = array($allowedValues);
+ }
+
+ if (!isset($this->allowedValues[$option])) {
+ $this->allowedValues[$option] = $allowedValues;
+ } else {
+ $this->allowedValues[$option] = array_merge($this->allowedValues[$option], $allowedValues);
+ }
+
+ // Make sure the option is processed
+ unset($this->resolved[$option]);
+
+ return $this;
+ }
+
+ /**
+ * Sets allowed types for an option.
+ *
+ * Any type for which a corresponding is_<type>() function exists is
+ * acceptable. Additionally, fully-qualified class or interface names may
+ * be passed.
+ *
+ * @param string $option The option name
+ * @param string|string[] $allowedTypes One or more accepted types
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws UndefinedOptionsException If the option is undefined
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function setAllowedTypes($option, $allowedTypes = null)
+ {
+ if ($this->locked) {
+ throw new AccessException('Allowed types cannot be set from a lazy option or normalizer.');
+ }
+
+ // BC
+ if (is_array($option) && null === $allowedTypes) {
+ @trigger_error('Calling the '.__METHOD__.' method with an array of options is deprecated since version 2.6 and will be removed in 3.0. Use the new signature with a single option instead.', E_USER_DEPRECATED);
+
+ foreach ($option as $optionName => $optionTypes) {
+ $this->setAllowedTypes($optionName, $optionTypes);
+ }
+
+ return $this;
+ }
+
+ if (!isset($this->defined[$option])) {
+ throw new UndefinedOptionsException(sprintf(
+ 'The option "%s" does not exist. Defined options are: "%s".',
+ $option,
+ implode('", "', array_keys($this->defined))
+ ));
+ }
+
+ $this->allowedTypes[$option] = (array) $allowedTypes;
+
+ // Make sure the option is processed
+ unset($this->resolved[$option]);
+
+ return $this;
+ }
+
+ /**
+ * Adds allowed types for an option.
+ *
+ * The types are merged with the allowed types defined previously.
+ *
+ * Any type for which a corresponding is_<type>() function exists is
+ * acceptable. Additionally, fully-qualified class or interface names may
+ * be passed.
+ *
+ * @param string $option The option name
+ * @param string|string[] $allowedTypes One or more accepted types
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws UndefinedOptionsException If the option is undefined
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function addAllowedTypes($option, $allowedTypes = null)
+ {
+ if ($this->locked) {
+ throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.');
+ }
+
+ // BC
+ if (is_array($option) && null === $allowedTypes) {
+ @trigger_error('Calling the '.__METHOD__.' method with an array of options is deprecated since version 2.6 and will be removed in 3.0. Use the new signature with a single option instead.', E_USER_DEPRECATED);
+
+ foreach ($option as $optionName => $optionTypes) {
+ $this->addAllowedTypes($optionName, $optionTypes);
+ }
+
+ return $this;
+ }
+
+ if (!isset($this->defined[$option])) {
+ throw new UndefinedOptionsException(sprintf(
+ 'The option "%s" does not exist. Defined options are: "%s".',
+ $option,
+ implode('", "', array_keys($this->defined))
+ ));
+ }
+
+ if (!isset($this->allowedTypes[$option])) {
+ $this->allowedTypes[$option] = (array) $allowedTypes;
+ } else {
+ $this->allowedTypes[$option] = array_merge($this->allowedTypes[$option], (array) $allowedTypes);
+ }
+
+ // Make sure the option is processed
+ unset($this->resolved[$option]);
+
+ return $this;
+ }
+
+ /**
+ * Removes the option with the given name.
+ *
+ * Undefined options are ignored.
+ *
+ * @param string|string[] $optionNames One or more option names
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function remove($optionNames)
+ {
+ if ($this->locked) {
+ throw new AccessException('Options cannot be removed from a lazy option or normalizer.');
+ }
+
+ foreach ((array) $optionNames as $option) {
+ unset($this->defined[$option], $this->defaults[$option], $this->required[$option], $this->resolved[$option]);
+ unset($this->lazy[$option], $this->normalizers[$option], $this->allowedTypes[$option], $this->allowedValues[$option]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Removes all options.
+ *
+ * @return OptionsResolver This instance
+ *
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function clear()
+ {
+ if ($this->locked) {
+ throw new AccessException('Options cannot be cleared from a lazy option or normalizer.');
+ }
+
+ $this->defined = array();
+ $this->defaults = array();
+ $this->required = array();
+ $this->resolved = array();
+ $this->lazy = array();
+ $this->normalizers = array();
+ $this->allowedTypes = array();
+ $this->allowedValues = array();
+
+ return $this;
+ }
+
+ /**
+ * Merges options with the default values stored in the container and
+ * validates them.
+ *
+ * Exceptions are thrown if:
+ *
+ * - Undefined options are passed;
+ * - Required options are missing;
+ * - Options have invalid types;
+ * - Options have invalid values.
+ *
+ * @param array $options A map of option names to values
+ *
+ * @return array The merged and validated options
+ *
+ * @throws UndefinedOptionsException If an option name is undefined
+ * @throws InvalidOptionsException If an option doesn't fulfill the
+ * specified validation rules
+ * @throws MissingOptionsException If a required option is missing
+ * @throws OptionDefinitionException If there is a cyclic dependency between
+ * lazy options and/or normalizers
+ * @throws NoSuchOptionException If a lazy option reads an unavailable option
+ * @throws AccessException If called from a lazy option or normalizer
+ */
+ public function resolve(array $options = array())
+ {
+ if ($this->locked) {
+ throw new AccessException('Options cannot be resolved from a lazy option or normalizer.');
+ }
+
+ // Allow this method to be called multiple times
+ $clone = clone $this;
+
+ // Make sure that no unknown options are passed
+ $diff = array_diff_key($options, $clone->defined);
+
+ if (count($diff) > 0) {
+ ksort($clone->defined);
+ ksort($diff);
+
+ throw new UndefinedOptionsException(sprintf(
+ (count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".',
+ implode('", "', array_keys($diff)),
+ implode('", "', array_keys($clone->defined))
+ ));
+ }
+
+ // Override options set by the user
+ foreach ($options as $option => $value) {
+ $clone->defaults[$option] = $value;
+ unset($clone->resolved[$option], $clone->lazy[$option]);
+ }
+
+ // Check whether any required option is missing
+ $diff = array_diff_key($clone->required, $clone->defaults);
+
+ if (count($diff) > 0) {
+ ksort($diff);
+
+ throw new MissingOptionsException(sprintf(
+ count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.',
+ implode('", "', array_keys($diff))
+ ));
+ }
+
+ // Lock the container
+ $clone->locked = true;
+
+ // Now process the individual options. Use offsetGet(), which resolves
+ // the option itself and any options that the option depends on
+ foreach ($clone->defaults as $option => $_) {
+ $clone->offsetGet($option);
+ }
+
+ return $clone->resolved;
+ }
+
+ /**
+ * Returns the resolved value of an option.
+ *
+ * @param string $option The option name
+ *
+ * @return mixed The option value
+ *
+ * @throws AccessException If accessing this method outside of
+ * {@link resolve()}
+ * @throws NoSuchOptionException If the option is not set
+ * @throws InvalidOptionsException If the option doesn't fulfill the
+ * specified validation rules
+ * @throws OptionDefinitionException If there is a cyclic dependency between
+ * lazy options and/or normalizers
+ */
+ public function offsetGet($option)
+ {
+ if (!$this->locked) {
+ throw new AccessException('Array access is only supported within closures of lazy options and normalizers.');
+ }
+
+ // Shortcut for resolved options
+ if (array_key_exists($option, $this->resolved)) {
+ return $this->resolved[$option];
+ }
+
+ // Check whether the option is set at all
+ if (!array_key_exists($option, $this->defaults)) {
+ if (!isset($this->defined[$option])) {
+ throw new NoSuchOptionException(sprintf(
+ 'The option "%s" does not exist. Defined options are: "%s".',
+ $option,
+ implode('", "', array_keys($this->defined))
+ ));
+ }
+
+ throw new NoSuchOptionException(sprintf(
+ 'The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.',
+ $option
+ ));
+ }
+
+ $value = $this->defaults[$option];
+
+ // Resolve the option if the default value is lazily evaluated
+ if (isset($this->lazy[$option])) {
+ // If the closure is already being called, we have a cyclic
+ // dependency
+ if (isset($this->calling[$option])) {
+ throw new OptionDefinitionException(sprintf(
+ 'The options "%s" have a cyclic dependency.',
+ implode('", "', array_keys($this->calling))
+ ));
+ }
+
+ // The following section must be protected from cyclic
+ // calls. Set $calling for the current $option to detect a cyclic
+ // dependency
+ // BEGIN
+ $this->calling[$option] = true;
+ try {
+ foreach ($this->lazy[$option] as $closure) {
+ $value = $closure($this, $value);
+ }
+ } catch (\Exception $e) {
+ unset($this->calling[$option]);
+ throw $e;
+ }
+ unset($this->calling[$option]);
+ // END
+ }
+
+ // Validate the type of the resolved option
+ if (isset($this->allowedTypes[$option])) {
+ $valid = false;
+
+ foreach ($this->allowedTypes[$option] as $type) {
+ $type = isset(self::$typeAliases[$type]) ? self::$typeAliases[$type] : $type;
+
+ if (function_exists($isFunction = 'is_'.$type)) {
+ if ($isFunction($value)) {
+ $valid = true;
+ break;
+ }
+
+ continue;
+ }
+
+ if ($value instanceof $type) {
+ $valid = true;
+ break;
+ }
+ }
+
+ if (!$valid) {
+ throw new InvalidOptionsException(sprintf(
+ 'The option "%s" with value %s is expected to be of type '.
+ '"%s", but is of type "%s".',
+ $option,
+ $this->formatValue($value),
+ implode('" or "', $this->allowedTypes[$option]),
+ $this->formatTypeOf($value)
+ ));
+ }
+ }
+
+ // Validate the value of the resolved option
+ if (isset($this->allowedValues[$option])) {
+ $success = false;
+ $printableAllowedValues = array();
+
+ foreach ($this->allowedValues[$option] as $allowedValue) {
+ if ($allowedValue instanceof \Closure) {
+ if ($allowedValue($value)) {
+ $success = true;
+ break;
+ }
+
+ // Don't include closures in the exception message
+ continue;
+ } elseif ($value === $allowedValue) {
+ $success = true;
+ break;
+ }
+
+ $printableAllowedValues[] = $allowedValue;
+ }
+
+ if (!$success) {
+ $message = sprintf(
+ 'The option "%s" with value %s is invalid.',
+ $option,
+ $this->formatValue($value)
+ );
+
+ if (count($printableAllowedValues) > 0) {
+ $message .= sprintf(
+ ' Accepted values are: %s.',
+ $this->formatValues($printableAllowedValues)
+ );
+ }
+
+ throw new InvalidOptionsException($message);
+ }
+ }
+
+ // Normalize the validated option
+ if (isset($this->normalizers[$option])) {
+ // If the closure is already being called, we have a cyclic
+ // dependency
+ if (isset($this->calling[$option])) {
+ throw new OptionDefinitionException(sprintf(
+ 'The options "%s" have a cyclic dependency.',
+ implode('", "', array_keys($this->calling))
+ ));
+ }
+
+ $normalizer = $this->normalizers[$option];
+
+ // The following section must be protected from cyclic
+ // calls. Set $calling for the current $option to detect a cyclic
+ // dependency
+ // BEGIN
+ $this->calling[$option] = true;
+ try {
+ $value = $normalizer($this, $value);
+ } catch (\Exception $e) {
+ unset($this->calling[$option]);
+ throw $e;
+ }
+ unset($this->calling[$option]);
+ // END
+ }
+
+ // Mark as resolved
+ $this->resolved[$option] = $value;
+
+ return $value;
+ }
+
+ /**
+ * Returns whether a resolved option with the given name exists.
+ *
+ * @param string $option The option name
+ *
+ * @return bool Whether the option is set
+ *
+ * @throws AccessException If accessing this method outside of {@link resolve()}
+ *
+ * @see \ArrayAccess::offsetExists()
+ */
+ public function offsetExists($option)
+ {
+ if (!$this->locked) {
+ throw new AccessException('Array access is only supported within closures of lazy options and normalizers.');
+ }
+
+ return array_key_exists($option, $this->defaults);
+ }
+
+ /**
+ * Not supported.
+ *
+ * @throws AccessException
+ */
+ public function offsetSet($option, $value)
+ {
+ throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.');
+ }
+
+ /**
+ * Not supported.
+ *
+ * @throws AccessException
+ */
+ public function offsetUnset($option)
+ {
+ throw new AccessException('Removing options via array access is not supported. Use remove() instead.');
+ }
+
+ /**
+ * Returns the number of set options.
+ *
+ * This may be only a subset of the defined options.
+ *
+ * @return int Number of options
+ *
+ * @throws AccessException If accessing this method outside of {@link resolve()}
+ *
+ * @see \Countable::count()
+ */
+ public function count()
+ {
+ if (!$this->locked) {
+ throw new AccessException('Counting is only supported within closures of lazy options and normalizers.');
+ }
+
+ return count($this->defaults);
+ }
+
+ /**
+ * Alias of {@link setDefault()}.
+ *
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function set($option, $value)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setDefaults() method instead.', E_USER_DEPRECATED);
+
+ return $this->setDefault($option, $value);
+ }
+
+ /**
+ * Shortcut for {@link clear()} and {@link setDefaults()}.
+ *
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function replace(array $defaults)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the clear() and setDefaults() methods instead.', E_USER_DEPRECATED);
+
+ $this->clear();
+
+ return $this->setDefaults($defaults);
+ }
+
+ /**
+ * Alias of {@link setDefault()}.
+ *
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function overload($option, $value)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setDefault() method instead.', E_USER_DEPRECATED);
+
+ return $this->setDefault($option, $value);
+ }
+
+ /**
+ * Alias of {@link offsetGet()}.
+ *
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function get($option)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the ArrayAccess syntax instead to get an option value.', E_USER_DEPRECATED);
+
+ return $this->offsetGet($option);
+ }
+
+ /**
+ * Alias of {@link offsetExists()}.
+ *
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function has($option)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the ArrayAccess syntax instead to get an option value.', E_USER_DEPRECATED);
+
+ return $this->offsetExists($option);
+ }
+
+ /**
+ * Shortcut for {@link clear()} and {@link setDefaults()}.
+ *
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function replaceDefaults(array $defaultValues)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the clear() and setDefaults() methods instead.', E_USER_DEPRECATED);
+
+ $this->clear();
+
+ return $this->setDefaults($defaultValues);
+ }
+
+ /**
+ * Alias of {@link setDefined()}.
+ *
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function setOptional(array $optionNames)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setDefined() method instead.', E_USER_DEPRECATED);
+
+ return $this->setDefined($optionNames);
+ }
+
+ /**
+ * Alias of {@link isDefined()}.
+ *
+ * @deprecated since version 2.6, to be removed in 3.0.
+ */
+ public function isKnown($option)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the isDefined() method instead.', E_USER_DEPRECATED);
+
+ return $this->isDefined($option);
+ }
+
+ /**
+ * Returns a string representation of the type of the value.
+ *
+ * This method should be used if you pass the type of a value as
+ * message parameter to a constraint violation. Note that such
+ * parameters should usually not be included in messages aimed at
+ * non-technical people.
+ *
+ * @param mixed $value The value to return the type of
+ *
+ * @return string The type of the value
+ */
+ private function formatTypeOf($value)
+ {
+ return is_object($value) ? get_class($value) : gettype($value);
+ }
+
+ /**
+ * Returns a string representation of the value.
+ *
+ * This method returns the equivalent PHP tokens for most scalar types
+ * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped
+ * in double quotes (").
+ *
+ * @param mixed $value The value to format as string
+ *
+ * @return string The string representation of the passed value
+ */
+ private function formatValue($value)
+ {
+ if (is_object($value)) {
+ return get_class($value);
+ }
+
+ if (is_array($value)) {
+ return 'array';
+ }
+
+ if (is_string($value)) {
+ return '"'.$value.'"';
+ }
+
+ if (is_resource($value)) {
+ return 'resource';
+ }
+
+ if (null === $value) {
+ return 'null';
+ }
+
+ if (false === $value) {
+ return 'false';
+ }
+
+ if (true === $value) {
+ return 'true';
+ }
+
+ return (string) $value;
+ }
+
+ /**
+ * Returns a string representation of a list of values.
+ *
+ * Each of the values is converted to a string using
+ * {@link formatValue()}. The values are then concatenated with commas.
+ *
+ * @param array $values A list of values
+ *
+ * @return string The string representation of the value list
+ *
+ * @see formatValue()
+ */
+ private function formatValues(array $values)
+ {
+ foreach ($values as $key => $value) {
+ $values[$key] = $this->formatValue($value);
+ }
+
+ return implode(', ', $values);
+ }
+}
diff --git a/library/symfony/options-resolver/OptionsResolverInterface.php b/library/symfony/options-resolver/OptionsResolverInterface.php
new file mode 100644
index 000000000..aebc8df22
--- /dev/null
+++ b/library/symfony/options-resolver/OptionsResolverInterface.php
@@ -0,0 +1,212 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver;
+
+use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
+use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
+use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @deprecated since version 2.6, to be removed in 3.0. Use {@link OptionsResolver} instead.
+ */
+interface OptionsResolverInterface
+{
+ /**
+ * Sets default option values.
+ *
+ * The options can either be values of any types or closures that
+ * evaluate the option value lazily. These closures must have one
+ * of the following signatures:
+ *
+ * <code>
+ * function (Options $options)
+ * function (Options $options, $value)
+ * </code>
+ *
+ * The second parameter passed to the closure is the previously
+ * set default value, in case you are overwriting an existing
+ * default value.
+ *
+ * The closures should return the lazily created option value.
+ *
+ * @param array $defaultValues A list of option names as keys and default
+ * values or closures as values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ */
+ public function setDefaults(array $defaultValues);
+
+ /**
+ * Replaces default option values.
+ *
+ * Old defaults are erased, which means that closures passed here cannot
+ * 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 $defaultValues A list of option names as keys and default
+ * values or closures as values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ */
+ public function replaceDefaults(array $defaultValues);
+
+ /**
+ * Sets optional options.
+ *
+ * This method declares valid option names without setting default values for them.
+ * If these options are not passed to {@link resolve()} and no default has been set
+ * for them, 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 because otherwise
+ * {@link resolve()} would trigger an exception for unknown options.
+ *
+ * @param array $optionNames A list of option names.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ */
+ public function setOptional(array $optionNames);
+
+ /**
+ * Sets required options.
+ *
+ * If these options are not passed to {@link resolve()} and no default has been set for
+ * them, an exception will be thrown.
+ *
+ * @param array $optionNames A list of option names.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ */
+ public function setRequired($optionNames);
+
+ /**
+ * 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.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws InvalidOptionsException If an option has not been defined
+ * (see {@link isKnown()}) for which
+ * an allowed value is set.
+ */
+ public function setAllowedValues($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.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws InvalidOptionsException If an option has not been defined
+ * (see {@link isKnown()}) for which
+ * an allowed value is set.
+ */
+ public function addAllowedValues($allowedValues);
+
+ /**
+ * Sets allowed types for a list of options.
+ *
+ * @param array $allowedTypes A list of option names as keys and type
+ * names passed as string or array as values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws InvalidOptionsException If an option has not been defined for
+ * which an allowed type is set.
+ */
+ public function setAllowedTypes($allowedTypes);
+
+ /**
+ * Adds allowed types for a list of options.
+ *
+ * The types are merged with the allowed types defined previously.
+ *
+ * @param array $allowedTypes A list of option names as keys and type
+ * names passed as string or array as values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws InvalidOptionsException If an option has not been defined for
+ * which an allowed type is set.
+ */
+ public function addAllowedTypes($allowedTypes);
+
+ /**
+ * Sets normalizers that are applied on resolved options.
+ *
+ * The normalizers should be closures with the following signature:
+ *
+ * <code>
+ * function (Options $options, $value)
+ * </code>
+ *
+ * The second parameter passed to the closure is the value of
+ * the option.
+ *
+ * The closure should return the normalized value.
+ *
+ * @param array $normalizers An array of closures.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ */
+ public function setNormalizers(array $normalizers);
+
+ /**
+ * Returns whether an option is known.
+ *
+ * An option is known if it has been passed to either {@link setDefaults()},
+ * {@link setRequired()} or {@link setOptional()} before.
+ *
+ * @param string $option The name of the option.
+ *
+ * @return bool Whether the option is known.
+ */
+ public function isKnown($option);
+
+ /**
+ * Returns whether an option is required.
+ *
+ * An option is required if it has been passed to {@link setRequired()},
+ * but not to {@link setDefaults()}. That is, the option has been declared
+ * as required and no default value has been set.
+ *
+ * @param string $option The name of the option.
+ *
+ * @return bool Whether the option is required.
+ */
+ public function isRequired($option);
+
+ /**
+ * 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 resolve(array $options = array());
+}
diff --git a/library/symfony/options-resolver/README.md b/library/symfony/options-resolver/README.md
new file mode 100644
index 000000000..cd7a7405d
--- /dev/null
+++ b/library/symfony/options-resolver/README.md
@@ -0,0 +1,20 @@
+OptionsResolver Component
+=========================
+
+This component processes and validates option arrays.
+
+Documentation
+-------------
+
+The documentation for the component can be found [online] [1].
+
+Resources
+---------
+
+You can run the unit tests with the following command:
+
+ $ cd path/to/Symfony/Component/OptionsResolver/
+ $ composer install
+ $ phpunit
+
+[1]: https://symfony.com/doc/current/components/options_resolver.html
diff --git a/library/symfony/options-resolver/Tests/LegacyOptionsResolverTest.php b/library/symfony/options-resolver/Tests/LegacyOptionsResolverTest.php
new file mode 100644
index 000000000..ee89f5279
--- /dev/null
+++ b/library/symfony/options-resolver/Tests/LegacyOptionsResolverTest.php
@@ -0,0 +1,733 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Tests;
+
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\OptionsResolver\Options;
+
+/**
+ * @group legacy
+ */
+class LegacyOptionsResolverTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var OptionsResolver
+ */
+ private $resolver;
+
+ protected function setUp()
+ {
+ $this->resolver = new OptionsResolver();
+ }
+
+ public function testResolve()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ 'two' => '2',
+ ));
+
+ $options = array(
+ 'two' => '20',
+ );
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ 'two' => '20',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveNumericOptions()
+ {
+ $this->resolver->setDefaults(array(
+ '1' => '1',
+ '2' => '2',
+ ));
+
+ $options = array(
+ '2' => '20',
+ );
+
+ $this->assertEquals(array(
+ '1' => '1',
+ '2' => '20',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveLazy()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ 'two' => function (Options $options) {
+ return '20';
+ },
+ ));
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ 'two' => '20',
+ ), $this->resolver->resolve(array()));
+ }
+
+ public function testTypeAliasesForAllowedTypes()
+ {
+ $this->resolver->setDefaults(array(
+ 'force' => false,
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'force' => 'boolean',
+ ));
+
+ $this->resolver->resolve(array(
+ 'force' => true,
+ ));
+ }
+
+ public function testResolveLazyDependencyOnOptional()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ 'two' => function (Options $options) {
+ return $options['one'].'2';
+ },
+ ));
+
+ $options = array(
+ 'one' => '10',
+ );
+
+ $this->assertEquals(array(
+ 'one' => '10',
+ 'two' => '102',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveLazyDependencyOnMissingOptionalWithoutDefault()
+ {
+ $test = $this;
+
+ $this->resolver->setOptional(array(
+ 'one',
+ ));
+
+ $this->resolver->setDefaults(array(
+ 'two' => function (Options $options) use ($test) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertFalse(isset($options['one']));
+
+ return '2';
+ },
+ ));
+
+ $options = array();
+
+ $this->assertEquals(array(
+ 'two' => '2',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveLazyDependencyOnOptionalWithoutDefault()
+ {
+ $test = $this;
+
+ $this->resolver->setOptional(array(
+ 'one',
+ ));
+
+ $this->resolver->setDefaults(array(
+ 'two' => function (Options $options) use ($test) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertTrue(isset($options['one']));
+
+ return $options['one'].'2';
+ },
+ ));
+
+ $options = array(
+ 'one' => '10',
+ );
+
+ $this->assertEquals(array(
+ 'one' => '10',
+ 'two' => '102',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveLazyDependencyOnRequired()
+ {
+ $this->resolver->setRequired(array(
+ 'one',
+ ));
+ $this->resolver->setDefaults(array(
+ 'two' => function (Options $options) {
+ return $options['one'].'2';
+ },
+ ));
+
+ $options = array(
+ 'one' => '10',
+ );
+
+ $this->assertEquals(array(
+ 'one' => '10',
+ 'two' => '102',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveLazyReplaceDefaults()
+ {
+ $test = $this;
+
+ $this->resolver->setDefaults(array(
+ 'one' => function (Options $options) use ($test) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->fail('Previous closure should not be executed');
+ },
+ ));
+
+ $this->resolver->replaceDefaults(array(
+ 'one' => function (Options $options, $previousValue) {
+ return '1';
+ },
+ ));
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ ), $this->resolver->resolve(array()));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
+ * @expectedExceptionMessage The option "foo" does not exist. Defined options are: "one", "three", "two".
+ */
+ public function testResolveFailsIfNonExistingOption()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setRequired(array(
+ 'two',
+ ));
+
+ $this->resolver->setOptional(array(
+ 'three',
+ ));
+
+ $this->resolver->resolve(array(
+ 'foo' => 'bar',
+ ));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
+ */
+ public function testResolveFailsIfMissingRequiredOption()
+ {
+ $this->resolver->setRequired(array(
+ 'one',
+ ));
+
+ $this->resolver->setDefaults(array(
+ 'two' => '2',
+ ));
+
+ $this->resolver->resolve(array(
+ 'two' => '20',
+ ));
+ }
+
+ public function testResolveSucceedsIfOptionValueAllowed()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedValues(array(
+ 'one' => array('1', 'one'),
+ ));
+
+ $options = array(
+ 'one' => 'one',
+ );
+
+ $this->assertEquals(array(
+ 'one' => 'one',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionValueAllowed2()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ 'two' => '2',
+ ));
+
+ $this->resolver->setAllowedValues(array(
+ 'one' => '1',
+ 'two' => '2',
+ ));
+ $this->resolver->addAllowedValues(array(
+ 'one' => 'one',
+ 'two' => 'two',
+ ));
+
+ $options = array(
+ 'one' => '1',
+ 'two' => 'two',
+ );
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ 'two' => 'two',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionalWithAllowedValuesNotSet()
+ {
+ $this->resolver->setRequired(array(
+ 'one',
+ ));
+
+ $this->resolver->setOptional(array(
+ 'two',
+ ));
+
+ $this->resolver->setAllowedValues(array(
+ 'one' => array('1', 'one'),
+ 'two' => array('2', 'two'),
+ ));
+
+ $options = array(
+ 'one' => '1',
+ );
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ ), $this->resolver->resolve($options));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfOptionValueNotAllowed()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedValues(array(
+ 'one' => array('1', 'one'),
+ ));
+
+ $this->resolver->resolve(array(
+ 'one' => '2',
+ ));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowed()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ ));
+
+ $options = array(
+ 'one' => 'one',
+ );
+
+ $this->assertEquals(array(
+ 'one' => 'one',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowedPassArray()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => array('string', 'bool'),
+ ));
+
+ $options = array(
+ 'one' => true,
+ );
+
+ $this->assertEquals(array(
+ 'one' => true,
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowedPassObject()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'object',
+ ));
+
+ $object = new \stdClass();
+ $options = array(
+ 'one' => $object,
+ );
+
+ $this->assertEquals(array(
+ 'one' => $object,
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowedPassClass()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => '\stdClass',
+ ));
+
+ $object = new \stdClass();
+ $options = array(
+ 'one' => $object,
+ );
+
+ $this->assertEquals(array(
+ 'one' => $object,
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowedAddTypes()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ 'two' => '2',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ 'two' => 'bool',
+ ));
+ $this->resolver->addAllowedTypes(array(
+ 'one' => 'float',
+ 'two' => 'integer',
+ ));
+
+ $options = array(
+ 'one' => 1.23,
+ 'two' => false,
+ );
+
+ $this->assertEquals(array(
+ 'one' => 1.23,
+ 'two' => false,
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionalWithTypeAndWithoutValue()
+ {
+ $this->resolver->setOptional(array(
+ 'one',
+ 'two',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ 'two' => 'int',
+ ));
+
+ $options = array(
+ 'two' => 1,
+ );
+
+ $this->assertEquals(array(
+ 'two' => 1,
+ ), $this->resolver->resolve($options));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfOptionTypeNotAllowed()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => array('string', 'bool'),
+ ));
+
+ $this->resolver->resolve(array(
+ 'one' => 1.23,
+ ));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfOptionTypeNotAllowedMultipleOptions()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ 'two' => '2',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ 'two' => 'bool',
+ ));
+
+ $this->resolver->resolve(array(
+ 'one' => 'foo',
+ 'two' => 1.23,
+ ));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfOptionTypeNotAllowedAddTypes()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ ));
+ $this->resolver->addAllowedTypes(array(
+ 'one' => 'bool',
+ ));
+
+ $this->resolver->resolve(array(
+ 'one' => 1.23,
+ ));
+ }
+
+ 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));
+ }
+
+ public function testKnownIfDefaultWasSet()
+ {
+ $this->assertFalse($this->resolver->isKnown('foo'));
+
+ $this->resolver->setDefaults(array(
+ 'foo' => 'bar',
+ ));
+
+ $this->assertTrue($this->resolver->isKnown('foo'));
+ }
+
+ public function testKnownIfRequired()
+ {
+ $this->assertFalse($this->resolver->isKnown('foo'));
+
+ $this->resolver->setRequired(array(
+ 'foo',
+ ));
+
+ $this->assertTrue($this->resolver->isKnown('foo'));
+ }
+
+ public function testKnownIfOptional()
+ {
+ $this->assertFalse($this->resolver->isKnown('foo'));
+
+ $this->resolver->setOptional(array(
+ 'foo',
+ ));
+
+ $this->assertTrue($this->resolver->isKnown('foo'));
+ }
+
+ public function testRequiredIfRequired()
+ {
+ $this->assertFalse($this->resolver->isRequired('foo'));
+
+ $this->resolver->setRequired(array(
+ 'foo',
+ ));
+
+ $this->assertTrue($this->resolver->isRequired('foo'));
+ }
+
+ public function testNormalizersTransformFinalOptions()
+ {
+ $this->resolver->setDefaults(array(
+ 'foo' => 'bar',
+ 'bam' => 'baz',
+ ));
+ $this->resolver->setNormalizers(array(
+ 'foo' => function (Options $options, $value) {
+ return $options['bam'].'['.$value.']';
+ },
+ ));
+
+ $expected = array(
+ 'foo' => 'baz[bar]',
+ 'bam' => 'baz',
+ );
+
+ $this->assertEquals($expected, $this->resolver->resolve(array()));
+
+ $expected = array(
+ 'foo' => 'boo[custom]',
+ 'bam' => 'boo',
+ );
+
+ $this->assertEquals($expected, $this->resolver->resolve(array(
+ 'foo' => 'custom',
+ 'bam' => 'boo',
+ )));
+ }
+
+ public function testResolveWithoutOptionSucceedsIfRequiredAndDefaultValue()
+ {
+ $this->resolver->setRequired(array(
+ 'foo',
+ ));
+ $this->resolver->setDefaults(array(
+ 'foo' => 'bar',
+ ));
+
+ $this->assertEquals(array(
+ 'foo' => 'bar',
+ ), $this->resolver->resolve(array()));
+ }
+
+ public function testResolveWithoutOptionSucceedsIfDefaultValueAndRequired()
+ {
+ $this->resolver->setDefaults(array(
+ 'foo' => 'bar',
+ ));
+ $this->resolver->setRequired(array(
+ 'foo',
+ ));
+
+ $this->assertEquals(array(
+ 'foo' => 'bar',
+ ), $this->resolver->resolve(array()));
+ }
+
+ public function testResolveSucceedsIfOptionRequiredAndValueAllowed()
+ {
+ $this->resolver->setRequired(array(
+ 'one', 'two',
+ ));
+ $this->resolver->setAllowedValues(array(
+ 'two' => array('2'),
+ ));
+
+ $options = array(
+ 'one' => '1',
+ 'two' => '2',
+ );
+
+ $this->assertEquals($options, $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfValueAllowedCallbackReturnsTrue()
+ {
+ $this->resolver->setRequired(array(
+ 'test',
+ ));
+ $this->resolver->setAllowedValues(array(
+ 'test' => function ($value) {
+ return true;
+ },
+ ));
+
+ $options = array(
+ 'test' => true,
+ );
+
+ $this->assertEquals($options, $this->resolver->resolve($options));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfValueAllowedCallbackReturnsFalse()
+ {
+ $this->resolver->setRequired(array(
+ 'test',
+ ));
+ $this->resolver->setAllowedValues(array(
+ 'test' => function ($value) {
+ return false;
+ },
+ ));
+
+ $options = array(
+ 'test' => true,
+ );
+
+ $this->assertEquals($options, $this->resolver->resolve($options));
+ }
+
+ public function testClone()
+ {
+ $this->resolver->setDefaults(array('one' => '1'));
+
+ $clone = clone $this->resolver;
+
+ // Changes after cloning don't affect each other
+ $this->resolver->setDefaults(array('two' => '2'));
+ $clone->setDefaults(array('three' => '3'));
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ 'two' => '2',
+ ), $this->resolver->resolve());
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ 'three' => '3',
+ ), $clone->resolve());
+ }
+
+ public function testOverloadReturnsThis()
+ {
+ $this->assertSame($this->resolver, $this->resolver->overload('foo', 'bar'));
+ }
+
+ public function testOverloadCallsSet()
+ {
+ $this->resolver->overload('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+}
diff --git a/library/symfony/options-resolver/Tests/LegacyOptionsTest.php b/library/symfony/options-resolver/Tests/LegacyOptionsTest.php
new file mode 100644
index 000000000..b65a09002
--- /dev/null
+++ b/library/symfony/options-resolver/Tests/LegacyOptionsTest.php
@@ -0,0 +1,337 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Tests;
+
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @group legacy
+ */
+class LegacyOptionsTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var OptionsResolver
+ */
+ private $options;
+
+ protected function setUp()
+ {
+ $this->options = new OptionsResolver();
+ }
+
+ public function testSetLazyOption()
+ {
+ $test = $this;
+
+ $this->options->set('foo', function (Options $options) use ($test) {
+ return 'dynamic';
+ });
+
+ $this->assertEquals(array('foo' => 'dynamic'), $this->options->resolve());
+ }
+
+ public function testOverloadKeepsPreviousValue()
+ {
+ $test = $this;
+
+ // defined by superclass
+ $this->options->set('foo', 'bar');
+
+ // 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(array('foo' => 'dynamic'), $this->options->resolve());
+ }
+
+ public function testPreviousValueIsEvaluatedIfLazy()
+ {
+ $test = $this;
+
+ // defined by superclass
+ $this->options->set('foo', function (Options $options) {
+ return 'bar';
+ });
+
+ // 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(array('foo' => 'dynamic'), $this->options->resolve());
+ }
+
+ public function testPreviousValueIsNotEvaluatedIfNoSecondArgument()
+ {
+ $test = $this;
+
+ // defined by superclass
+ $this->options->set('foo', function (Options $options) use ($test) {
+ $test->fail('Should not be called');
+ });
+
+ // defined by subclass, no $previousValue argument defined!
+ $this->options->overload('foo', function (Options $options) use ($test) {
+ return 'dynamic';
+ });
+
+ $this->assertEquals(array('foo' => 'dynamic'), $this->options->resolve());
+ }
+
+ 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(array('foo' => 'bar', 'bam' => 'dynamic'), $this->options->resolve());
+ }
+
+ 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(array('foo' => 'bar', 'bam' => 'dynamic'), $this->options->resolve());
+ }
+
+ public function testNormalizer()
+ {
+ $this->options->set('foo', 'bar');
+
+ $this->options->setNormalizer('foo', function () {
+ return 'normalized';
+ });
+
+ $this->assertEquals(array('foo' => 'normalized'), $this->options->resolve());
+ }
+
+ public function testNormalizerReceivesUnnormalizedValue()
+ {
+ $this->options->set('foo', 'bar');
+
+ $this->options->setNormalizer('foo', function (Options $options, $value) {
+ return 'normalized['.$value.']';
+ });
+
+ $this->assertEquals(array('foo' => 'normalized[bar]'), $this->options->resolve());
+ }
+
+ public function testNormalizerCanAccessOtherOptions()
+ {
+ $test = $this;
+
+ $this->options->set('foo', 'bar');
+ $this->options->set('bam', 'baz');
+
+ $this->options->setNormalizer('bam', function (Options $options) use ($test) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertEquals('bar', $options->get('foo'));
+
+ return 'normalized';
+ });
+
+ $this->assertEquals(array('foo' => 'bar', 'bam' => 'normalized'), $this->options->resolve());
+ }
+
+ public function testNormalizerCanAccessOtherLazyOptions()
+ {
+ $test = $this;
+
+ $this->options->set('foo', function (Options $options) {
+ return 'bar';
+ });
+ $this->options->set('bam', 'baz');
+
+ $this->options->setNormalizer('bam', function (Options $options) use ($test) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertEquals('bar', $options->get('foo'));
+
+ return 'normalized';
+ });
+
+ $this->assertEquals(array('foo' => 'bar', 'bam' => 'normalized'), $this->options->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
+ */
+ public function testFailForCyclicDependencies()
+ {
+ $this->options->set('foo', function (Options $options) {
+ $options->get('bam');
+ });
+
+ $this->options->set('bam', function (Options $options) {
+ $options->get('foo');
+ });
+
+ $this->options->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
+ */
+ public function testFailForCyclicDependenciesBetweenNormalizers()
+ {
+ $this->options->set('foo', 'bar');
+ $this->options->set('bam', 'baz');
+
+ $this->options->setNormalizer('foo', function (Options $options) {
+ $options->get('bam');
+ });
+
+ $this->options->setNormalizer('bam', function (Options $options) {
+ $options->get('foo');
+ });
+
+ $this->options->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
+ */
+ public function testFailForCyclicDependenciesBetweenNormalizerAndLazyOption()
+ {
+ $this->options->set('foo', function (Options $options) {
+ $options->get('bam');
+ });
+ $this->options->set('bam', 'baz');
+
+ $this->options->setNormalizer('bam', function (Options $options) {
+ $options->get('foo');
+ });
+
+ $this->options->resolve();
+ }
+
+ 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->resolve());
+ }
+
+ public function testClearRemovesAllOptions()
+ {
+ $this->options->set('one', 1);
+ $this->options->set('two', 2);
+
+ $this->options->clear();
+
+ $this->assertEmpty($this->options->resolve());
+ }
+
+ public function testOverloadCannotBeEvaluatedLazilyWithoutExpectedClosureParams()
+ {
+ $this->options->set('foo', 'bar');
+
+ $this->options->overload('foo', function () {
+ return 'test';
+ });
+
+ $resolved = $this->options->resolve();
+ $this->assertTrue(is_callable($resolved['foo']));
+ }
+
+ public function testOverloadCannotBeEvaluatedLazilyWithoutFirstParamTypeHint()
+ {
+ $this->options->set('foo', 'bar');
+
+ $this->options->overload('foo', function ($object) {
+ return 'test';
+ });
+
+ $resolved = $this->options->resolve();
+ $this->assertTrue(is_callable($resolved['foo']));
+ }
+
+ public function testRemoveOptionAndNormalizer()
+ {
+ $this->options->set('foo1', 'bar');
+ $this->options->setNormalizer('foo1', function (Options $options) {
+ return '';
+ });
+ $this->options->set('foo2', 'bar');
+ $this->options->setNormalizer('foo2', function (Options $options) {
+ return '';
+ });
+
+ $this->options->remove('foo2');
+ $this->assertEquals(array('foo1' => ''), $this->options->resolve());
+ }
+
+ public function testReplaceOptionAndNormalizer()
+ {
+ $this->options->set('foo1', 'bar');
+ $this->options->setNormalizer('foo1', function (Options $options) {
+ return '';
+ });
+ $this->options->set('foo2', 'bar');
+ $this->options->setNormalizer('foo2', function (Options $options) {
+ return '';
+ });
+
+ $this->options->replace(array('foo1' => 'new'));
+ $this->assertEquals(array('foo1' => 'new'), $this->options->resolve());
+ }
+
+ public function testClearOptionAndNormalizer()
+ {
+ $this->options->set('foo1', 'bar');
+ $this->options->setNormalizer('foo1', function (Options $options) {
+ return '';
+ });
+ $this->options->set('foo2', 'bar');
+ $this->options->setNormalizer('foo2', function (Options $options) {
+ return '';
+ });
+
+ $this->options->clear();
+ $this->assertEmpty($this->options->resolve());
+ }
+}
diff --git a/library/symfony/options-resolver/Tests/OptionsResolver2Dot6Test.php b/library/symfony/options-resolver/Tests/OptionsResolver2Dot6Test.php
new file mode 100644
index 000000000..9158c5ba0
--- /dev/null
+++ b/library/symfony/options-resolver/Tests/OptionsResolver2Dot6Test.php
@@ -0,0 +1,1550 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\OptionsResolver\Tests;
+
+use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
+use Symfony\Component\OptionsResolver\Options;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+class OptionsResolver2Dot6Test extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var OptionsResolver
+ */
+ private $resolver;
+
+ protected function setUp()
+ {
+ $this->resolver = new OptionsResolver();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // resolve()
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
+ * @expectedExceptionMessage The option "foo" does not exist. Defined options are: "a", "z".
+ */
+ public function testResolveFailsIfNonExistingOption()
+ {
+ $this->resolver->setDefault('z', '1');
+ $this->resolver->setDefault('a', '2');
+
+ $this->resolver->resolve(array('foo' => 'bar'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
+ * @expectedExceptionMessage The options "baz", "foo", "ping" do not exist. Defined options are: "a", "z".
+ */
+ public function testResolveFailsIfMultipleNonExistingOptions()
+ {
+ $this->resolver->setDefault('z', '1');
+ $this->resolver->setDefault('a', '2');
+
+ $this->resolver->resolve(array('ping' => 'pong', 'foo' => 'bar', 'baz' => 'bam'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testResolveFailsFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->resolve(array());
+ });
+
+ $this->resolver->resolve();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // setDefault()/hasDefault()
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testSetDefaultReturnsThis()
+ {
+ $this->assertSame($this->resolver, $this->resolver->setDefault('foo', 'bar'));
+ }
+
+ public function testSetDefault()
+ {
+ $this->resolver->setDefault('one', '1');
+ $this->resolver->setDefault('two', '20');
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ 'two' => '20',
+ ), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfSetDefaultFromLazyOption()
+ {
+ $this->resolver->setDefault('lazy', function (Options $options) {
+ $options->setDefault('default', 42);
+ });
+
+ $this->resolver->resolve();
+ }
+
+ public function testHasDefault()
+ {
+ $this->assertFalse($this->resolver->hasDefault('foo'));
+ $this->resolver->setDefault('foo', 42);
+ $this->assertTrue($this->resolver->hasDefault('foo'));
+ }
+
+ public function testHasDefaultWithNullValue()
+ {
+ $this->assertFalse($this->resolver->hasDefault('foo'));
+ $this->resolver->setDefault('foo', null);
+ $this->assertTrue($this->resolver->hasDefault('foo'));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // lazy setDefault()
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testSetLazyReturnsThis()
+ {
+ $this->assertSame($this->resolver, $this->resolver->setDefault('foo', function (Options $options) {}));
+ }
+
+ public function testSetLazyClosure()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ return 'lazy';
+ });
+
+ $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve());
+ }
+
+ public function testClosureWithoutTypeHintNotInvoked()
+ {
+ $closure = function ($options) {
+ \PHPUnit_Framework_Assert::fail('Should not be called');
+ };
+
+ $this->resolver->setDefault('foo', $closure);
+
+ $this->assertSame(array('foo' => $closure), $this->resolver->resolve());
+ }
+
+ public function testClosureWithoutParametersNotInvoked()
+ {
+ $closure = function () {
+ \PHPUnit_Framework_Assert::fail('Should not be called');
+ };
+
+ $this->resolver->setDefault('foo', $closure);
+
+ $this->assertSame(array('foo' => $closure), $this->resolver->resolve());
+ }
+
+ public function testAccessPreviousDefaultValue()
+ {
+ // defined by superclass
+ $this->resolver->setDefault('foo', 'bar');
+
+ // defined by subclass
+ $this->resolver->setDefault('foo', function (Options $options, $previousValue) {
+ \PHPUnit_Framework_Assert::assertEquals('bar', $previousValue);
+
+ return 'lazy';
+ });
+
+ $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve());
+ }
+
+ public function testAccessPreviousLazyDefaultValue()
+ {
+ // defined by superclass
+ $this->resolver->setDefault('foo', function (Options $options) {
+ return 'bar';
+ });
+
+ // defined by subclass
+ $this->resolver->setDefault('foo', function (Options $options, $previousValue) {
+ \PHPUnit_Framework_Assert::assertEquals('bar', $previousValue);
+
+ return 'lazy';
+ });
+
+ $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve());
+ }
+
+ public function testPreviousValueIsNotEvaluatedIfNoSecondArgument()
+ {
+ // defined by superclass
+ $this->resolver->setDefault('foo', function () {
+ \PHPUnit_Framework_Assert::fail('Should not be called');
+ });
+
+ // defined by subclass, no $previousValue argument defined!
+ $this->resolver->setDefault('foo', function (Options $options) {
+ return 'lazy';
+ });
+
+ $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve());
+ }
+
+ public function testOverwrittenLazyOptionNotEvaluated()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ \PHPUnit_Framework_Assert::fail('Should not be called');
+ });
+
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testInvokeEachLazyOptionOnlyOnce()
+ {
+ $calls = 0;
+
+ $this->resolver->setDefault('lazy1', function (Options $options) use (&$calls) {
+ \PHPUnit_Framework_Assert::assertSame(1, ++$calls);
+
+ $options['lazy2'];
+ });
+
+ $this->resolver->setDefault('lazy2', function (Options $options) use (&$calls) {
+ \PHPUnit_Framework_Assert::assertSame(2, ++$calls);
+ });
+
+ $this->resolver->resolve();
+
+ $this->assertSame(2, $calls);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // setRequired()/isRequired()/getRequiredOptions()
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testSetRequiredReturnsThis()
+ {
+ $this->assertSame($this->resolver, $this->resolver->setRequired('foo'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfSetRequiredFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->setRequired('bar');
+ });
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
+ */
+ public function testResolveFailsIfRequiredOptionMissing()
+ {
+ $this->resolver->setRequired('foo');
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfRequiredOptionSet()
+ {
+ $this->resolver->setRequired('foo');
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertNotEmpty($this->resolver->resolve());
+ }
+
+ public function testResolveSucceedsIfRequiredOptionPassed()
+ {
+ $this->resolver->setRequired('foo');
+
+ $this->assertNotEmpty($this->resolver->resolve(array('foo' => 'bar')));
+ }
+
+ public function testIsRequired()
+ {
+ $this->assertFalse($this->resolver->isRequired('foo'));
+ $this->resolver->setRequired('foo');
+ $this->assertTrue($this->resolver->isRequired('foo'));
+ }
+
+ public function testRequiredIfSetBefore()
+ {
+ $this->assertFalse($this->resolver->isRequired('foo'));
+
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setRequired('foo');
+
+ $this->assertTrue($this->resolver->isRequired('foo'));
+ }
+
+ public function testStillRequiredAfterSet()
+ {
+ $this->assertFalse($this->resolver->isRequired('foo'));
+
+ $this->resolver->setRequired('foo');
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertTrue($this->resolver->isRequired('foo'));
+ }
+
+ public function testIsNotRequiredAfterRemove()
+ {
+ $this->assertFalse($this->resolver->isRequired('foo'));
+ $this->resolver->setRequired('foo');
+ $this->resolver->remove('foo');
+ $this->assertFalse($this->resolver->isRequired('foo'));
+ }
+
+ public function testIsNotRequiredAfterClear()
+ {
+ $this->assertFalse($this->resolver->isRequired('foo'));
+ $this->resolver->setRequired('foo');
+ $this->resolver->clear();
+ $this->assertFalse($this->resolver->isRequired('foo'));
+ }
+
+ public function testGetRequiredOptions()
+ {
+ $this->resolver->setRequired(array('foo', 'bar'));
+ $this->resolver->setDefault('bam', 'baz');
+ $this->resolver->setDefault('foo', 'boo');
+
+ $this->assertSame(array('foo', 'bar'), $this->resolver->getRequiredOptions());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // isMissing()/getMissingOptions()
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testIsMissingIfNotSet()
+ {
+ $this->assertFalse($this->resolver->isMissing('foo'));
+ $this->resolver->setRequired('foo');
+ $this->assertTrue($this->resolver->isMissing('foo'));
+ }
+
+ public function testIsNotMissingIfSet()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertFalse($this->resolver->isMissing('foo'));
+ $this->resolver->setRequired('foo');
+ $this->assertFalse($this->resolver->isMissing('foo'));
+ }
+
+ public function testIsNotMissingAfterRemove()
+ {
+ $this->resolver->setRequired('foo');
+ $this->resolver->remove('foo');
+ $this->assertFalse($this->resolver->isMissing('foo'));
+ }
+
+ public function testIsNotMissingAfterClear()
+ {
+ $this->resolver->setRequired('foo');
+ $this->resolver->clear();
+ $this->assertFalse($this->resolver->isRequired('foo'));
+ }
+
+ public function testGetMissingOptions()
+ {
+ $this->resolver->setRequired(array('foo', 'bar'));
+ $this->resolver->setDefault('bam', 'baz');
+ $this->resolver->setDefault('foo', 'boo');
+
+ $this->assertSame(array('bar'), $this->resolver->getMissingOptions());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // setDefined()/isDefined()/getDefinedOptions()
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfSetDefinedFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->setDefined('bar');
+ });
+
+ $this->resolver->resolve();
+ }
+
+ public function testDefinedOptionsNotIncludedInResolvedOptions()
+ {
+ $this->resolver->setDefined('foo');
+
+ $this->assertSame(array(), $this->resolver->resolve());
+ }
+
+ public function testDefinedOptionsIncludedIfDefaultSetBefore()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setDefined('foo');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testDefinedOptionsIncludedIfDefaultSetAfter()
+ {
+ $this->resolver->setDefined('foo');
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testDefinedOptionsIncludedIfPassedToResolve()
+ {
+ $this->resolver->setDefined('foo');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve(array('foo' => 'bar')));
+ }
+
+ public function testIsDefined()
+ {
+ $this->assertFalse($this->resolver->isDefined('foo'));
+ $this->resolver->setDefined('foo');
+ $this->assertTrue($this->resolver->isDefined('foo'));
+ }
+
+ public function testLazyOptionsAreDefined()
+ {
+ $this->assertFalse($this->resolver->isDefined('foo'));
+ $this->resolver->setDefault('foo', function (Options $options) {});
+ $this->assertTrue($this->resolver->isDefined('foo'));
+ }
+
+ public function testRequiredOptionsAreDefined()
+ {
+ $this->assertFalse($this->resolver->isDefined('foo'));
+ $this->resolver->setRequired('foo');
+ $this->assertTrue($this->resolver->isDefined('foo'));
+ }
+
+ public function testSetOptionsAreDefined()
+ {
+ $this->assertFalse($this->resolver->isDefined('foo'));
+ $this->resolver->setDefault('foo', 'bar');
+ $this->assertTrue($this->resolver->isDefined('foo'));
+ }
+
+ public function testGetDefinedOptions()
+ {
+ $this->resolver->setDefined(array('foo', 'bar'));
+ $this->resolver->setDefault('baz', 'bam');
+ $this->resolver->setRequired('boo');
+
+ $this->assertSame(array('foo', 'bar', 'baz', 'boo'), $this->resolver->getDefinedOptions());
+ }
+
+ public function testRemovedOptionsAreNotDefined()
+ {
+ $this->assertFalse($this->resolver->isDefined('foo'));
+ $this->resolver->setDefined('foo');
+ $this->assertTrue($this->resolver->isDefined('foo'));
+ $this->resolver->remove('foo');
+ $this->assertFalse($this->resolver->isDefined('foo'));
+ }
+
+ public function testClearedOptionsAreNotDefined()
+ {
+ $this->assertFalse($this->resolver->isDefined('foo'));
+ $this->resolver->setDefined('foo');
+ $this->assertTrue($this->resolver->isDefined('foo'));
+ $this->resolver->clear();
+ $this->assertFalse($this->resolver->isDefined('foo'));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // setAllowedTypes()
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
+ */
+ public function testSetAllowedTypesFailsIfUnknownOption()
+ {
+ $this->resolver->setAllowedTypes('foo', 'string');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfSetAllowedTypesFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->setAllowedTypes('bar', 'string');
+ });
+
+ $this->resolver->setDefault('bar', 'baz');
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @dataProvider provideInvalidTypes
+ */
+ public function testResolveFailsIfInvalidType($actualType, $allowedType, $exceptionMessage)
+ {
+ $this->resolver->setDefined('option');
+ $this->resolver->setAllowedTypes('option', $allowedType);
+ $this->setExpectedException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException', $exceptionMessage);
+ $this->resolver->resolve(array('option' => $actualType));
+ }
+
+ public function provideInvalidTypes()
+ {
+ return array(
+ array(true, 'string', 'The option "option" with value true is expected to be of type "string", but is of type "boolean".'),
+ array(false, 'string', 'The option "option" with value false is expected to be of type "string", but is of type "boolean".'),
+ array(fopen(__FILE__, 'r'), 'string', 'The option "option" with value resource is expected to be of type "string", but is of type "resource".'),
+ array(array(), 'string', 'The option "option" with value array is expected to be of type "string", but is of type "array".'),
+ array(new OptionsResolver(), 'string', 'The option "option" with value Symfony\Component\OptionsResolver\OptionsResolver is expected to be of type "string", but is of type "Symfony\Component\OptionsResolver\OptionsResolver".'),
+ array(42, 'string', 'The option "option" with value 42 is expected to be of type "string", but is of type "integer".'),
+ array(null, 'string', 'The option "option" with value null is expected to be of type "string", but is of type "NULL".'),
+ array('bar', '\stdClass', 'The option "option" with value "bar" is expected to be of type "\stdClass", but is of type "string".'),
+ );
+ }
+
+ public function testResolveSucceedsIfValidType()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedTypes('foo', 'string');
+
+ $this->assertNotEmpty($this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ * @expectedExceptionMessage The option "foo" with value 42 is expected to be of type "string" or "bool", but is of type "integer".
+ */
+ public function testResolveFailsIfInvalidTypeMultiple()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->setAllowedTypes('foo', array('string', 'bool'));
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfValidTypeMultiple()
+ {
+ $this->resolver->setDefault('foo', true);
+ $this->resolver->setAllowedTypes('foo', array('string', 'bool'));
+
+ $this->assertNotEmpty($this->resolver->resolve());
+ }
+
+ public function testResolveSucceedsIfInstanceOfClass()
+ {
+ $this->resolver->setDefault('foo', new \stdClass());
+ $this->resolver->setAllowedTypes('foo', '\stdClass');
+
+ $this->assertNotEmpty($this->resolver->resolve());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // addAllowedTypes()
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
+ */
+ public function testAddAllowedTypesFailsIfUnknownOption()
+ {
+ $this->resolver->addAllowedTypes('foo', 'string');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfAddAllowedTypesFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->addAllowedTypes('bar', 'string');
+ });
+
+ $this->resolver->setDefault('bar', 'baz');
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfInvalidAddedType()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->addAllowedTypes('foo', 'string');
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfValidAddedType()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->addAllowedTypes('foo', 'string');
+
+ $this->assertNotEmpty($this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfInvalidAddedTypeMultiple()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->addAllowedTypes('foo', array('string', 'bool'));
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfValidAddedTypeMultiple()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->addAllowedTypes('foo', array('string', 'bool'));
+
+ $this->assertNotEmpty($this->resolver->resolve());
+ }
+
+ public function testAddAllowedTypesDoesNotOverwrite()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedTypes('foo', 'string');
+ $this->resolver->addAllowedTypes('foo', 'bool');
+
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertNotEmpty($this->resolver->resolve());
+ }
+
+ public function testAddAllowedTypesDoesNotOverwrite2()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedTypes('foo', 'string');
+ $this->resolver->addAllowedTypes('foo', 'bool');
+
+ $this->resolver->setDefault('foo', false);
+
+ $this->assertNotEmpty($this->resolver->resolve());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // setAllowedValues()
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
+ */
+ public function testSetAllowedValuesFailsIfUnknownOption()
+ {
+ $this->resolver->setAllowedValues('foo', 'bar');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfSetAllowedValuesFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->setAllowedValues('bar', 'baz');
+ });
+
+ $this->resolver->setDefault('bar', 'baz');
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ * @expectedExceptionMessage The option "foo" with value 42 is invalid. Accepted values are: "bar".
+ */
+ public function testResolveFailsIfInvalidValue()
+ {
+ $this->resolver->setDefined('foo');
+ $this->resolver->setAllowedValues('foo', 'bar');
+
+ $this->resolver->resolve(array('foo' => 42));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ * @expectedExceptionMessage The option "foo" with value null is invalid. Accepted values are: "bar".
+ */
+ public function testResolveFailsIfInvalidValueIsNull()
+ {
+ $this->resolver->setDefault('foo', null);
+ $this->resolver->setAllowedValues('foo', 'bar');
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfInvalidValueStrict()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->setAllowedValues('foo', '42');
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfValidValue()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedValues('foo', 'bar');
+
+ $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testResolveSucceedsIfValidValueIsNull()
+ {
+ $this->resolver->setDefault('foo', null);
+ $this->resolver->setAllowedValues('foo', null);
+
+ $this->assertEquals(array('foo' => null), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ * @expectedExceptionMessage The option "foo" with value 42 is invalid. Accepted values are: "bar", false, null.
+ */
+ public function testResolveFailsIfInvalidValueMultiple()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->setAllowedValues('foo', array('bar', false, null));
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfValidValueMultiple()
+ {
+ $this->resolver->setDefault('foo', 'baz');
+ $this->resolver->setAllowedValues('foo', array('bar', 'baz'));
+
+ $this->assertEquals(array('foo' => 'baz'), $this->resolver->resolve());
+ }
+
+ public function testResolveFailsIfClosureReturnsFalse()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->setAllowedValues('foo', function ($value) use (&$passedValue) {
+ $passedValue = $value;
+
+ return false;
+ });
+
+ try {
+ $this->resolver->resolve();
+ $this->fail('Should fail');
+ } catch (InvalidOptionsException $e) {
+ }
+
+ $this->assertSame(42, $passedValue);
+ }
+
+ public function testResolveSucceedsIfClosureReturnsTrue()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedValues('foo', function ($value) use (&$passedValue) {
+ $passedValue = $value;
+
+ return true;
+ });
+
+ $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve());
+ $this->assertSame('bar', $passedValue);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfAllClosuresReturnFalse()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->setAllowedValues('foo', array(
+ function () { return false; },
+ function () { return false; },
+ function () { return false; },
+ ));
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfAnyClosureReturnsTrue()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedValues('foo', array(
+ function () { return false; },
+ function () { return true; },
+ function () { return false; },
+ ));
+
+ $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // addAllowedValues()
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
+ */
+ public function testAddAllowedValuesFailsIfUnknownOption()
+ {
+ $this->resolver->addAllowedValues('foo', 'bar');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfAddAllowedValuesFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->addAllowedValues('bar', 'baz');
+ });
+
+ $this->resolver->setDefault('bar', 'baz');
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfInvalidAddedValue()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->addAllowedValues('foo', 'bar');
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfValidAddedValue()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->addAllowedValues('foo', 'bar');
+
+ $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testResolveSucceedsIfValidAddedValueIsNull()
+ {
+ $this->resolver->setDefault('foo', null);
+ $this->resolver->addAllowedValues('foo', null);
+
+ $this->assertEquals(array('foo' => null), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfInvalidAddedValueMultiple()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->addAllowedValues('foo', array('bar', 'baz'));
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfValidAddedValueMultiple()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->addAllowedValues('foo', array('bar', 'baz'));
+
+ $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testAddAllowedValuesDoesNotOverwrite()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedValues('foo', 'bar');
+ $this->resolver->addAllowedValues('foo', 'baz');
+
+ $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testAddAllowedValuesDoesNotOverwrite2()
+ {
+ $this->resolver->setDefault('foo', 'baz');
+ $this->resolver->setAllowedValues('foo', 'bar');
+ $this->resolver->addAllowedValues('foo', 'baz');
+
+ $this->assertEquals(array('foo' => 'baz'), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfAllAddedClosuresReturnFalse()
+ {
+ $this->resolver->setDefault('foo', 42);
+ $this->resolver->setAllowedValues('foo', function () { return false; });
+ $this->resolver->addAllowedValues('foo', function () { return false; });
+
+ $this->resolver->resolve();
+ }
+
+ public function testResolveSucceedsIfAnyAddedClosureReturnsTrue()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedValues('foo', function () { return false; });
+ $this->resolver->addAllowedValues('foo', function () { return true; });
+
+ $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testResolveSucceedsIfAnyAddedClosureReturnsTrue2()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedValues('foo', function () { return true; });
+ $this->resolver->addAllowedValues('foo', function () { return false; });
+
+ $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // setNormalizer()
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testSetNormalizerReturnsThis()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->assertSame($this->resolver, $this->resolver->setNormalizer('foo', function () {}));
+ }
+
+ public function testSetNormalizerClosure()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setNormalizer('foo', function () {
+ return 'normalized';
+ });
+
+ $this->assertEquals(array('foo' => 'normalized'), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
+ */
+ public function testSetNormalizerFailsIfUnknownOption()
+ {
+ $this->resolver->setNormalizer('foo', function () {});
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfSetNormalizerFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->setNormalizer('foo', function () {});
+ });
+
+ $this->resolver->setDefault('bar', 'baz');
+
+ $this->resolver->resolve();
+ }
+
+ public function testNormalizerReceivesSetOption()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->resolver->setNormalizer('foo', function (Options $options, $value) {
+ return 'normalized['.$value.']';
+ });
+
+ $this->assertEquals(array('foo' => 'normalized[bar]'), $this->resolver->resolve());
+ }
+
+ public function testNormalizerReceivesPassedOption()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->resolver->setNormalizer('foo', function (Options $options, $value) {
+ return 'normalized['.$value.']';
+ });
+
+ $resolved = $this->resolver->resolve(array('foo' => 'baz'));
+
+ $this->assertEquals(array('foo' => 'normalized[baz]'), $resolved);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testValidateTypeBeforeNormalization()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->resolver->setAllowedTypes('foo', 'int');
+
+ $this->resolver->setNormalizer('foo', function () {
+ \PHPUnit_Framework_Assert::fail('Should not be called.');
+ });
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testValidateValueBeforeNormalization()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->resolver->setAllowedValues('foo', 'baz');
+
+ $this->resolver->setNormalizer('foo', function () {
+ \PHPUnit_Framework_Assert::fail('Should not be called.');
+ });
+
+ $this->resolver->resolve();
+ }
+
+ public function testNormalizerCanAccessOtherOptions()
+ {
+ $this->resolver->setDefault('default', 'bar');
+ $this->resolver->setDefault('norm', 'baz');
+
+ $this->resolver->setNormalizer('norm', function (Options $options) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ \PHPUnit_Framework_Assert::assertSame('bar', $options['default']);
+
+ return 'normalized';
+ });
+
+ $this->assertEquals(array(
+ 'default' => 'bar',
+ 'norm' => 'normalized',
+ ), $this->resolver->resolve());
+ }
+
+ public function testNormalizerCanAccessLazyOptions()
+ {
+ $this->resolver->setDefault('lazy', function (Options $options) {
+ return 'bar';
+ });
+ $this->resolver->setDefault('norm', 'baz');
+
+ $this->resolver->setNormalizer('norm', function (Options $options) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ \PHPUnit_Framework_Assert::assertEquals('bar', $options['lazy']);
+
+ return 'normalized';
+ });
+
+ $this->assertEquals(array(
+ 'lazy' => 'bar',
+ 'norm' => 'normalized',
+ ), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
+ */
+ public function testFailIfCyclicDependencyBetweenNormalizers()
+ {
+ $this->resolver->setDefault('norm1', 'bar');
+ $this->resolver->setDefault('norm2', 'baz');
+
+ $this->resolver->setNormalizer('norm1', function (Options $options) {
+ $options['norm2'];
+ });
+
+ $this->resolver->setNormalizer('norm2', function (Options $options) {
+ $options['norm1'];
+ });
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
+ */
+ public function testFailIfCyclicDependencyBetweenNormalizerAndLazyOption()
+ {
+ $this->resolver->setDefault('lazy', function (Options $options) {
+ $options['norm'];
+ });
+
+ $this->resolver->setDefault('norm', 'baz');
+
+ $this->resolver->setNormalizer('norm', function (Options $options) {
+ $options['lazy'];
+ });
+
+ $this->resolver->resolve();
+ }
+
+ public function testCatchedExceptionFromNormalizerDoesNotCrashOptionResolver()
+ {
+ $throw = true;
+
+ $this->resolver->setDefaults(array('catcher' => null, 'thrower' => null));
+
+ $this->resolver->setNormalizer('catcher', function (Options $options) {
+ try {
+ return $options['thrower'];
+ } catch(\Exception $e) {
+ return false;
+ }
+ });
+
+ $this->resolver->setNormalizer('thrower', function (Options $options) use (&$throw) {
+ if ($throw) {
+ $throw = false;
+ throw new \UnexpectedValueException('throwing');
+ }
+
+ return true;
+ });
+
+ $this->resolver->resolve();
+ }
+
+ public function testCatchedExceptionFromLazyDoesNotCrashOptionResolver()
+ {
+ $throw = true;
+
+ $this->resolver->setDefault('catcher', function (Options $options) {
+ try {
+ return $options['thrower'];
+ } catch(\Exception $e) {
+ return false;
+ }
+ });
+
+ $this->resolver->setDefault('thrower', function (Options $options) use (&$throw) {
+ if ($throw) {
+ $throw = false;
+ throw new \UnexpectedValueException('throwing');
+ }
+
+ return true;
+ });
+
+ $this->resolver->resolve();
+ }
+
+ public function testInvokeEachNormalizerOnlyOnce()
+ {
+ $calls = 0;
+
+ $this->resolver->setDefault('norm1', 'bar');
+ $this->resolver->setDefault('norm2', 'baz');
+
+ $this->resolver->setNormalizer('norm1', function ($options) use (&$calls) {
+ \PHPUnit_Framework_Assert::assertSame(1, ++$calls);
+
+ $options['norm2'];
+ });
+ $this->resolver->setNormalizer('norm2', function () use (&$calls) {
+ \PHPUnit_Framework_Assert::assertSame(2, ++$calls);
+ });
+
+ $this->resolver->resolve();
+
+ $this->assertSame(2, $calls);
+ }
+
+ public function testNormalizerNotCalledForUnsetOptions()
+ {
+ $this->resolver->setDefined('norm');
+
+ $this->resolver->setNormalizer('norm', function () {
+ \PHPUnit_Framework_Assert::fail('Should not be called.');
+ });
+
+ $this->assertEmpty($this->resolver->resolve());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // setDefaults()
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testSetDefaultsReturnsThis()
+ {
+ $this->assertSame($this->resolver, $this->resolver->setDefaults(array('foo', 'bar')));
+ }
+
+ public function testSetDefaults()
+ {
+ $this->resolver->setDefault('one', '1');
+ $this->resolver->setDefault('two', 'bar');
+
+ $this->resolver->setDefaults(array(
+ 'two' => '2',
+ 'three' => '3',
+ ));
+
+ $this->assertEquals(array(
+ 'one' => '1',
+ 'two' => '2',
+ 'three' => '3',
+ ), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfSetDefaultsFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->setDefaults(array('two' => '2'));
+ });
+
+ $this->resolver->resolve();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // remove()
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testRemoveReturnsThis()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame($this->resolver, $this->resolver->remove('foo'));
+ }
+
+ public function testRemoveSingleOption()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setDefault('baz', 'boo');
+ $this->resolver->remove('foo');
+
+ $this->assertSame(array('baz' => 'boo'), $this->resolver->resolve());
+ }
+
+ public function testRemoveMultipleOptions()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setDefault('baz', 'boo');
+ $this->resolver->setDefault('doo', 'dam');
+
+ $this->resolver->remove(array('foo', 'doo'));
+
+ $this->assertSame(array('baz' => 'boo'), $this->resolver->resolve());
+ }
+
+ public function testRemoveLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ return 'lazy';
+ });
+ $this->resolver->remove('foo');
+
+ $this->assertSame(array(), $this->resolver->resolve());
+ }
+
+ public function testRemoveNormalizer()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setNormalizer('foo', function (Options $options, $value) {
+ return 'normalized';
+ });
+ $this->resolver->remove('foo');
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testRemoveAllowedTypes()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedTypes('foo', 'int');
+ $this->resolver->remove('foo');
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testRemoveAllowedValues()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedValues('foo', array('baz', 'boo'));
+ $this->resolver->remove('foo');
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfRemoveFromLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->remove('bar');
+ });
+
+ $this->resolver->setDefault('bar', 'baz');
+
+ $this->resolver->resolve();
+ }
+
+ public function testRemoveUnknownOptionIgnored()
+ {
+ $this->assertNotNull($this->resolver->remove('foo'));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // clear()
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testClearReturnsThis()
+ {
+ $this->assertSame($this->resolver, $this->resolver->clear());
+ }
+
+ public function testClearRemovesAllOptions()
+ {
+ $this->resolver->setDefault('one', 1);
+ $this->resolver->setDefault('two', 2);
+
+ $this->resolver->clear();
+
+ $this->assertEmpty($this->resolver->resolve());
+ }
+
+ public function testClearLazyOption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ return 'lazy';
+ });
+ $this->resolver->clear();
+
+ $this->assertSame(array(), $this->resolver->resolve());
+ }
+
+ public function testClearNormalizer()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setNormalizer('foo', function (Options $options, $value) {
+ return 'normalized';
+ });
+ $this->resolver->clear();
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testClearAllowedTypes()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedTypes('foo', 'int');
+ $this->resolver->clear();
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ public function testClearAllowedValues()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+ $this->resolver->setAllowedValues('foo', 'baz');
+ $this->resolver->clear();
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testFailIfClearFromLazyption()
+ {
+ $this->resolver->setDefault('foo', function (Options $options) {
+ $options->clear();
+ });
+
+ $this->resolver->setDefault('bar', 'baz');
+
+ $this->resolver->resolve();
+ }
+
+ public function testClearOptionAndNormalizer()
+ {
+ $this->resolver->setDefault('foo1', 'bar');
+ $this->resolver->setNormalizer('foo1', function (Options $options) {
+ return '';
+ });
+ $this->resolver->setDefault('foo2', 'bar');
+ $this->resolver->setNormalizer('foo2', function (Options $options) {
+ return '';
+ });
+
+ $this->resolver->clear();
+ $this->assertEmpty($this->resolver->resolve());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // ArrayAccess
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testArrayAccess()
+ {
+ $this->resolver->setDefault('default1', 0);
+ $this->resolver->setDefault('default2', 1);
+ $this->resolver->setRequired('required');
+ $this->resolver->setDefined('defined');
+ $this->resolver->setDefault('lazy1', function (Options $options) {
+ return 'lazy';
+ });
+
+ $this->resolver->setDefault('lazy2', function (Options $options) {
+ \PHPUnit_Framework_Assert::assertTrue(isset($options['default1']));
+ \PHPUnit_Framework_Assert::assertTrue(isset($options['default2']));
+ \PHPUnit_Framework_Assert::assertTrue(isset($options['required']));
+ \PHPUnit_Framework_Assert::assertTrue(isset($options['lazy1']));
+ \PHPUnit_Framework_Assert::assertTrue(isset($options['lazy2']));
+ \PHPUnit_Framework_Assert::assertFalse(isset($options['defined']));
+
+ \PHPUnit_Framework_Assert::assertSame(0, $options['default1']);
+ \PHPUnit_Framework_Assert::assertSame(42, $options['default2']);
+ \PHPUnit_Framework_Assert::assertSame('value', $options['required']);
+ \PHPUnit_Framework_Assert::assertSame('lazy', $options['lazy1']);
+
+ // Obviously $options['lazy'] and $options['defined'] cannot be
+ // accessed
+ });
+
+ $this->resolver->resolve(array('default2' => 42, 'required' => 'value'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testArrayAccessGetFailsOutsideResolve()
+ {
+ $this->resolver->setDefault('default', 0);
+
+ $this->resolver['default'];
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testArrayAccessExistsFailsOutsideResolve()
+ {
+ $this->resolver->setDefault('default', 0);
+
+ isset($this->resolver['default']);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testArrayAccessSetNotSupported()
+ {
+ $this->resolver['default'] = 0;
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testArrayAccessUnsetNotSupported()
+ {
+ $this->resolver->setDefault('default', 0);
+
+ unset($this->resolver['default']);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\NoSuchOptionException
+ * @expectedExceptionMessage The option "undefined" does not exist. Defined options are: "foo", "lazy".
+ */
+ public function testFailIfGetNonExisting()
+ {
+ $this->resolver->setDefault('foo', 'bar');
+
+ $this->resolver->setDefault('lazy', function (Options $options) {
+ $options['undefined'];
+ });
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\NoSuchOptionException
+ * @expectedExceptionMessage The optional option "defined" has no value set. You should make sure it is set with "isset" before reading it.
+ */
+ public function testFailIfGetDefinedButUnset()
+ {
+ $this->resolver->setDefined('defined');
+
+ $this->resolver->setDefault('lazy', function (Options $options) {
+ $options['defined'];
+ });
+
+ $this->resolver->resolve();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
+ */
+ public function testFailIfCyclicDependency()
+ {
+ $this->resolver->setDefault('lazy1', function (Options $options) {
+ $options['lazy2'];
+ });
+
+ $this->resolver->setDefault('lazy2', function (Options $options) {
+ $options['lazy1'];
+ });
+
+ $this->resolver->resolve();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Countable
+ ////////////////////////////////////////////////////////////////////////////
+
+ public function testCount()
+ {
+ $this->resolver->setDefault('default', 0);
+ $this->resolver->setRequired('required');
+ $this->resolver->setDefined('defined');
+ $this->resolver->setDefault('lazy1', function () {});
+
+ $this->resolver->setDefault('lazy2', function (Options $options) {
+ \PHPUnit_Framework_Assert::assertCount(4, $options);
+ });
+
+ $this->assertCount(4, $this->resolver->resolve(array('required' => 'value')));
+ }
+
+ /**
+ * In resolve() we count the options that are actually set (which may be
+ * only a subset of the defined options). Outside of resolve(), it's not
+ * clear what is counted.
+ *
+ * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
+ */
+ public function testCountFailsOutsideResolve()
+ {
+ $this->resolver->setDefault('foo', 0);
+ $this->resolver->setRequired('bar');
+ $this->resolver->setDefined('bar');
+ $this->resolver->setDefault('lazy1', function () {});
+
+ count($this->resolver);
+ }
+}
diff --git a/library/symfony/options-resolver/phpunit.xml.dist b/library/symfony/options-resolver/phpunit.xml.dist
new file mode 100644
index 000000000..abf84614b
--- /dev/null
+++ b/library/symfony/options-resolver/phpunit.xml.dist
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+ backupGlobals="false"
+ colors="true"
+ bootstrap="vendor/autoload.php"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Symfony OptionsResolver Component Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Resources</directory>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>