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

Skip to content

Commit 9e4e02a

Browse files
committed
[OptionsResolver] resolve extra options
closes #15524. Set allowed types for many options or for a set of nested or extra options. * add `Options::NONE` * add `Options::ALL` * add `Options::DEFINED` * add `Options::NESTED` * add `Options::EXTRA` * add `OptionsResolver::allowedValuesForAll` * add `OptionsResolver::allowedTypesForAll` * add `OptionsResolver::resolveUndefined` * add `OptionsResolver::addAllowedValuesForAll()` * add `OptionsResolver::addAllowedTypesForAll()` * add `OptionsResolver::setPrototype()`
1 parent f2ad947 commit 9e4e02a

File tree

2 files changed

+290
-9
lines changed

2 files changed

+290
-9
lines changed

src/Symfony/Component/OptionsResolver/Options.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@
1919
*/
2020
interface Options extends \ArrayAccess, \Countable
2121
{
22+
const NONE = 0;
23+
const ALL = 1;
24+
const DEFINED = 2;
25+
const NESTED = 3;
26+
const EXTRA = 4;
2227
}

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 285 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,34 @@ class OptionsResolver implements Options
7676
*/
7777
private $allowedValues = array();
7878

79+
/**
80+
* A list of accepted values for all options.
81+
*
82+
* @var array
83+
*/
84+
private $allowedValuesForAll = array();
85+
7986
/**
8087
* A list of accepted types for each option.
8188
*
8289
* @var array
8390
*/
8491
private $allowedTypes = array();
8592

