Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 356540a

Browse files
committed
[OptionsResolver] Display full nested options hierarchy in exceptions
1 parent 47322db commit 356540a

File tree

2 files changed

+45
-21
lines changed

2 files changed

+45
-21
lines changed

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ class OptionsResolver implements Options
103103
*/
104104
private $locked = false;
105105

106+
private $parentsOptions = [];
107+
106108
private static $typeAliases = [
107109
'boolean' => 'bool',
108110
'integer' => 'int',
@@ -423,7 +425,7 @@ public function setDeprecated(string $option, $deprecationMessage = 'The option
423425
}
424426

425427
if (!isset($this->defined[$option])) {
426-
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
428+
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptionsForAnException($option), implode('", "', array_keys($this->defined))));
427429
}
428430

429431
if (!\is_string($deprecationMessage) && !$deprecationMessage instanceof \Closure) {
@@ -481,7 +483,7 @@ public function setNormalizer($option, \Closure $normalizer)
481483
}
482484

483485
if (!isset($this->defined[$option])) {
484-
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
486+
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptionsForAnException($option), implode('", "', array_keys($this->defined))));
485487
}
486488

487489
$this->normalizers[$option] = [$normalizer];
@@ -526,7 +528,7 @@ public function addNormalizer(string $option, \Closure $normalizer, bool $forceP
526528
}
527529

528530
if (!isset($this->defined[$option])) {
529-
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
531+
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptionsForAnException($option), implode('", "', array_keys($this->defined))));
530532
}
531533

532534
if ($forcePrepend) {
@@ -569,7 +571,7 @@ public function setAllowedValues($option, $allowedValues)
569571
}
570572

571573
if (!isset($this->defined[$option])) {
572-
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
574+
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptionsForAnException($option), implode('", "', array_keys($this->defined))));
573575
}
574576

575577
$this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : [$allowedValues];
@@ -610,7 +612,7 @@ public function addAllowedValues($option, $allowedValues)
610612
}
611613

612614
if (!isset($this->defined[$option])) {
613-
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
615+
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptionsForAnException($option), implode('", "', array_keys($this->defined))));
614616
}
615617

616618
if (!\is_array($allowedValues)) {
@@ -651,7 +653,7 @@ public function setAllowedTypes($option, $allowedTypes)
651653
}
652654

653655
if (!isset($this->defined[$option])) {
654-
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
656+
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptionsForAnException($option), implode('", "', array_keys($this->defined))));
655657
}
656658

657659
$this->allowedTypes[$option] = (array) $allowedTypes;
@@ -686,7 +688,7 @@ public function addAllowedTypes($option, $allowedTypes)
686688
}
687689

688690
if (!isset($this->defined[$option])) {
689-
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
691+
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptionsForAnException($option), implode('", "', array_keys($this->defined))));
690692
}
691693

692694
if (!isset($this->allowedTypes[$option])) {
@@ -793,7 +795,7 @@ public function resolve(array $options = [])
793795
ksort($clone->defined);
794796
ksort($diff);
795797

796-
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))));
798+
throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', $this->formatOptionsForAnException(array_keys($diff)), implode('", "', array_keys($clone->defined))));
797799
}
798800

799801
// Override options set by the user
@@ -809,7 +811,7 @@ public function resolve(array $options = [])
809811
if (\count($diff) > 0) {
810812
ksort($diff);
811813

812-
throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', implode('", "', array_keys($diff))));
814+
throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', $this->formatOptionsForAnException(array_keys($diff))));
813815
}
814816

815817
// Lock the container
@@ -860,10 +862,10 @@ public function offsetGet($option/*, bool $triggerDeprecation = true*/)
860862
// Check whether the option is set at all
861863
if (!isset($this->defaults[$option]) && !\array_key_exists($option, $this->defaults)) {
862864
if (!isset($this->defined[$option])) {
863-
throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
865+
throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptionsForAnException($option), implode('", "', array_keys($this->defined))));
864866
}
865867

866-
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));
868+
throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $this->formatOptionsForAnException($option)));
867869
}
868870

869871
$value = $this->defaults[$option];
@@ -872,17 +874,19 @@ public function offsetGet($option/*, bool $triggerDeprecation = true*/)
872874
if (isset($this->nested[$option])) {
873875
// If the closure is already being called, we have a cyclic dependency
874876
if (isset($this->calling[$option])) {
875-
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling))));
877+
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptionsForAnException(array_keys($this->calling))));
876878
}
877879

878880
if (!\is_array($value)) {
879-
throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $option, $this->formatValue($value), $this->formatTypeOf($value)));
881+
throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptionsForAnException($option), $this->formatValue($value), $this->formatTypeOf($value)));
880882
}
881883

