[ Index ]

PHP Cross Reference of phpBB-3.3.5-deutsch

title

Body

[close]

/vendor/symfony/config/Definition/ -> PrototypedArrayNode.php (source)

   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 = [];
  31      protected $defaultChildren;
  32      /**
  33       * @var NodeInterface[] An array of the prototypes of the simplified value children
  34       */
  35      private $valuePrototypes = [];
  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       *     [
  57       *         ['id' => 'my_name', 'foo' => 'bar'],
  58       *     ];
  59       *
  60       *  becomes
  61       *
  62       *      [
  63       *          'my_name' => ['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|null 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 = ['defaults'])
 118      {
 119          if (null === $children) {
 120              $this->defaultChildren = ['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() : [];
 136              $defaults = [];
 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 = [];
 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) === ['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, 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       *     [
 339       *         [
 340       *             'name' => 'name001',
 341       *             'value' => 'value001'
 342       *         ]
 343       *     ]
 344       *
 345       * Now, the key is 0 and the child node is:
 346       *
 347       *     [
 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       *     [
 356       *         'name001' => ['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       *     ['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  }


Generated: Mon Oct 4 17:42:11 2021 Cross-referenced by PHPXref 0.7.1