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
;
14 use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
;
15 use Symfony\Component\Config\Definition\Exception\InvalidTypeException
;
16 use Symfony\Component\Config\Definition\Exception\UnsetKeyException
;
19 * Represents an Array node in the config tree.
21 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
23 class ArrayNode
extends BaseNode
implements PrototypeNodeInterface
25 protected $xmlRemappings = array();
26 protected $children = array();
27 protected $allowFalse = false;
28 protected $allowNewKeys = true;
29 protected $addIfNotSet = false;
30 protected $performDeepMerging = true;
31 protected $ignoreExtraKeys = false;
32 protected $removeExtraKeys = true;
33 protected $normalizeKeys = true;
35 public function setNormalizeKeys($normalizeKeys)
37 $this->normalizeKeys
= (bool) $normalizeKeys;
41 * Normalizes keys between the different configuration formats.
43 * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
44 * After running this method, all keys are normalized to foo_bar.
46 * If you have a mixed key like foo-bar_moo, it will not be altered.
47 * The key will also not be altered if the target key already exists.
51 * @return array The value with normalized keys
53 protected function preNormalize($value)
55 if (!$this->normalizeKeys ||
!is_array($value)) {
59 $normalized = array();
61 foreach ($value as $k => $v) {
62 if (false !== strpos($k, '-') && false === strpos($k, '_') && !array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) {
63 $normalized[$normalizedKey] = $v;
73 * Retrieves the children of this node.
75 * @return array The children
77 public function getChildren()
79 return $this->children
;
83 * Sets the xml remappings that should be performed.
85 * @param array $remappings An array of the form array(array(string, string))
87 public function setXmlRemappings(array $remappings)
89 $this->xmlRemappings
= $remappings;
93 * Gets the xml remappings that should be performed.
95 * @return array $remappings an array of the form array(array(string, string))
97 public function getXmlRemappings()
99 return $this->xmlRemappings
;
103 * Sets whether to add default values for this array if it has not been
104 * defined in any of the configuration files.
106 * @param bool $boolean
108 public function setAddIfNotSet($boolean)
110 $this->addIfNotSet
= (bool) $boolean;
114 * Sets whether false is allowed as value indicating that the array should be unset.
118 public function setAllowFalse($allow)
120 $this->allowFalse
= (bool) $allow;
124 * Sets whether new keys can be defined in subsequent configurations.
128 public function setAllowNewKeys($allow)
130 $this->allowNewKeys
= (bool) $allow;
134 * Sets if deep merging should occur.
136 * @param bool $boolean
138 public function setPerformDeepMerging($boolean)
140 $this->performDeepMerging
= (bool) $boolean;
144 * Whether extra keys should just be ignore without an exception.
146 * @param bool $boolean To allow extra keys
147 * @param bool $remove To remove extra keys
149 public function setIgnoreExtraKeys($boolean, $remove = true)
151 $this->ignoreExtraKeys
= (bool) $boolean;
152 $this->removeExtraKeys
= $this->ignoreExtraKeys
&& $remove;
158 public function setName($name)
166 public function hasDefaultValue()
168 return $this->addIfNotSet
;
174 public function getDefaultValue()
176 if (!$this->hasDefaultValue()) {
177 throw new \
RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
181 foreach ($this->children
as $name => $child) {
182 if ($child->hasDefaultValue()) {
183 $defaults[$name] = $child->getDefaultValue();
193 * @throws \InvalidArgumentException when the child node has no name
194 * @throws \InvalidArgumentException when the child node's name is not unique
196 public function addChild(NodeInterface
$node)
198 $name = $node->getName();
199 if (!strlen($name)) {
200 throw new \
InvalidArgumentException('Child nodes must be named.');
202 if (isset($this->children
[$name])) {
203 throw new \
InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name));
206 $this->children
[$name] = $node;
210 * Finalizes the value of this node.
212 * @param mixed $value
214 * @return mixed The finalised value
216 * @throws UnsetKeyException
217 * @throws InvalidConfigurationException if the node doesn't have enough children
219 protected function finalizeValue($value)
221 if (false === $value) {
222 $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
223 throw new UnsetKeyException($msg);
226 foreach ($this->children
as $name => $child) {
227 if (!array_key_exists($name, $value)) {
228 if ($child->isRequired()) {
229 $msg = sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath());
230 $ex = new InvalidConfigurationException($msg);
231 $ex->setPath($this->getPath());
236 if ($child->hasDefaultValue()) {
237 $value[$name] = $child->getDefaultValue();
244 $value[$name] = $child->finalize($value[$name]);
245 } catch (UnsetKeyException
$e) {
246 unset($value[$name]);
254 * Validates the type of the value.
256 * @param mixed $value
258 * @throws InvalidTypeException
260 protected function validateType($value)
262 if (!is_array($value) && (!$this->allowFalse ||
false !== $value)) {
263 $ex = new InvalidTypeException(sprintf(
264 'Invalid type for path "%s". Expected array, but got %s',
268 if ($hint = $this->getInfo()) {
271 $ex->setPath($this->getPath());
278 * Normalizes the value.
280 * @param mixed $value The value to normalize
282 * @return mixed The normalized value
284 * @throws InvalidConfigurationException
286 protected function normalizeValue($value)
288 if (false === $value) {
292 $value = $this->remapXml($value);
294 $normalized = array();
295 foreach ($value as $name => $val) {
296 if (isset($this->children
[$name])) {
297 $normalized[$name] = $this->children
[$name]->normalize($val);
298 unset($value[$name]);
299 } elseif (!$this->removeExtraKeys
) {
300 $normalized[$name] = $val;
304 // if extra fields are present, throw exception
305 if (count($value) && !$this->ignoreExtraKeys
) {
306 $msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === count($value) ?
'' : 's', implode(', ', array_keys($value)), $this->getPath());
307 $ex = new InvalidConfigurationException($msg);
308 $ex->setPath($this->getPath());
317 * Remaps multiple singular values to a single plural value.
319 * @param array $value The source values
321 * @return array The remapped values
323 protected function remapXml($value)
325 foreach ($this->xmlRemappings
as $transformation) {
326 list($singular, $plural) = $transformation;
328 if (!isset($value[$singular])) {
332 $value[$plural] = Processor
::normalizeConfig($value, $singular, $plural);
333 unset($value[$singular]);
340 * Merges values together.
342 * @param mixed $leftSide The left side to merge
343 * @param mixed $rightSide The right side to merge
345 * @return mixed The merged values
347 * @throws InvalidConfigurationException
348 * @throws \RuntimeException
350 protected function mergeValues($leftSide, $rightSide)
352 if (false === $rightSide) {
353 // if this is still false after the last config has been merged the
354 // finalization pass will take care of removing this key entirely
358 if (false === $leftSide ||
!$this->performDeepMerging
) {
362 foreach ($rightSide as $k => $v) {
364 if (!array_key_exists($k, $leftSide)) {
365 if (!$this->allowNewKeys
) {
366 $ex = new InvalidConfigurationException(sprintf(
367 'You are not allowed to define new elements for path "%s". '
368 .'Please define all elements for this path in one config file. '
369 .'If you are trying to overwrite an element, make sure you redefine it '
370 .'with the same name.',
373 $ex->setPath($this->getPath());
382 if (!isset($this->children
[$k])) {
383 throw new \
RuntimeException('merge() expects a normalized config array.');
386 $leftSide[$k] = $this->children
[$k]->merge($leftSide[$k], $v);