93+
/**
94+
* A list of accepted types for all options.
95+
*
96+
* @var array
97+
*/
98+
private $allowedTypesForAll = array();
99+
100+
/**
101+
* Whether to resolve undefined options.
102+
*
103+
* @var bool
104+
*/
105+
private $resolveUndefined = false;
106+
86107
/**
87108
* A list of closures for evaluating lazy options.
88109
*
@@ -558,7 +579,7 @@ public function setAllowedValues($option, $allowedValues)
558579
return $this;
559580
}
560581

561-
if (!isset($this->defined[$option])) {
582+
if (!isset($this->defined[$option]) && !$this->resolveUndefined && Options::EXTRA !== $option) {
562583
throw new UndefinedOptionsException(sprintf(
563584
'The option "%s" does not exist. Defined options are: "%s".',
564585
$option,
@@ -609,7 +630,7 @@ public function addAllowedValues($option, $allowedValues)
609630
return $this;
610631
}
611632

612-
if (!isset($this->defined[$option])) {
633+
if (!isset($this->defined[$option]) && !$this->resolveUndefined && Options::EXTRA !== $option) {
613634
throw new UndefinedOptionsException(sprintf(
614635
'The option "%s" does not exist. Defined options are: "%s".',
615636
$option,
@@ -633,6 +654,98 @@ public function addAllowedValues($option, $allowedValues)
633654
return $this;
634655
}
635656

657+
/**
658+
* Adds allowed values for one or more options.
659+
*
660+
* First argument may be a name or an array of option names.
661+
*
662+
* You can pass a constant of Options interface as first argument to target
663+
* a group of options. Supported values are:
664+
*
665+
* - Options::ALL Defined and extra options
666+
* - Options::DEFINED
667+
* - Options::NESTED
668+
* - Options::EXTRA
669+
*
670+
* If a nested option name is passed it will apply to all nested options.
671+
* You can prevent it by passing Options::NONE as fourth argument.
672+
*
673+
* Instead of passing values, you may also pass a closures with the
674+
* following signature:
675+
*
676+
* function ($value) {
677+
* // return true or false
678+
* }
679+
*
680+
* The closure receives the value as argument and should return true to
681+
* accept the value and false to reject the value.
682+
*
683+
* @param string|string[]|int $optionNames One or more option names
684+
* or an Options constant
685+
* @param mixed $allowedValues One or more accepted values
686+
* or closures
687+
* @param bool $replace Whether to replace previous
688+
* values
689+
* @param int $nested This method is recursive for
690+
* nested options which is the
691+
* default. Pass Options::NONE
692+
* to change it.
693+
*
694+
* @return OptionsResolver This instance
695+
*
696+
* @throws UndefinedOptionsException If an option is undefined
697+
* @throws AccessException If called from a lazy option or normalizer
698+
*/
699+
public function addAllowedValuesForAll($optionNames, $allowedValues, $replace = false, $nested = Options::ALL)
700+
{
701+
if ($this->locked) {
702+
throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.');
703+
}
704+
705+
switch ($optionNames) {
706+
case Options::ALL:
707+
// Use this values for all defined and extra options
708+
// except nested which are always arrays.
709+
$this->allowedValuesForAll = $replace
710+
? $allowedValues : array_merge($this->allowedValuesForAll, $allowedValues);
711+
712+
// If not recursive return
713+
if (Options::NONE === $nested) {
714+
return $this;
715+
}
716+
717+
$optionNames = $this->getNestedOptions();
718+
break;
719+
case Options::NESTED:
720+
$optionNames = $this->getNestedOptions();
721+
break;
722+
case Options::DEFINED:
723+
$optionNames = $this->getDefinedOptions();
724+
break;
725+
case Options::NONE:
726+
// Not supported
727+
return $this;
728+
default:
729+
// A custom array of option names or
730+
// one option name or Options::EXTRA
731+
$optionNames = (array) $optionNames;
732+
}
733+
734+
foreach ($optionNames as $option) {
735+
if ($this->isNested($option) && Options::NONE !== $nested) {
736+
$this->nested[$option]->addAllowedValuesForAll($nested, $allowedValues, $replace);
737+
} else {
738+
if ($replace) {
739+
$this->setAllowedValues($option, $allowedValues);
740+
} else {
741+
$this->addAllowedValues($option, $allowedValues);
742+
}
743+
}
744+
}
745+
746+
return $this;
747+
}
748+
636749
/**
637750
* Sets allowed types for an option.
638751
*
@@ -660,7 +773,7 @@ public function setAllowedTypes($option, $allowedTypes)
660773
return $this;
661774
}
662775

663-
if (!isset($this->defined[$option])) {
776+
if (!isset($this->defined[$option]) && !$this->resolveUndefined && Options::EXTRA !== $option) {
664777
throw new UndefinedOptionsException(sprintf(
665778
'The option "%s" does not exist. Defined options are: "%s".',
666779
$option,
@@ -705,7 +818,7 @@ public function addAllowedTypes($option, $allowedTypes)
705818
return $this;
706819
}
707820

708-
if (!isset($this->defined[$option])) {
821+
if (!isset($this->defined[$option]) && !$this->resolveUndefined && Options::EXTRA !== $option) {
709822
throw new UndefinedOptionsException(sprintf(
710823
'The option "%s" does not exist. Defined options are: "%s".',
711824
$option,
@@ -725,6 +838,147 @@ public function addAllowedTypes($option, $allowedTypes)
725838
return $this;
726839
}
727840

841+
/**
842+
* Adds allowed types for one or more options.
843+
*
844+
* First argument may be a name or an array of option names.
845+
*
846+
* You can pass a constant of Options interface as first argument to target
847+
* a group of options. Supported values are:
848+
*
849+
* - Options::ALL Defined and extra options
850+
* - Options::DEFINED
851+
* - Options::NESTED
852+
*
853+
* If a nested option name is passed it will apply to all nested options.
854+
* You can prevent it by passing Options::NONE as fourth argument.
855+
*
856+
* Any type for which a corresponding is_<type>() function exists is
857+
* acceptable. Additionally, fully-qualified class or interface names may
858+
* be passed.
859+
*
860+
* @param string|string[]|int $optionNames One or more option names
861+
* or Options::ALL
862+
* @param string|string[] $allowedTypes One or more accepted types
863+
* @param bool $replace Whether to replace previous
864+
* value
865+
* @param int $nested This method is recursive for
866+
* nested options which is the
867+
* default. Pass Options::NONE
868+
* to change it.
869+
*
870+
* @return OptionsResolver This instance
871+
*
872+
* @throws UndefinedOptionsException If an option is undefined
873+
* @throws AccessException If called from a lazy option or normalizer
874+
*/
875+
public function addAllowedTypesForAll($optionNames, $allowedTypes, $replace = false, $nested = Options::ALL)
876+
{
877+
if ($this->locked) {
878+
throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.');
879+
}
880+
881+
switch ($optionNames) {
882+
case Options::ALL:
883+
// Use this values for all defined and extra options
884+
// except nested which are always arrays.
885+
$this->allowedTypesForAll = $replace
886+
? array_merge($this->allowedTypesForAll, $allowedTypes) : $allowedTypes;
887+
888+
// If not recursive return
889+
if (Options::NONE === $nested) {
890+
return $this;
891+
}
892+
893+
$optionNames = $this->getNestedOptions();
894+
break;
895+
case Options::NESTED:
896+
$optionNames = $this->getNestedOptions();
897+
break;
898+
case Options::DEFINED:
899+
$optionNames = $this->getDefinedOptions();
900+
break;
901+
case Options::NONE:
902+
// Not supported
903+
return $this;
904+
default:
905+
// A custom array of option names or
906+
// one option name or Options::EXTRA
907+
$optionNames = (array) $optionNames;
908+
}
909+
910+
foreach ($optionNames as $option) {
911+
if ($this->isNested($option) && Options::NONE !== $nested) {
912+
$this->nested[$option]->addAllowedTypesForAll($nested, $allowedTypes, $replace);
913+
} else {
914+
if ($replace) {
915+
$this->setAllowedTypes($option, $allowedTypes);
916+
} else {
917+
$this->addAllowedTypes($option, $allowedTypes);
918+
}
919+
}
920+
}
921+
922+
return $this;
923+
}
924+
925+
/**
926+
* Add a prototype for extra or all options.
927+
*
928+
* If this instance was not accepting extra options before, this method
929+
* allows it by default.
930+
*
931+
* First argument is an allowed type and second is one more values/closures.
932+
* By default it only applies on undefined options, to validate previous defaults
933+
* as well, pass Options::ALL as third argument.
934+
*
935+
* This method overrides previous allowed type and values for concerned options.
936+
*
937+
* $resolver->setNested('emails')
938+
* ->setPrototype(array(
939+
* 'string' => function ($email) use ($regex) {
940+
* return preg_match($regex, $email);
941+
* },
942+
* );
943+
*
944+
* @param string $type A string defining an allowed type
945+
* @param mixed $values One or more allowed values/closures
946+
* @param int $options Options::EXTRA by default, but you can pass
947+
* Options::ALL to apply the rule on previous defaults
948+
*
949+
* @return OptionsResolver This instance
950+
*
951+
* @throws UndefinedOptionsException If an option is undefined
952+
* @throws AccessException If called from a lazy option or normalizer
953+
*/
954+
public function setPrototype($type, $values, $options = Options::EXTRA)
955+
{
956+
if ($this->locked) {
957+
throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.');
958+
}
959+
960+
$this->resolveUndefined = true;
961+
962+
$this->addAllowedTypesForAll($options, $type, true, Options::NONE);
963+
$this->addAllowedValuesForAll($options, $values, true, Options::NONE);
964+
965+
return $this;
966+
}
967+
968+
/**
969+
* Defines whether undefined options should be resolved.
970+
*
971+
* @param bool $allow Whether to resolve undefined options
972+
*
973+
* @return OptionsResolver This instance
974+
*/
975+
public function allowExtraOptions($allow = true)
976+
{
977+
$this->resolveUndefined = $allow;
978+
979+
return $this;
980+
}
981+
728982
/**
729983
* Removes the option with the given name.
730984
*
@@ -813,7 +1067,7 @@ public function resolve(array $options = array())
8131067
// Make sure that no unknown options are passed
8141068
$diff = array_diff_key($options, $clone->defined);
8151069

816-
if (count($diff) > 0) {
1070+
if (count($diff) > 0 && false === $this->resolveUndefined) {
8171071
ksort($clone->defined);
8181072
ksort($diff);
8191073

@@ -939,10 +1193,21 @@ public function offsetGet($option)
9391193
}
9401194

9411195
// Validate the type of the resolved option
942-
if (isset($this->allowedTypes[$option])) {
1196+
if (isset($this->allowedTypes[$option])
1197+
|| ($this->allowedTypesForAll && false === $this->isNested($option))
1198+
|| $extra = (isset($this->allowedTypes[Options::EXTRA]) && false === $this->isDefined($option))
1199+
) {
9431200
$valid = false;
9441201

945-
foreach ($this->allowedTypes[$option] as $type) {
1202+
if (isset($extra) && $extra && $this->resolveUndefined) {
1203+
$allowedTypes = $this->allowedTypes[Options::EXTRA];
1204+
} else {
1205+
$allowedTypes = isset($this->allowedTypes[$option]) ? $this->allowedTypes[$option] : array();
1206+
}
1207+
1208+
$allowedTypes = array_unique(array_merge($allowedTypes, $this->allowedTypesForAll));
1209+
1210+
foreach ($allowedTypes as $type) {
9461211
$type = isset(self::$typeAliases[$type]) ? self::$typeAliases[$type] : $type;
9471212

9481213
if (function_exists($isFunction = 'is_'.$type)) {
@@ -973,11 +1238,22 @@ public function offsetGet($option)
9731238
}
9741239

9751240
// Validate the value of the resolved option
976-
if (isset($this->allowedValues[$option])) {
1241+
if (isset($this->allowedValues[$option])
1242+
|| ($this->allowedValuesForAll && false === $this->isNested($option))
1243+
|| $extra = (isset($this->allowedValues[Options::EXTRA]) && false === $this->isDefined($option))
1244+
) {
9771245
$success = false;
9781246
$printableAllowedValues = array();
9791247

980-
foreach ($this->allowedValues[$option] as $allowedValue) {
1248+
if (isset($extra) && $extra && $this->resolveUndefined) {
1249+
$allowedValues = $this->allowedTypes[Options::EXTRA];
1250+
} else {
1251+
$allowedValues = isset($this->allowedValues[$option]) ? $this->allowedValues[$option] : array();
1252+
}
1253+
1254+
$allowedValues = array_merge($allowedValues, $this->allowedValuesForAll);
1255+
1256+
foreach ($allowedValues as $allowedValue) {
9811257
if ($allowedValue instanceof \Closure) {
9821258
if ($allowedValue($value)) {
9831259
$success = true;

0 commit comments

Comments
 (0)