a1fc1fab0ef78fbd866f2ad2f00a942c805f740b
[yaffs-website] / vendor / symfony / config / Definition / Builder / ArrayNodeDefinition.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Config\Definition\Builder;
13
14 use Symfony\Component\Config\Definition\ArrayNode;
15 use Symfony\Component\Config\Definition\PrototypedArrayNode;
16 use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
17
18 /**
19  * This class provides a fluent interface for defining an array node.
20  *
21  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
22  */
23 class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
24 {
25     protected $performDeepMerging = true;
26     protected $ignoreExtraKeys = false;
27     protected $removeExtraKeys = true;
28     protected $children = array();
29     protected $prototype;
30     protected $atLeastOne = false;
31     protected $allowNewKeys = true;
32     protected $key;
33     protected $removeKeyItem;
34     protected $addDefaults = false;
35     protected $addDefaultChildren = false;
36     protected $nodeBuilder;
37     protected $normalizeKeys = true;
38
39     /**
40      * {@inheritdoc}
41      */
42     public function __construct($name, NodeParentInterface $parent = null)
43     {
44         parent::__construct($name, $parent);
45
46         $this->nullEquivalent = array();
47         $this->trueEquivalent = array();
48     }
49
50     /**
51      * Sets a custom children builder.
52      *
53      * @param NodeBuilder $builder A custom NodeBuilder
54      */
55     public function setBuilder(NodeBuilder $builder)
56     {
57         $this->nodeBuilder = $builder;
58     }
59
60     /**
61      * Returns a builder to add children nodes.
62      *
63      * @return NodeBuilder
64      */
65     public function children()
66     {
67         return $this->getNodeBuilder();
68     }
69
70     /**
71      * Sets a prototype for child nodes.
72      *
73      * @param string $type the type of node
74      *
75      * @return NodeDefinition
76      */
77     public function prototype($type)
78     {
79         return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
80     }
81
82     /**
83      * Adds the default value if the node is not set in the configuration.
84      *
85      * This method is applicable to concrete nodes only (not to prototype nodes).
86      * If this function has been called and the node is not set during the finalization
87      * phase, it's default value will be derived from its children default values.
88      *
89      * @return $this
90      */
91     public function addDefaultsIfNotSet()
92     {
93         $this->addDefaults = true;
94
95         return $this;
96     }
97
98     /**
99      * Adds children with a default value when none are defined.
100      *
101      * @param int|string|array|null $children The number of children|The child name|The children names to be added
102      *
103      * This method is applicable to prototype nodes only.
104      *
105      * @return $this
106      */
107     public function addDefaultChildrenIfNoneSet($children = null)
108     {
109         $this->addDefaultChildren = $children;
110
111         return $this;
112     }
113
114     /**
115      * Requires the node to have at least one element.
116      *
117      * This method is applicable to prototype nodes only.
118      *
119      * @return $this
120      */
121     public function requiresAtLeastOneElement()
122     {
123         $this->atLeastOne = true;
124
125         return $this;
126     }
127
128     /**
129      * Disallows adding news keys in a subsequent configuration.
130      *
131      * If used all keys have to be defined in the same configuration file.
132      *
133      * @return $this
134      */
135     public function disallowNewKeysInSubsequentConfigs()
136     {
137         $this->allowNewKeys = false;
138
139         return $this;
140     }
141
142     /**
143      * Sets a normalization rule for XML configurations.
144      *
145      * @param string $singular The key to remap
146      * @param string $plural   The plural of the key for irregular plurals
147      *
148      * @return $this
149      */
150     public function fixXmlConfig($singular, $plural = null)
151     {
152         $this->normalization()->remap($singular, $plural);
153
154         return $this;
155     }
156
157     /**
158      * Sets the attribute which value is to be used as key.
159      *
160      * This is useful when you have an indexed array that should be an
161      * associative array. You can select an item from within the array
162      * to be the key of the particular item. For example, if "id" is the
163      * "key", then:
164      *
165      *     array(
166      *         array('id' => 'my_name', 'foo' => 'bar'),
167      *     );
168      *
169      *   becomes
170      *
171      *     array(
172      *         'my_name' => array('foo' => 'bar'),
173      *     );
174      *
175      * If you'd like "'id' => 'my_name'" to still be present in the resulting
176      * array, then you can set the second argument of this method to false.
177      *
178      * This method is applicable to prototype nodes only.
179      *
180      * @param string $name          The name of the key
181      * @param bool   $removeKeyItem Whether or not the key item should be removed
182      *
183      * @return $this
184      */
185     public function useAttributeAsKey($name, $removeKeyItem = true)
186     {
187         $this->key = $name;
188         $this->removeKeyItem = $removeKeyItem;
189
190         return $this;
191     }
192
193     /**
194      * Sets whether the node can be unset.
195      *
196      * @param bool $allow
197      *
198      * @return $this
199      */
200     public function canBeUnset($allow = true)
201     {
202         $this->merge()->allowUnset($allow);
203
204         return $this;
205     }
206
207     /**
208      * Adds an "enabled" boolean to enable the current section.
209      *
210      * By default, the section is disabled. If any configuration is specified then
211      * the node will be automatically enabled:
212      *
213      * enableableArrayNode: {enabled: true, ...}   # The config is enabled & default values get overridden
214      * enableableArrayNode: ~                      # The config is enabled & use the default values
215      * enableableArrayNode: true                   # The config is enabled & use the default values
216      * enableableArrayNode: {other: value, ...}    # The config is enabled & default values get overridden
217      * enableableArrayNode: {enabled: false, ...}  # The config is disabled
218      * enableableArrayNode: false                  # The config is disabled
219      *
220      * @return $this
221      */
222     public function canBeEnabled()
223     {
224         $this
225             ->addDefaultsIfNotSet()
226             ->treatFalseLike(array('enabled' => false))
227             ->treatTrueLike(array('enabled' => true))
228             ->treatNullLike(array('enabled' => true))
229             ->beforeNormalization()
230                 ->ifArray()
231                 ->then(function ($v) {
232                     $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
233
234                     return $v;
235                 })
236             ->end()
237             ->children()
238                 ->booleanNode('enabled')
239                     ->defaultFalse()
240         ;
241
242         return $this;
243     }
244
245     /**
246      * Adds an "enabled" boolean to enable the current section.
247      *
248      * By default, the section is enabled.
249      *
250      * @return $this
251      */
252     public function canBeDisabled()
253     {
254         $this
255             ->addDefaultsIfNotSet()
256             ->treatFalseLike(array('enabled' => false))
257             ->treatTrueLike(array('enabled' => true))
258             ->treatNullLike(array('enabled' => true))
259             ->children()
260                 ->booleanNode('enabled')
261                     ->defaultTrue()
262         ;
263
264         return $this;
265     }
266
267     /**
268      * Disables the deep merging of the node.
269      *
270      * @return $this
271      */
272     public function performNoDeepMerging()
273     {
274         $this->performDeepMerging = false;
275
276         return $this;
277     }
278
279     /**
280      * Allows extra config keys to be specified under an array without
281      * throwing an exception.
282      *
283      * Those config values are simply ignored and removed from the
284      * resulting array. This should be used only in special cases where
285      * you want to send an entire configuration array through a special
286      * tree that processes only part of the array.
287      *
288      * @param bool $remove Whether to remove the extra keys
289      *
290      * @return $this
291      */
292     public function ignoreExtraKeys($remove = true)
293     {
294         $this->ignoreExtraKeys = true;
295         $this->removeExtraKeys = $remove;
296
297         return $this;
298     }
299
300     /**
301      * Sets key normalization.
302      *
303      * @param bool $bool Whether to enable key normalization
304      *
305      * @return $this
306      */
307     public function normalizeKeys($bool)
308     {
309         $this->normalizeKeys = (bool) $bool;
310
311         return $this;
312     }
313
314     /**
315      * Appends a node definition.
316      *
317      *     $node = new ArrayNodeDefinition()
318      *         ->children()
319      *             ->scalarNode('foo')->end()
320      *             ->scalarNode('baz')->end()
321      *         ->end()
322      *         ->append($this->getBarNodeDefinition())
323      *     ;
324      *
325      * @param NodeDefinition $node A NodeDefinition instance
326      *
327      * @return $this
328      */
329     public function append(NodeDefinition $node)
330     {
331         $this->children[$node->name] = $node->setParent($this);
332
333         return $this;
334     }
335
336     /**
337      * Returns a node builder to be used to add children and prototype.
338      *
339      * @return NodeBuilder The node builder
340      */
341     protected function getNodeBuilder()
342     {
343         if (null === $this->nodeBuilder) {
344             $this->nodeBuilder = new NodeBuilder();
345         }
346
347         return $this->nodeBuilder->setParent($this);
348     }
349
350     /**
351      * {@inheritdoc}
352      */
353     protected function createNode()
354     {
355         if (null === $this->prototype) {
356             $node = new ArrayNode($this->name, $this->parent);
357
358             $this->validateConcreteNode($node);
359
360             $node->setAddIfNotSet($this->addDefaults);
361
362             foreach ($this->children as $child) {
363                 $child->parent = $node;
364                 $node->addChild($child->getNode());
365             }
366         } else {
367             $node = new PrototypedArrayNode($this->name, $this->parent);
368
369             $this->validatePrototypeNode($node);
370
371             if (null !== $this->key) {
372                 $node->setKeyAttribute($this->key, $this->removeKeyItem);
373             }
374
375             if (true === $this->atLeastOne) {
376                 $node->setMinNumberOfElements(1);
377             }
378
379             if ($this->default) {
380                 $node->setDefaultValue($this->defaultValue);
381             }
382
383             if (false !== $this->addDefaultChildren) {
384                 $node->setAddChildrenIfNoneSet($this->addDefaultChildren);
385                 if ($this->prototype instanceof static && null === $this->prototype->prototype) {
386                     $this->prototype->addDefaultsIfNotSet();
387                 }
388             }
389
390             $this->prototype->parent = $node;
391             $node->setPrototype($this->prototype->getNode());
392         }
393
394         $node->setAllowNewKeys($this->allowNewKeys);
395         $node->addEquivalentValue(null, $this->nullEquivalent);
396         $node->addEquivalentValue(true, $this->trueEquivalent);
397         $node->addEquivalentValue(false, $this->falseEquivalent);
398         $node->setPerformDeepMerging($this->performDeepMerging);
399         $node->setRequired($this->required);
400         $node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys);
401         $node->setNormalizeKeys($this->normalizeKeys);
402
403         if (null !== $this->normalization) {
404             $node->setNormalizationClosures($this->normalization->before);
405             $node->setXmlRemappings($this->normalization->remappings);
406         }
407
408         if (null !== $this->merge) {
409             $node->setAllowOverwrite($this->merge->allowOverwrite);
410             $node->setAllowFalse($this->merge->allowFalse);
411         }
412
413         if (null !== $this->validation) {
414             $node->setFinalValidationClosures($this->validation->rules);
415         }
416
417         return $node;
418     }
419
420     /**
421      * Validate the configuration of a concrete node.
422      *
423      * @param ArrayNode $node The related node
424      *
425      * @throws InvalidDefinitionException
426      */
427     protected function validateConcreteNode(ArrayNode $node)
428     {
429         $path = $node->getPath();
430
431         if (null !== $this->key) {
432             throw new InvalidDefinitionException(
433                 sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path)
434             );
435         }
436
437         if (true === $this->atLeastOne) {
438             throw new InvalidDefinitionException(
439                 sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path)
440             );
441         }
442
443         if ($this->default) {
444             throw new InvalidDefinitionException(
445                 sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path)
446             );
447         }
448
449         if (false !== $this->addDefaultChildren) {
450             throw new InvalidDefinitionException(
451                 sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path)
452             );
453         }
454     }
455
456     /**
457      * Validate the configuration of a prototype node.
458      *
459      * @param PrototypedArrayNode $node The related node
460      *
461      * @throws InvalidDefinitionException
462      */
463     protected function validatePrototypeNode(PrototypedArrayNode $node)
464     {
465         $path = $node->getPath();
466
467         if ($this->addDefaults) {
468             throw new InvalidDefinitionException(
469                 sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path)
470             );
471         }
472
473         if (false !== $this->addDefaultChildren) {
474             if ($this->default) {
475                 throw new InvalidDefinitionException(
476                     sprintf('A default value and default children might not be used together at path "%s"', $path)
477                 );
478             }
479
480             if (null !== $this->key && (null === $this->addDefaultChildren || is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
481                 throw new InvalidDefinitionException(
482                     sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path)
483                 );
484             }
485
486             if (null === $this->key && (is_string($this->addDefaultChildren) || is_array($this->addDefaultChildren))) {
487                 throw new InvalidDefinitionException(
488                     sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path)
489                 );
490             }
491         }
492     }
493 }