882884
// The following section must be protected from cyclic calls.
883885
$this->calling[$option] = true;
884886
try {
885887
$resolver = new self();
888+
$resolver->parentsOptions = $this->parentsOptions;
889+
$resolver->parentsOptions[] = $option;
886890
foreach ($this->nested[$option] as $closure) {
887891
$closure($resolver, $this);
888892
}
@@ -897,7 +901,7 @@ public function offsetGet($option/*, bool $triggerDeprecation = true*/)
897901
// If the closure is already being called, we have a cyclic
898902
// dependency
899903
if (isset($this->calling[$option])) {
900-
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling))));
904+
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptionsForAnException(array_keys($this->calling))));
901905
}
902906

903907
// The following section must be protected from cyclic
@@ -932,10 +936,10 @@ public function offsetGet($option/*, bool $triggerDeprecation = true*/)
932936
$keys = array_keys($invalidTypes);
933937

934938
if (1 === \count($keys) && '[]' === substr($keys[0], -2)) {
935-
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $option, $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), $keys[0]));
939+
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $this->formatOptionsForAnException($option), $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), $keys[0]));
936940
}
937941

938-
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]), implode('|', array_keys($invalidTypes))));
942+
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $this->formatOptionsForAnException($option), $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), implode('|', array_keys($invalidTypes))));
939943
}
940944
}
941945

@@ -989,7 +993,7 @@ public function offsetGet($option/*, bool $triggerDeprecation = true*/)
989993
if ($deprecationMessage instanceof \Closure) {
990994
// If the closure is already being called, we have a cyclic dependency
991995
if (isset($this->calling[$option])) {
992-
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling))));
996+
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptionsForAnException(array_keys($this->calling))));
993997
}
994998

995999
$this->calling[$option] = true;
@@ -1012,7 +1016,7 @@ public function offsetGet($option/*, bool $triggerDeprecation = true*/)
10121016
// If the closure is already being called, we have a cyclic
10131017
// dependency
10141018
if (isset($this->calling[$option])) {
1015-
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling))));
1019+
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptionsForAnException(array_keys($this->calling))));
10161020
}
10171021

10181022
// The following section must be protected from cyclic
@@ -1195,4 +1199,24 @@ private function formatValues(array $values): string
11951199

11961200
return implode(', ', $values);
11971201
}
1202+
1203+
private function formatOptionsForAnException($options): string
1204+
{
1205+
if (!\is_array($options)) {
1206+
$options = [$options];
1207+
}
1208+
1209+
if (!empty($this->parentsOptions)) {
1210+
$prefix = array_shift($this->parentsOptions);
1211+
if (!empty($this->parentsOptions)) {
1212+
$prefix .= sprintf('[%s]', implode('][', $this->parentsOptions));
1213+
}
1214+
1215+
$options = array_map(static function (string $option) use ($prefix): string {
1216+
return sprintf('%s[%s]', $prefix, $option);
1217+
}, $options);
1218+
}
1219+
1220+
return implode('", "', $options);
1221+
}
11981222
}

src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,7 +1993,7 @@ public function testIsNestedOption()
19931993
public function testFailsIfUndefinedNestedOption()
19941994
{
19951995
$this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException');
1996-
$this->expectExceptionMessage('The option "foo" does not exist. Defined options are: "host", "port".');
1996+
$this->expectExceptionMessage('The option "database[foo]" does not exist. Defined options are: "host", "port".');
19971997
$this->resolver->setDefaults([
19981998
'name' => 'default',
19991999
'database' => function (OptionsResolver $resolver) {
@@ -2008,7 +2008,7 @@ public function testFailsIfUndefinedNestedOption()
20082008
public function testFailsIfMissingRequiredNestedOption()
20092009
{
20102010
$this->expectException('Symfony\Component\OptionsResolver\Exception\MissingOptionsException');
2011-
$this->expectExceptionMessage('The required option "host" is missing.');
2011+
$this->expectExceptionMessage('The required option "database[host]" is missing.');
20122012
$this->resolver->setDefaults([
20132013
'name' => 'default',
20142014
'database' => function (OptionsResolver $resolver) {
@@ -2023,7 +2023,7 @@ public function testFailsIfMissingRequiredNestedOption()
20232023
public function testFailsIfInvalidTypeNestedOption()
20242024
{
20252025
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
2026-
$this->expectExceptionMessage('The option "logging" with value null is expected to be of type "bool", but is of type "NULL".');
2026+
$this->expectExceptionMessage('The option "database[logging]" with value null is expected to be of type "bool", but is of type "NULL".');
20272027
$this->resolver->setDefaults([
20282028
'name' => 'default',
20292029
'database' => function (OptionsResolver $resolver) {

0 commit comments

Comments
 (0)