composer package updates
[openemr.git] / vendor / symfony / config / Definition / ArrayNode.php
blob076cf5c6909577e5d85aef0b23b7df6bb94de198
1 <?php
3 /*
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;
18 /**
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;
40 /**
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.
49 * @param mixed $value
51 * @return array The value with normalized keys
53 protected function preNormalize($value)
55 if (!$this->normalizeKeys || !is_array($value)) {
56 return $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;
64 } else {
65 $normalized[$k] = $v;
69 return $normalized;
72 /**
73 * Retrieves the children of this node.
75 * @return array The children
77 public function getChildren()
79 return $this->children;
82 /**
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;
92 /**
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.
116 * @param bool $allow
118 public function setAllowFalse($allow)
120 $this->allowFalse = (bool) $allow;
124 * Sets whether new keys can be defined in subsequent configurations.
126 * @param bool $allow
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;
156 * {@inheritdoc}
158 public function setName($name)
160 $this->name = $name;
164 * {@inheritdoc}
166 public function hasDefaultValue()
168 return $this->addIfNotSet;
172 * {@inheritdoc}
174 public function getDefaultValue()
176 if (!$this->hasDefaultValue()) {
177 throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
180 $defaults = array();
181 foreach ($this->children as $name => $child) {
182 if ($child->hasDefaultValue()) {
183 $defaults[$name] = $child->getDefaultValue();
187 return $defaults;
191 * Adds a child node.
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());
233 throw $ex;
236 if ($child->hasDefaultValue()) {
237 $value[$name] = $child->getDefaultValue();
240 continue;
243 try {
244 $value[$name] = $child->finalize($value[$name]);
245 } catch (UnsetKeyException $e) {
246 unset($value[$name]);
250 return $value;
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',
265 $this->getPath(),
266 gettype($value)
268 if ($hint = $this->getInfo()) {
269 $ex->addHint($hint);
271 $ex->setPath($this->getPath());
273 throw $ex;
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) {
289 return $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());
310 throw $ex;
313 return $normalized;
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])) {
329 continue;
332 $value[$plural] = Processor::normalizeConfig($value, $singular, $plural);
333 unset($value[$singular]);
336 return $value;
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
355 return false;
358 if (false === $leftSide || !$this->performDeepMerging) {
359 return $rightSide;
362 foreach ($rightSide as $k => $v) {
363 // no conflict
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.',
371 $this->getPath()
373 $ex->setPath($this->getPath());
375 throw $ex;
378 $leftSide[$k] = $v;
379 continue;
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);
389 return $leftSide;