From 0de9a59cad68cb7738ef21ab8017616a4edaa741 Mon Sep 17 00:00:00 2001 From: Cas Leentfaar Date: Fri, 30 May 2014 11:40:52 +0200 Subject: [PATCH 1/5] clarified exception message to show the actual type passed to the resolver further improved message of the exception --- .../Component/OptionsResolver/Options.php | 163 ++++++++++++++++-- 1 file changed, 145 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 135b20a12af02..1f9fa81c2e142 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -23,26 +23,45 @@ */ class Options implements \ArrayAccess, \Iterator, \Countable { + /** + * Whether to format {@link \DateTime} objects as RFC-3339 dates + * during exceptions ("Y-m-d H:i:s"). + * + * @var int + */ + const PRETTY_DATE = 1; + + /** + * Whether to cast objects with a "__toString()" method to strings during exceptions. + * + * @var int + */ + const OBJECT_TO_STRING = 2; + /** * A list of option values. + * * @var array */ private $options = array(); /** * A list of normalizer closures. + * * @var array */ private $normalizers = array(); /** * A list of closures for evaluating lazy options. + * * @var array */ private $lazy = array(); /** * A list containing the currently locked options. + * * @var array */ private $lock = array(); @@ -228,7 +247,7 @@ public static function validateTypes(array $options, array $acceptedTypes) continue; } - $value = $options[$option]; + $value = $options[$option]; $optionTypes = (array) $optionTypes; foreach ($optionTypes as $type) { @@ -241,17 +260,11 @@ public static function validateTypes(array $options, array $acceptedTypes) } } - $printableValue = is_object($value) - ? get_class($value) - : (is_array($value) - ? 'Array' - : (string) $value); - throw new InvalidOptionsException(sprintf( 'The option "%s" with value "%s" is expected to be of type "%s"', $option, - $printableValue, - implode('", "', $optionTypes) + self::formatValue($value), + implode('", "', self::formatTypesOf($optionTypes)) )); } } @@ -275,7 +288,7 @@ public static function validateValues(array $options, array $acceptedValues) foreach ($acceptedValues as $option => $optionValues) { if (array_key_exists($option, $options)) { if (is_array($optionValues) && !in_array($options[$option], $optionValues, true)) { - throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', $optionValues))); + throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', self::formatValues($optionValues)))); } if (is_callable($optionValues) && !call_user_func($optionValues, $options[$option])) { @@ -285,6 +298,120 @@ public static function validateValues(array $options, array $acceptedValues) } } + /** + * Returns a string representation of the type of the value. + * + * @param mixed $value + * + * @return string + */ + private static function formatTypeOf($value) + { + return is_object($value) ? get_class($value) : gettype($value); + } + + /** + * @param array $optionTypes + * + * @return array + */ + private static function formatTypesOf(array $optionTypes) + { + foreach ($optionTypes as $x => $type) { + $optionTypes[$x] = self::formatTypeOf($type); + } + + return $optionTypes; + } + + /** + * 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 ("). Objects, arrays and resources are formatted as + * "object", "array" and "resource". If the parameter $prettyDateTime + * is set to true, {@link \DateTime} objects will be formatted as + * RFC-3339 dates ("Y-m-d H:i:s"). + * + * @param mixed $value The value to format as string + * @param int $format A bitwise combination of the format + * constants in this class + * + * @return string The string representation of the passed value + */ + private static function formatValue($value, $format = 0) + { + $isDateTime = $value instanceof \DateTime || $value instanceof \DateTimeInterface; + if (($format & self::PRETTY_DATE) && $isDateTime) { + if (class_exists('IntlDateFormatter')) { + $locale = \Locale::getDefault(); + $formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT); + // neither the native nor the stub IntlDateFormatter support + // DateTimeImmutable as of yet + if (!$value instanceof \DateTime) { + $value = new \DateTime( + $value->format('Y-m-d H:i:s.u e'), + $value->getTimezone() + ); + } + + return $formatter->format($value); + } + + return $value->format('Y-m-d H:i:s'); + } + + if (is_object($value)) { + if ($format & self::OBJECT_TO_STRING && method_exists($value, '__toString')) { + return $value->__toString(); + } + + return 'object'; + } + + 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. + * + * @param array $values + * @param bool $prettyDateTime + * + * @return string + */ + private static function formatValues(array $values, $prettyDateTime = false) + { + return array_map(function ($value) use ($prettyDateTime) { + return self::formatValue($value, $prettyDateTime); + }, $values); + } + /** * Constructs a new object with a set of default options. * @@ -382,8 +509,8 @@ public function replace(array $options) throw new OptionDefinitionException('Options cannot be replaced anymore once options have been read.'); } - $this->options = array(); - $this->lazy = array(); + $this->options = array(); + $this->lazy = array(); $this->normalizers = array(); foreach ($options as $option => $value) { @@ -424,7 +551,7 @@ public function overload($option, $value) $reflClosure = is_array($value) ? new \ReflectionMethod($value[0], $value[1]) : new \ReflectionFunction($value); - $params = $reflClosure->getParameters(); + $params = $reflClosure->getParameters(); if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && __CLASS__ === $class->name) { // Initialize the option if no previous value exists @@ -487,7 +614,7 @@ public function get($option) * * @param string $option The option name. * - * @return bool Whether the option exists. + * @return bool Whether the option exists. */ public function has($option) { @@ -527,8 +654,8 @@ public function clear() throw new OptionDefinitionException('Options cannot be cleared anymore once options have been read.'); } - $this->options = array(); - $this->lazy = array(); + $this->options = array(); + $this->lazy = array(); $this->normalizers = array(); } @@ -567,7 +694,7 @@ public function all() * * @param string $option The option name. * - * @return bool Whether the option exists. + * @return bool Whether the option exists. * * @see \ArrayAccess::offsetExists() */ @@ -746,7 +873,7 @@ private function normalizeOption($option) /** @var \Closure $normalizer */ $normalizer = $this->normalizers[$option]; - $this->lock[$option] = true; + $this->lock[$option] = true; $this->options[$option] = $normalizer($this, array_key_exists($option, $this->options) ? $this->options[$option] : null); unset($this->lock[$option]); From 004e83252265df7f0fc0eefdee12ec0c772bf697 Mon Sep 17 00:00:00 2001 From: Cas Leentfaar Date: Fri, 10 Oct 2014 09:28:11 +0200 Subject: [PATCH 2/5] simplified methods --- .../Component/OptionsResolver/Options.php | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 1f9fa81c2e142..f8feeb0c55139 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -263,8 +263,8 @@ public static function validateTypes(array $options, array $acceptedTypes) throw new InvalidOptionsException(sprintf( 'The option "%s" with value "%s" is expected to be of type "%s"', $option, - self::formatValue($value), - implode('", "', self::formatTypesOf($optionTypes)) + static::formatValue($value), + static::formatTypesOf($optionTypes) )); } } @@ -288,7 +288,7 @@ public static function validateValues(array $options, array $acceptedValues) foreach ($acceptedValues as $option => $optionValues) { if (array_key_exists($option, $options)) { if (is_array($optionValues) && !in_array($options[$option], $optionValues, true)) { - throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', self::formatValues($optionValues)))); + throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], static::formatValues($optionValues))); } if (is_callable($optionValues) && !call_user_func($optionValues, $options[$option])) { @@ -311,17 +311,24 @@ private static function formatTypeOf($value) } /** - * @param array $optionTypes + * Returns a string representation of a list of types. * - * @return array + * Each of the types is converted to a string using + * {@link formatTypeOf()}. The values are then concatenated with commas. + * + * @param array $types A list of types + * + * @return string The string representation of the type list + * + * @see formatTypeOf() */ - private static function formatTypesOf(array $optionTypes) + private static function formatTypesOf(array $types) { - foreach ($optionTypes as $x => $type) { - $optionTypes[$x] = self::formatTypeOf($type); + foreach ($types as $key => $value) { + $types[$key] = static::formatTypeOf($value); } - return $optionTypes; + return implode(', ', $types); } /** @@ -334,16 +341,14 @@ private static function formatTypesOf(array $optionTypes) * is set to true, {@link \DateTime} objects will be formatted as * RFC-3339 dates ("Y-m-d H:i:s"). * - * @param mixed $value The value to format as string - * @param int $format A bitwise combination of the format - * constants in this class + * @param mixed $value The value to format as string * * @return string The string representation of the passed value */ - private static function formatValue($value, $format = 0) + private static function formatValue($value) { $isDateTime = $value instanceof \DateTime || $value instanceof \DateTimeInterface; - if (($format & self::PRETTY_DATE) && $isDateTime) { + if (static::PRETTY_DATE && $isDateTime) { if (class_exists('IntlDateFormatter')) { $locale = \Locale::getDefault(); $formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT); @@ -363,7 +368,7 @@ private static function formatValue($value, $format = 0) } if (is_object($value)) { - if ($format & self::OBJECT_TO_STRING && method_exists($value, '__toString')) { + if (static::OBJECT_TO_STRING && method_exists($value, '__toString')) { return $value->__toString(); } @@ -400,16 +405,22 @@ private static function formatValue($value, $format = 0) /** * Returns a string representation of a list of values. * - * @param array $values - * @param bool $prettyDateTime + * Each of the values is converted to a string using + * {@link formatValue()}. The values are then concatenated with commas. * - * @return string + * @param array $values A list of values + * + * @return string The string representation of the value list + * + * @see formatValue() */ - private static function formatValues(array $values, $prettyDateTime = false) + private static function formatValues(array $values) { - return array_map(function ($value) use ($prettyDateTime) { - return self::formatValue($value, $prettyDateTime); - }, $values); + foreach ($values as $key => $value) { + $values[$key] = static::formatValue($value); + } + + return implode(', ', $values); } /** From dd014720e9767d304ed37d1e48767fe19b9e47c7 Mon Sep 17 00:00:00 2001 From: Cas Leentfaar Date: Fri, 10 Oct 2014 09:54:41 +0200 Subject: [PATCH 3/5] removed bad indent --- src/Symfony/Component/OptionsResolver/Options.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index f8feeb0c55139..89da15c710d85 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -247,7 +247,7 @@ public static function validateTypes(array $options, array $acceptedTypes) continue; } - $value = $options[$option]; + $value = $options[$option]; $optionTypes = (array) $optionTypes; foreach ($optionTypes as $type) { From b7409f2c378b792911754e2143318a18a6ab19e5 Mon Sep 17 00:00:00 2001 From: Cas Leentfaar Date: Fri, 10 Oct 2014 09:56:11 +0200 Subject: [PATCH 4/5] removed bad indents --- src/Symfony/Component/OptionsResolver/Options.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 89da15c710d85..0e94906cdad62 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -520,8 +520,8 @@ public function replace(array $options) throw new OptionDefinitionException('Options cannot be replaced anymore once options have been read.'); } - $this->options = array(); - $this->lazy = array(); + $this->options = array(); + $this->lazy = array(); $this->normalizers = array(); foreach ($options as $option => $value) { @@ -562,7 +562,7 @@ public function overload($option, $value) $reflClosure = is_array($value) ? new \ReflectionMethod($value[0], $value[1]) : new \ReflectionFunction($value); - $params = $reflClosure->getParameters(); + $params = $reflClosure->getParameters(); if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && __CLASS__ === $class->name) { // Initialize the option if no previous value exists @@ -665,8 +665,8 @@ public function clear() throw new OptionDefinitionException('Options cannot be cleared anymore once options have been read.'); } - $this->options = array(); - $this->lazy = array(); + $this->options = array(); + $this->lazy = array(); $this->normalizers = array(); } @@ -884,7 +884,7 @@ private function normalizeOption($option) /** @var \Closure $normalizer */ $normalizer = $this->normalizers[$option]; - $this->lock[$option] = true; + $this->lock[$option] = true; $this->options[$option] = $normalizer($this, array_key_exists($option, $this->options) ? $this->options[$option] : null); unset($this->lock[$option]); From 18a8d10fe65fa191c2a06776278d0242468f2678 Mon Sep 17 00:00:00 2001 From: Cas Leentfaar Date: Mon, 13 Oct 2014 12:12:30 +0200 Subject: [PATCH 5/5] replaced 'static'-calls with 'self', removed alignment --- .../Component/OptionsResolver/Options.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 0e94906cdad62..9e5a7736c1af0 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -98,13 +98,13 @@ class Options implements \ArrayAccess, \Iterator, \Countable public static function resolve(array $options, $defaults) { if (is_array($defaults)) { - static::validateNames($options, $defaults, true); + self::validateNames($options, $defaults, true); return array_replace($defaults, $options); } if ($defaults instanceof self) { - static::validateNames($options, $defaults->options, true); + self::validateNames($options, $defaults->options, true); // Make sure this method can be called multiple times $combinedOptions = clone $defaults; @@ -119,8 +119,8 @@ public static function resolve(array $options, $defaults) } if ($defaults instanceof OptionsConfig) { - static::validateNames($options, $defaults->knownOptions, true); - static::validateRequired($options, $defaults->requiredOptions, true); + self::validateNames($options, $defaults->knownOptions, true); + self::validateRequired($options, $defaults->requiredOptions, true); // Make sure this method can be called multiple times $combinedOptions = clone $defaults->defaultOptions; @@ -133,8 +133,8 @@ public static function resolve(array $options, $defaults) // Resolve options $resolvedOptions = $combinedOptions->all(); - static::validateTypes($resolvedOptions, $defaults->allowedTypes); - static::validateValues($resolvedOptions, $defaults->allowedValues); + self::validateTypes($resolvedOptions, $defaults->allowedTypes); + self::validateValues($resolvedOptions, $defaults->allowedValues); return $resolvedOptions; } @@ -263,8 +263,8 @@ public static function validateTypes(array $options, array $acceptedTypes) throw new InvalidOptionsException(sprintf( 'The option "%s" with value "%s" is expected to be of type "%s"', $option, - static::formatValue($value), - static::formatTypesOf($optionTypes) + self::formatValue($value), + self::formatTypesOf($optionTypes) )); } } @@ -288,7 +288,7 @@ public static function validateValues(array $options, array $acceptedValues) foreach ($acceptedValues as $option => $optionValues) { if (array_key_exists($option, $options)) { if (is_array($optionValues) && !in_array($options[$option], $optionValues, true)) { - throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], static::formatValues($optionValues))); + throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], self::formatValues($optionValues))); } if (is_callable($optionValues) && !call_user_func($optionValues, $options[$option])) { @@ -325,7 +325,7 @@ private static function formatTypeOf($value) private static function formatTypesOf(array $types) { foreach ($types as $key => $value) { - $types[$key] = static::formatTypeOf($value); + $types[$key] = self::formatTypeOf($value); } return implode(', ', $types); @@ -348,9 +348,9 @@ private static function formatTypesOf(array $types) private static function formatValue($value) { $isDateTime = $value instanceof \DateTime || $value instanceof \DateTimeInterface; - if (static::PRETTY_DATE && $isDateTime) { + if (self::PRETTY_DATE && $isDateTime) { if (class_exists('IntlDateFormatter')) { - $locale = \Locale::getDefault(); + $locale = \Locale::getDefault(); $formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT); // neither the native nor the stub IntlDateFormatter support // DateTimeImmutable as of yet @@ -368,7 +368,7 @@ private static function formatValue($value) } if (is_object($value)) { - if (static::OBJECT_TO_STRING && method_exists($value, '__toString')) { + if (self::OBJECT_TO_STRING && method_exists($value, '__toString')) { return $value->__toString(); } @@ -417,7 +417,7 @@ private static function formatValue($value) private static function formatValues(array $values) { foreach ($values as $key => $value) { - $values[$key] = static::formatValue($value); + $values[$key] = self::formatValue($value); } return implode(', ', $values);