Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc2944ac

Browse files
committed
[OptionsResolver] resolve nested options
closes#4833.Allow to resolve nested options. * add `OptionsResolver::nested` * add `OptionsResolver::setNested()` * add `OptionsResolver::isNested()` * add `OptionsResolver::getNested()`
1 parentbb2727a commitc2944ac

File tree

2 files changed

+220
-5
lines changed

2 files changed

+220
-5
lines changed

‎src/Symfony/Component/OptionsResolver/OptionsResolver.php‎

Lines changed: 157 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespaceSymfony\Component\OptionsResolver;
1313

1414
useSymfony\Component\OptionsResolver\Exception\AccessException;
15+
useSymfony\Component\OptionsResolver\Exception\ExceptionInterface;
1516
useSymfony\Component\OptionsResolver\Exception\InvalidOptionsException;
1617
useSymfony\Component\OptionsResolver\Exception\MissingOptionsException;
1718
useSymfony\Component\OptionsResolver\Exception\NoSuchOptionException;
@@ -40,6 +41,13 @@ class OptionsResolver implements Options
4041
*/
4142
private$defaults =array();
4243

44+
/**
45+
* The nested options.
46+
*
47+
* @var OptionsResolver[]
48+
*/
49+
private$nested =array();
50+
4351
/**
4452
* The names of required options.
4553
*
@@ -142,10 +150,23 @@ class OptionsResolver implements Options
142150
* is spread across different locations of your code, such as base and
143151
* sub-classes.
144152
*
153+
* If you set default values of nested options, this method will return the
154+
* nested instance for the same convenience as above.
155+
*
156+
* // Master class
157+
* $options->setNested('connexion', array('port' => '80'));
158+
*
159+
* // Sub class inheriting $options
160+
* $nestedOptions = $options->setDefault('connexion', array(
161+
* 'port' => '443', // overrides default
162+
* ));
163+
*
164+
* $nestedOptions->setRequired('type');
165+
*
145166
* @param string $option The name of the option
146167
* @param mixed $value The default value of the option
147168
*
148-
* @return OptionsResolver This instance
169+
* @return OptionsResolver This instance or the nested instance
149170
*
150171
* @throws AccessException If called from a lazy option or normalizer
151172
*/
@@ -167,7 +188,7 @@ public function setDefault($option, $value)
167188
if (isset($params[0]) &&null !== ($class =$params[0]->getClass()) && Options::class ===$class->name) {
168189
// Initialize the option if no previous value exists
169190
if (!isset($this->defaults[$option])) {
170-
$this->defaults[$option] =null;
191+
$this->defaults[$option] =$this->isNested($option) ?array() :null;
171192
}
172193

173194
// Ignore previous lazy options if the closure has no second parameter
@@ -189,6 +210,18 @@ public function setDefault($option, $value)
189210
// This option is not lazy anymore
190211
unset($this->lazy[$option]);
191212

213+
if ($this->isNested($option)) {
214+
$defaults =isset($this->defaults[$option]) ?$this->defaults[$option] :array();
215+
$this->defaults[$option] =array_replace($defaults,$value);
216+
$this->defined[$option] =true;
217+
// Make sure the nested options are processed
218+
unset($this->resolved[$option]);
219+
220+
// Returning the nested options here is convenient when we need to
221+
// override them from a sub class
222+
return$this->nested[$option];
223+
}
224+
192225
// Yet undefined options can be marked as resolved, because we only need
193226
// to resolve options with lazy closures, normalizers or validation
194227
// rules, none of which can exist for undefined options
@@ -236,6 +269,82 @@ public function hasDefault($option)
236269
returnarray_key_exists($option,$this->defaults);
237270
}
238271

