[ Index ] |
PHP Cross Reference of phpBB-3.2.11-deutsch |
[Summary view] [Print] [Text view]
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; 13 14 use Symfony\Component\Config\Definition\Exception\DuplicateKeyException; 15 use Symfony\Component\Config\Definition\Exception\Exception; 16 use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; 17 use Symfony\Component\Config\Definition\Exception\UnsetKeyException; 18 19 /** 20 * Represents a prototyped Array node in the config tree. 21 * 22 * @author Johannes M. Schmitt <schmittjoh@gmail.com> 23 */ 24 class PrototypedArrayNode extends ArrayNode 25 { 26 protected $prototype; 27 protected $keyAttribute; 28 protected $removeKeyAttribute = false; 29 protected $minNumberOfElements = 0; 30 protected $defaultValue = array(); 31 protected $defaultChildren; 32 /** 33 * @var NodeInterface[] An array of the prototypes of the simplified value children 34 */ 35 private $valuePrototypes = array(); 36 37 /** 38 * Sets the minimum number of elements that a prototype based node must 39 * contain. By default this is zero, meaning no elements. 40 * 41 * @param int $number 42 */ 43 public function setMinNumberOfElements($number) 44 { 45 $this->minNumberOfElements = $number; 46 } 47 48 /** 49 * Sets the attribute which value is to be used as key. 50 * 51 * This is useful when you have an indexed array that should be an 52 * associative array. You can select an item from within the array 53 * to be the key of the particular item. For example, if "id" is the 54 * "key", then: 55 * 56 * array( 57 * array('id' => 'my_name', 'foo' => 'bar'), 58 * ); 59 * 60 * becomes 61 * 62 * array( 63 * 'my_name' => array('foo' => 'bar'), 64 * ); 65 * 66 * If you'd like "'id' => 'my_name'" to still be present in the resulting 67 * array, then you can set the second argument of this method to false. 68 * 69 * @param string $attribute The name of the attribute which value is to be used as a key 70 * @param bool $remove Whether or not to remove the key 71 */ 72 public function setKeyAttribute($attribute, $remove = true) 73 { 74 $this->keyAttribute = $attribute; 75 $this->removeKeyAttribute = $remove; 76 } 77 78 /** 79 * Retrieves the name of the attribute which value should be used as key. 80 * 81 * @return string The name of the attribute 82 */ 83 public function getKeyAttribute() 84 { 85 return $this->keyAttribute; 86 } 87 88 /** 89 * Sets the default value of this node. 90 * 91 * @param string $value 92 * 93 * @throws \InvalidArgumentException if the default value is not an array 94 */ 95 public function setDefaultValue($value) 96 { 97 if (!\is_array($value)) { 98 throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.'); 99 } 100 101 $this->defaultValue = $value; 102 } 103 104 /** 105 * {@inheritdoc} 106 */ 107 public function hasDefaultValue() 108 { 109 return true; 110 } 111 112 /** 113 * Adds default children when none are set. 114 * 115 * @param int|string|array|null $children The number of children|The child name|The children names to be added 116 */ 117 public function setAddChildrenIfNoneSet($children = array('defaults')) 118 { 119 if (null === $children) { 120 $this->defaultChildren = array('defaults'); 121 } else { 122 $this->defaultChildren = \is_int($children) && $children > 0 ? range(1, $children) : (array) $children; 123 } 124 } 125 126 /** 127 * {@inheritdoc} 128 * 129 * The default value could be either explicited or derived from the prototype 130 * default value. 131 */ 132 public function getDefaultValue() 133 { 134 if (null !== $this->defaultChildren) { 135 $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array(); 136 $defaults = array(); 137 foreach (array_values($this->defaultChildren) as $i => $name) { 138 $defaults[null === $this->keyAttribute ? $i : $name] = $default; 139 } 140 141 return $defaults; 142 } 143 144 return $this->defaultValue; 145 } 146 147 /** 148 * Sets the node prototype. 149 */ 150 public function setPrototype(PrototypeNodeInterface $node) 151 { 152 $this->prototype = $node; 153 } 154 155 /** 156 * Retrieves the prototype. 157 * 158 * @return PrototypeNodeInterface The prototype 159 */ 160 public function getPrototype() 161 { 162 return $this->prototype; 163 } 164 165 /** 166 * Disable adding concrete children for prototyped nodes. 167 * 168 * @throws Exception 169 */ 170 public function addChild(NodeInterface $node) 171 { 172 throw new Exception('A prototyped array node can not have concrete children.'); 173 } 174 175 /** 176 * Finalizes the value of this node. 177 * 178 * @param mixed $value 179 * 180 * @return mixed The finalized value 181 * 182 * @throws UnsetKeyException 183 * @throws InvalidConfigurationException if the node doesn't have enough children 184 */ 185 protected function finalizeValue($value) 186 { 187 if (false === $value) { 188 throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value))); 189 } 190 191 foreach ($value as $k => $v) { 192 $prototype = $this->getPrototypeForChild($k); 193 try { 194 $value[$k] = $prototype->finalize($v); 195 } catch (UnsetKeyException $e) { 196 unset($value[$k]); 197 } 198 } 199 200 if (\count($value) < $this->minNumberOfElements) { 201 $ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements)); 202 $ex->setPath($this->getPath()); 203 204 throw $ex; 205 } 206 207 return $value; 208 } 209 210 /** 211 * Normalizes the value. 212 * 213 * @param mixed $value The value to normalize 214 * 215 * @return mixed The normalized value 216 * 217 * @throws InvalidConfigurationException 218 * @throws DuplicateKeyException 219 */ 220 protected function normalizeValue($value) 221 { 222 if (false === $value) { 223 return $value; 224 } 225 226 $value = $this->remapXml($value); 227 228 $isAssoc = array_keys($value) !== range(0, \count($value) - 1); 229 $normalized = array(); 230 foreach ($value as $k => $v) { 231 if (null !== $this->keyAttribute && \is_array($v)) { 232 if (!isset($v[$this->keyAttribute]) && \is_int($k) && !$isAssoc) { 233 $ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath())); 234 $ex->setPath($this->getPath()); 235 236 throw $ex; 237 } elseif (isset($v[$this->keyAttribute])) { 238 $k = $v[$this->keyAttribute]; 239 240 // remove the key attribute when required 241 if ($this->removeKeyAttribute) { 242 unset($v[$this->keyAttribute]); 243 } 244 245 // if only "value" is left 246 if (array_keys($v) === array('value')) { 247 $v = $v['value']; 248 if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && array_key_exists('value', $children)) { 249 $valuePrototype = current($this->valuePrototypes) ?: clone $children['value']; 250 $valuePrototype->parent = $this; 251 $originalClosures = $this->prototype->normalizationClosures; 252 if (\is_array($originalClosures)) { 253 $valuePrototypeClosures = $valuePrototype->normalizationClosures; 254 $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures; 255 } 256 $this->valuePrototypes[$k] = $valuePrototype; 257 } 258 } 259 } 260 261 if (array_key_exists($k, $normalized)) { 262 $ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath())); 263 $ex->setPath($this->getPath()); 264 265 throw $ex; 266 } 267 } 268 269 $prototype = $this->getPrototypeForChild($k); 270 if (null !== $this->keyAttribute || $isAssoc) { 271 $normalized[$k] = $prototype->normalize($v); 272 } else { 273 $normalized[] = $prototype->normalize($v); 274 } 275 } 276 277 return $normalized; 278 } 279 280 /** 281 * Merges values together. 282 * 283 * @param mixed $leftSide The left side to merge 284 * @param mixed $rightSide The right side to merge 285 * 286 * @return mixed The merged values 287 * 288 * @throws InvalidConfigurationException 289 * @throws \RuntimeException 290 */ 291 protected function mergeValues($leftSide, $rightSide) 292 { 293 if (false === $rightSide) { 294 // if this is still false after the last config has been merged the 295 // finalization pass will take care of removing this key entirely 296 return false; 297 } 298 299 if (false === $leftSide || !$this->performDeepMerging) { 300 return $rightSide; 301 } 302 303 foreach ($rightSide as $k => $v) { 304 // prototype, and key is irrelevant, so simply append the element 305 if (null === $this->keyAttribute) { 306 $leftSide[] = $v; 307 continue; 308 } 309 310 // no conflict 311 if (!array_key_exists($k, $leftSide)) { 312 if (!$this->allowNewKeys) { 313 $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath())); 314 $ex->setPath($this->getPath()); 315 316 throw $ex; 317 } 318 319 $leftSide[$k] = $v; 320 continue; 321 } 322 323 $prototype = $this->getPrototypeForChild($k); 324 $leftSide[$k] = $prototype->merge($leftSide[$k], $v); 325 } 326 327 return $leftSide; 328 } 329 330 /** 331 * Returns a prototype for the child node that is associated to $key in the value array. 332 * For general child nodes, this will be $this->prototype. 333 * But if $this->removeKeyAttribute is true and there are only two keys in the child node: 334 * one is same as this->keyAttribute and the other is 'value', then the prototype will be different. 335 * 336 * For example, assume $this->keyAttribute is 'name' and the value array is as follows: 337 * 338 * array( 339 * array( 340 * 'name' => 'name001', 341 * 'value' => 'value001' 342 * ) 343 * ) 344 * 345 * Now, the key is 0 and the child node is: 346 * 347 * array( 348 * 'name' => 'name001', 349 * 'value' => 'value001' 350 * ) 351 * 352 * When normalizing the value array, the 'name' element will removed from the child node 353 * and its value becomes the new key of the child node: 354 * 355 * array( 356 * 'name001' => array('value' => 'value001') 357 * ) 358 * 359 * Now only 'value' element is left in the child node which can be further simplified into a string: 360 * 361 * array('name001' => 'value001') 362 * 363 * Now, the key becomes 'name001' and the child node becomes 'value001' and 364 * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance. 365 * 366 * @param string $key The key of the child node 367 * 368 * @return mixed The prototype instance 369 */ 370 private function getPrototypeForChild($key) 371 { 372 $prototype = isset($this->valuePrototypes[$key]) ? $this->valuePrototypes[$key] : $this->prototype; 373 $prototype->setName($key); 374 375 return $prototype; 376 } 377 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:33:01 2020 | Cross-referenced by PHPXref 0.7.1 |