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