4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Config\Definition\Builder;
14 use Symfony\Component\Config\Definition\ArrayNode;
15 use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
16 use Symfony\Component\Config\Definition\PrototypedArrayNode;
19 * This class provides a fluent interface for defining an array node.
21 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
23 class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
25 protected $performDeepMerging = true;
26 protected $ignoreExtraKeys = false;
27 protected $removeExtraKeys = true;
28 protected $children = array();
30 protected $atLeastOne = false;
31 protected $allowNewKeys = true;
33 protected $removeKeyItem;
34 protected $addDefaults = false;
35 protected $addDefaultChildren = false;
36 protected $nodeBuilder;
37 protected $normalizeKeys = true;
42 public function __construct($name, NodeParentInterface $parent = null)
44 parent::__construct($name, $parent);
46 $this->nullEquivalent = array();
47 $this->trueEquivalent = array();
53 public function setBuilder(NodeBuilder $builder)
55 $this->nodeBuilder = $builder;
61 public function children()
63 return $this->getNodeBuilder();
67 * Sets a prototype for child nodes.
69 * @param string $type The type of node
71 * @return NodeDefinition
73 public function prototype($type)
75 return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
79 * @return VariableNodeDefinition
81 public function variablePrototype()
83 return $this->prototype('variable');
87 * @return ScalarNodeDefinition
89 public function scalarPrototype()
91 return $this->prototype('scalar');
95 * @return BooleanNodeDefinition
97 public function booleanPrototype()
99 return $this->prototype('boolean');
103 * @return IntegerNodeDefinition
105 public function integerPrototype()
107 return $this->prototype('integer');
111 * @return FloatNodeDefinition
113 public function floatPrototype()
115 return $this->prototype('float');
119 * @return ArrayNodeDefinition
121 public function arrayPrototype()
123 return $this->prototype('array');
127 * @return EnumNodeDefinition
129 public function enumPrototype()
131 return $this->prototype('enum');
135 * Adds the default value if the node is not set in the configuration.
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.
143 public function addDefaultsIfNotSet()
145 $this->addDefaults = true;
151 * Adds children with a default value when none are defined.
153 * This method is applicable to prototype nodes only.
155 * @param int|string|array|null $children The number of children|The child name|The children names to be added
159 public function addDefaultChildrenIfNoneSet($children = null)
161 $this->addDefaultChildren = $children;
167 * Requires the node to have at least one element.
169 * This method is applicable to prototype nodes only.
173 public function requiresAtLeastOneElement()
175 $this->atLeastOne = true;
181 * Disallows adding news keys in a subsequent configuration.
183 * If used all keys have to be defined in the same configuration file.
187 public function disallowNewKeysInSubsequentConfigs()
189 $this->allowNewKeys = false;
195 * Sets a normalization rule for XML configurations.
197 * @param string $singular The key to remap
198 * @param string $plural The plural of the key for irregular plurals
202 public function fixXmlConfig($singular, $plural = null)
204 $this->normalization()->remap($singular, $plural);
210 * Sets the attribute which value is to be used as key.
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
218 * array('id' => 'my_name', 'foo' => 'bar'),
224 * 'my_name' => array('foo' => 'bar'),
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.
230 * This method is applicable to prototype nodes only.
232 * @param string $name The name of the key
233 * @param bool $removeKeyItem Whether or not the key item should be removed
237 public function useAttributeAsKey($name, $removeKeyItem = true)
240 $this->removeKeyItem = $removeKeyItem;
246 * Sets whether the node can be unset.
252 public function canBeUnset($allow = true)
254 $this->merge()->allowUnset($allow);
260 * Adds an "enabled" boolean to enable the current section.
262 * By default, the section is disabled. If any configuration is specified then
263 * the node will be automatically enabled:
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
274 public function canBeEnabled()
277 ->addDefaultsIfNotSet()
278 ->treatFalseLike(array('enabled' => false))
279 ->treatTrueLike(array('enabled' => true))
280 ->treatNullLike(array('enabled' => true))
281 ->beforeNormalization()
283 ->then(function ($v) {
284 $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
290 ->booleanNode('enabled')
298 * Adds an "enabled" boolean to enable the current section.
300 * By default, the section is enabled.
304 public function canBeDisabled()
307 ->addDefaultsIfNotSet()
308 ->treatFalseLike(array('enabled' => false))
309 ->treatTrueLike(array('enabled' => true))
310 ->treatNullLike(array('enabled' => true))
312 ->booleanNode('enabled')
320 * Disables the deep merging of the node.
324 public function performNoDeepMerging()
326 $this->performDeepMerging = false;
332 * Allows extra config keys to be specified under an array without
333 * throwing an exception.
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.
340 * @param bool $remove Whether to remove the extra keys
344 public function ignoreExtraKeys($remove = true)
346 $this->ignoreExtraKeys = true;
347 $this->removeExtraKeys = $remove;
353 * Sets key normalization.
355 * @param bool $bool Whether to enable key normalization
359 public function normalizeKeys($bool)
361 $this->normalizeKeys = (bool) $bool;
369 public function append(NodeDefinition $node)
371 $this->children[$node->name] = $node->setParent($this);
377 * Returns a node builder to be used to add children and prototype.
379 * @return NodeBuilder The node builder
381 protected function getNodeBuilder()
383 if (null === $this->nodeBuilder) {
384 $this->nodeBuilder = new NodeBuilder();
387 return $this->nodeBuilder->setParent($this);
393 protected function createNode()
395 if (null === $this->prototype) {
396 $node = new ArrayNode($this->name, $this->parent);
398 $this->validateConcreteNode($node);
400 $node->setAddIfNotSet($this->addDefaults);
402 foreach ($this->children as $child) {
403 $child->parent = $node;
404 $node->addChild($child->getNode());
407 $node = new PrototypedArrayNode($this->name, $this->parent);
409 $this->validatePrototypeNode($node);
411 if (null !== $this->key) {
412 $node->setKeyAttribute($this->key, $this->removeKeyItem);
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);
419 if (true === $this->atLeastOne) {
420 $node->setMinNumberOfElements(1);
423 if ($this->default) {
424 $node->setDefaultValue($this->defaultValue);
427 if (false !== $this->addDefaultChildren) {
428 $node->setAddChildrenIfNoneSet($this->addDefaultChildren);
429 if ($this->prototype instanceof static && null === $this->prototype->prototype) {
430 $this->prototype->addDefaultsIfNotSet();
434 $this->prototype->parent = $node;
435 $node->setPrototype($this->prototype->getNode());
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);
448 if (null !== $this->normalization) {
449 $node->setNormalizationClosures($this->normalization->before);
450 $node->setXmlRemappings($this->normalization->remappings);
453 if (null !== $this->merge) {
454 $node->setAllowOverwrite($this->merge->allowOverwrite);
455 $node->setAllowFalse($this->merge->allowFalse);
458 if (null !== $this->validation) {
459 $node->setFinalValidationClosures($this->validation->rules);
466 * Validate the configuration of a concrete node.
468 * @throws InvalidDefinitionException
470 protected function validateConcreteNode(ArrayNode $node)
472 $path = $node->getPath();
474 if (null !== $this->key) {
475 throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path));
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);
482 if (true === $this->atLeastOne) {
483 throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path));
486 if ($this->default) {
487 throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path));
490 if (false !== $this->addDefaultChildren) {
491 throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path));
496 * Validate the configuration of a prototype node.
498 * @throws InvalidDefinitionException
500 protected function validatePrototypeNode(PrototypedArrayNode $node)
502 $path = $node->getPath();
504 if ($this->addDefaults) {
505 throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path));
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));
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));
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));