272+
/**
273+
* Defines an option as a new self.
274+
*
275+
* Returns a new OptionsResolver instance to configure nested options.
276+
*
277+
* $nestedOptions = $options->setNested('connexion', array(
278+
* 'host' => 'localhost',
279+
* 'port' => 80,
280+
* );
281+
*
282+
* $nestedOptions->setRequired('user');
283+
* $nestedOptions->setDefault('password', function (Options $nested) {
284+
* return isset($nested['user']) ? '' : null;
285+
* });
286+
* $nestedOptions->setDefined(array('secure'));
287+
* $nestedOptions->setNormalizer('secure', function (Options $nested, $secure) {
288+
* return 443 === $nested['port'] ?: $secure;
289+
* });
290+
* $nestedOptions->setAllowedTypes('port', 'int');
291+
*
292+
* @param string $option The option name
293+
* @param array $defaults The default nested options
294+
*
295+
* @return OptionsResolver The nested options resolver
296+
*
297+
* @throws AccessException If called from a lazy option or normalizer
298+
*/
299+
publicfunctionsetNested($option,array$defaults =array())
300+
{
301+
if ($this->locked) {
302+
thrownewAccessException('Options cannot be made nested from a lazy option or normalizer.');
303+
}
304+
305+
$nestedOptions =newself();
306+
307+
foreach ($defaultsas$name =>$default) {
308+
$nestedOptions->setDefault($name,$default);
309+
}
310+
311+
// Keep a raw copy of defaults until nested options are resolved allowing to
312+
// easily override them, even using lazy definition with {@link setDefault()}
313+
$this->defaults[$option] =$defaults;
314+
$this->defined[$option] =true;
315+
316+
// Make sure the nested options are processed
317+
unset($this->resolved[$option]);
318+
319+
return$this->nested[$option] =$nestedOptions;
320+
}
321+
322+
/**
323+
* Returns whether an option is nested.
324+
*
325+
* An option is nested if it was passed to {@link setNested()}.
326+
*
327+
* @param string $option The name of the option
328+
*
329+
* @return bool Whether the option is nested
330+
*/
331+
publicfunctionisNested($option)
332+
{
333+
returnisset($this->nested[$option]);
334+
}
335+
336+
/**
337+
* Returns the names of all nested options.
338+
*
339+
* @return string[] The names of the nested options
340+
*
341+
* @see isNested()
342+
*/
343+
publicfunctiongetNestedOptions()
344+
{
345+
returnarray_keys($this->nested);
346+
}
347+
239348
/**
240349
* Marks one or more options as required.
241350
*
@@ -443,6 +552,11 @@ public function setAllowedValues($option, $allowedValues)
443552
thrownewAccessException('Allowed values cannot be set from a lazy option or normalizer.');
444553
}
445554

555+
// Not supported for nested options
556+
if ($this->isNested($option)) {
557+
return$this;
558+
}
559+
446560
if (!isset($this->defined[$option])) {
447561
thrownewUndefinedOptionsException(sprintf(
448562
'The option "%s" does not exist. Defined options are: "%s".',
@@ -488,6 +602,11 @@ public function addAllowedValues($option, $allowedValues)
488602
thrownewAccessException('Allowed values cannot be added from a lazy option or normalizer.');
489603
}
490604

605+
// Not supported for nested options
606+
if ($this->isNested($option)) {
607+
return$this;
608+
}
609+
491610
if (!isset($this->defined[$option])) {
492611
thrownewUndefinedOptionsException(sprintf(
493612
'The option "%s" does not exist. Defined options are: "%s".',
@@ -533,6 +652,11 @@ public function setAllowedTypes($option, $allowedTypes)
533652
thrownewAccessException('Allowed types cannot be set from a lazy option or normalizer.');
534653
}
535654

655+
// Not supported for nested options
656+
if ($this->isNested($option)) {
657+
return$this;
658+
}
659+
536660
if (!isset($this->defined[$option])) {
537661
thrownewUndefinedOptionsException(sprintf(
538662
'The option "%s" does not exist. Defined options are: "%s".',
@@ -572,6 +696,11 @@ public function addAllowedTypes($option, $allowedTypes)
572696
thrownewAccessException('Allowed types cannot be added from a lazy option or normalizer.');
573697
}
574698

699+
// Not supported for nested options
700+
if ($this->isNested($option)) {
701+
return$this;
702+
}
703+
575704
if (!isset($this->defined[$option])) {
576705
thrownewUndefinedOptionsException(sprintf(
577706
'The option "%s" does not exist. Defined options are: "%s".',
@@ -610,8 +739,9 @@ public function remove($optionNames)
610739
}
611740

612741
foreach ((array)$optionNamesas$option) {
613-
unset($this->defined[$option],$this->defaults[$option],$this->required[$option],$this->resolved[$option]);
614-
unset($this->lazy[$option],$this->normalizers[$option],$this->allowedTypes[$option],$this->allowedValues[$option]);
742+
unset($this->defined[$option],$this->defaults[$option],$this->nested[$option]);
743+
unset($this->required[$option],$this->resolved[$option],$this->lazy[$option]);
744+
unset($this->normalizers[$option],$this->allowedTypes[$option],$this->allowedValues[$option]);
615745
}
616746

617747
return$this;
@@ -632,6 +762,7 @@ public function clear()
632762

633763
$this->defined =array();
634764
$this->defaults =array();
765+
$this->nested =array();
635766
$this->required =array();
636767
$this->resolved =array();
637768
$this->lazy =array();
@@ -691,7 +822,13 @@ public function resolve(array $options = array())
691822

692823
// Override options set by the user
693824
foreach ($optionsas$option =>$value) {
694-
$clone->defaults[$option] =$value;
825+
if ($clone->isNested($option)) {
826+
$defaults =isset($clone->defaults[$option]) ?$clone->defaults[$option] :array();
827+
$clone->defaults[$option] =array_replace($defaults,$value);
828+
}else {
829+
$clone->defaults[$option] =$value;
830+
}
831+
695832
unset($clone->resolved[$option],$clone->lazy[$option]);
696833
}
697834

@@ -789,6 +926,14 @@ public function offsetGet($option)
789926
// END
790927
}
791928

929+
if ($this->isNested($option)) {
930+
try {
931+
$value =$this->nested[$option]->resolve($value);
932+
}catch (ExceptionInterface$e) {
933+
thrownewInvalidOptionsException(sprintf('The nested options in the option "%s" could not be resolved.',$option),0,$e);
934+
}
935+
}
936+
792937
// Validate the type of the resolved option
793938
if (isset($this->allowedTypes[$option])) {
794939
$valid =false;
@@ -955,6 +1100,13 @@ public function count()
9551100
returncount($this->defaults);
9561101
}
9571102

1103+
publicfunction__clone()
1104+
{
1105+
foreach ($this->nestedas$name =>$options) {
1106+
$this->nested[$name] =clone$options;
1107+
}
1108+
}
1109+
9581110
/**
9591111
* Returns a string representation of the type of the value.
9601112
*

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,53 @@ public function testInvokeEachLazyOptionOnlyOnce()
230230
$this->assertSame(2,$calls);
231231
}
232232

233+
////////////////////////////////////////////////////////////////////////////
234+
// setNested()/isNested()/getNestedOptions()
235+
////////////////////////////////////////////////////////////////////////////
236+
237+
publicfunctiontestSetNestedReturnsNewResolver()
238+
{
239+
$this->assertNotSame($this->resolver,$this->resolver->setNested('foo'));
240+
$this->assertInstanceOf('Symfony\Component\OptionsResolver\OptionsResolver',$this->resolver->setNested('bar'));
241+
}
242+
243+
publicfunctiontestSetNested()
244+
{
245+
$this->resolver->setNested('one',array('un' =>'1'));
246+
$this->resolver->setNested('two',array('deux' =>'2','vingt' =>20));
247+
248+
$this->assertEquals(array(
249+
'one' =>array('un' =>'1'),
250+
'two' =>array('deux' =>'2','vingt' =>20),
251+
),$this->resolver->resolve());
252+
}
253+
254+
/**
255+
* @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
256+
*/
257+
publicfunctiontestFailIfSetNestedFromLazyOption()
258+
{
259+
$this->resolver->setDefault('lazy',function (Options$options) {
260+
$options->setNested('nested',array('number' =>42));
261+
});
262+
263+
$this->resolver->resolve();
264+
}
265+
266+
publicfunctiontestIsNested()
267+
{
268+
$this->assertFalse($this->resolver->isNested('foo'));
269+
$this->resolver->setNested('foo',array('number' =>42));
270+
$this->assertTrue($this->resolver->isNested('foo'));
271+
}
272+
273+
publicfunctiontestIsNestedWithNoValue()
274+
{
275+
$this->assertFalse($this->resolver->isNested('foo'));
276+
$this->resolver->setNested('foo');
277+
$this->assertTrue($this->resolver->isNested('foo'));
278+
}
279+
233280
////////////////////////////////////////////////////////////////////////////
234281
// setRequired()/isRequired()/getRequiredOptions()
235282
////////////////////////////////////////////////////////////////////////////
@@ -1547,4 +1594,20 @@ public function testCountFailsOutsideResolve()
15471594

15481595
count($this->resolver);
15491596
}
1597+
1598+
////////////////////////////////////////////////////////////////////////////
1599+
// Nested options
1600+
////////////////////////////////////////////////////////////////////////////
1601+
1602+
/**
1603+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1604+
* @expectedExceptionMessage The nested options in the option "z" could not be resolved.
1605+
*/
1606+
publicfunctiontestResolveFailsIfNonExistingNestedOption()
1607+
{
1608+
$this->resolver->setNested('z',array('one' =>'1'));
1609+
$this->resolver->setNested('a',array('two' =>'2'));
1610+
1611+
$this->resolver->resolve(array('z' =>array('foo' =>'bar')));
1612+
}
15501613
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp