vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php line 42

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Config\Definition\Builder;
  11. use Symfony\Component\Config\Definition\ArrayNode;
  12. use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
  13. use Symfony\Component\Config\Definition\PrototypedArrayNode;
  14. /**
  15.  * This class provides a fluent interface for defining an array node.
  16.  *
  17.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  18.  */
  19. class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
  20. {
  21.     protected $performDeepMerging true;
  22.     protected $ignoreExtraKeys false;
  23.     protected $removeExtraKeys true;
  24.     protected $children = [];
  25.     protected $prototype;
  26.     protected $atLeastOne false;
  27.     protected $allowNewKeys true;
  28.     protected $key;
  29.     protected $removeKeyItem;
  30.     protected $addDefaults false;
  31.     protected $addDefaultChildren false;
  32.     protected $nodeBuilder;
  33.     protected $normalizeKeys true;
  34.     /**
  35.      * {@inheritdoc}
  36.      */
  37.     public function __construct(?string $nameNodeParentInterface $parent null)
  38.     {
  39.         parent::__construct($name$parent);
  40.         $this->nullEquivalent = [];
  41.         $this->trueEquivalent = [];
  42.     }
  43.     /**
  44.      * {@inheritdoc}
  45.      */
  46.     public function setBuilder(NodeBuilder $builder)
  47.     {
  48.         $this->nodeBuilder $builder;
  49.     }
  50.     /**
  51.      * {@inheritdoc}
  52.      */
  53.     public function children()
  54.     {
  55.         return $this->getNodeBuilder();
  56.     }
  57.     /**
  58.      * Sets a prototype for child nodes.
  59.      *
  60.      * @return NodeDefinition
  61.      */
  62.     public function prototype(string $type)
  63.     {
  64.         return $this->prototype $this->getNodeBuilder()->node(null$type)->setParent($this);
  65.     }
  66.     /**
  67.      * @return VariableNodeDefinition
  68.      */
  69.     public function variablePrototype()
  70.     {
  71.         return $this->prototype('variable');
  72.     }
  73.     /**
  74.      * @return ScalarNodeDefinition
  75.      */
  76.     public function scalarPrototype()
  77.     {
  78.         return $this->prototype('scalar');
  79.     }
  80.     /**
  81.      * @return BooleanNodeDefinition
  82.      */
  83.     public function booleanPrototype()
  84.     {
  85.         return $this->prototype('boolean');
  86.     }
  87.     /**
  88.      * @return IntegerNodeDefinition
  89.      */
  90.     public function integerPrototype()
  91.     {
  92.         return $this->prototype('integer');
  93.     }
  94.     /**
  95.      * @return FloatNodeDefinition
  96.      */
  97.     public function floatPrototype()
  98.     {
  99.         return $this->prototype('float');
  100.     }
  101.     /**
  102.      * @return ArrayNodeDefinition
  103.      */
  104.     public function arrayPrototype()
  105.     {
  106.         return $this->prototype('array');
  107.     }
  108.     /**
  109.      * @return EnumNodeDefinition
  110.      */
  111.     public function enumPrototype()
  112.     {
  113.         return $this->prototype('enum');
  114.     }
  115.     /**
  116.      * Adds the default value if the node is not set in the configuration.
  117.      *
  118.      * This method is applicable to concrete nodes only (not to prototype nodes).
  119.      * If this function has been called and the node is not set during the finalization
  120.      * phase, it's default value will be derived from its children default values.
  121.      *
  122.      * @return $this
  123.      */
  124.     public function addDefaultsIfNotSet()
  125.     {
  126.         $this->addDefaults true;
  127.         return $this;
  128.     }
  129.     /**
  130.      * Adds children with a default value when none are defined.
  131.      *
  132.      * This method is applicable to prototype nodes only.
  133.      *
  134.      * @param int|string|array|null $children The number of children|The child name|The children names to be added
  135.      *
  136.      * @return $this
  137.      */
  138.     public function addDefaultChildrenIfNoneSet($children null)
  139.     {
  140.         $this->addDefaultChildren $children;
  141.         return $this;
  142.     }
  143.     /**
  144.      * Requires the node to have at least one element.
  145.      *
  146.      * This method is applicable to prototype nodes only.
  147.      *
  148.      * @return $this
  149.      */
  150.     public function requiresAtLeastOneElement()
  151.     {
  152.         $this->atLeastOne true;
  153.         return $this;
  154.     }
  155.     /**
  156.      * Disallows adding news keys in a subsequent configuration.
  157.      *
  158.      * If used all keys have to be defined in the same configuration file.
  159.      *
  160.      * @return $this
  161.      */
  162.     public function disallowNewKeysInSubsequentConfigs()
  163.     {
  164.         $this->allowNewKeys false;
  165.         return $this;
  166.     }
  167.     /**
  168.      * Sets a normalization rule for XML configurations.
  169.      *
  170.      * @param string      $singular The key to remap
  171.      * @param string|null $plural   The plural of the key for irregular plurals
  172.      *
  173.      * @return $this
  174.      */
  175.     public function fixXmlConfig(string $singularstring $plural null)
  176.     {
  177.         $this->normalization()->remap($singular$plural);
  178.         return $this;
  179.     }
  180.     /**
  181.      * Sets the attribute which value is to be used as key.
  182.      *
  183.      * This is useful when you have an indexed array that should be an
  184.      * associative array. You can select an item from within the array
  185.      * to be the key of the particular item. For example, if "id" is the
  186.      * "key", then:
  187.      *
  188.      *     [
  189.      *         ['id' => 'my_name', 'foo' => 'bar'],
  190.      *     ];
  191.      *
  192.      *   becomes
  193.      *
  194.      *     [
  195.      *         'my_name' => ['foo' => 'bar'],
  196.      *     ];
  197.      *
  198.      * If you'd like "'id' => 'my_name'" to still be present in the resulting
  199.      * array, then you can set the second argument of this method to false.
  200.      *
  201.      * This method is applicable to prototype nodes only.
  202.      *
  203.      * @param string $name          The name of the key
  204.      * @param bool   $removeKeyItem Whether or not the key item should be removed
  205.      *
  206.      * @return $this
  207.      */
  208.     public function useAttributeAsKey(string $namebool $removeKeyItem true)
  209.     {
  210.         $this->key $name;
  211.         $this->removeKeyItem $removeKeyItem;
  212.         return $this;
  213.     }
  214.     /**
  215.      * Sets whether the node can be unset.
  216.      *
  217.      * @return $this
  218.      */
  219.     public function canBeUnset(bool $allow true)
  220.     {
  221.         $this->merge()->allowUnset($allow);
  222.         return $this;
  223.     }
  224.     /**
  225.      * Adds an "enabled" boolean to enable the current section.
  226.      *
  227.      * By default, the section is disabled. If any configuration is specified then
  228.      * the node will be automatically enabled:
  229.      *
  230.      * enableableArrayNode: {enabled: true, ...}   # The config is enabled & default values get overridden
  231.      * enableableArrayNode: ~                      # The config is enabled & use the default values
  232.      * enableableArrayNode: true                   # The config is enabled & use the default values
  233.      * enableableArrayNode: {other: value, ...}    # The config is enabled & default values get overridden
  234.      * enableableArrayNode: {enabled: false, ...}  # The config is disabled
  235.      * enableableArrayNode: false                  # The config is disabled
  236.      *
  237.      * @return $this
  238.      */
  239.     public function canBeEnabled()
  240.     {
  241.         $this
  242.             ->addDefaultsIfNotSet()
  243.             ->treatFalseLike(['enabled' => false])
  244.             ->treatTrueLike(['enabled' => true])
  245.             ->treatNullLike(['enabled' => true])
  246.             ->beforeNormalization()
  247.                 ->ifArray()
  248.                 ->then(function (array $v) {
  249.                     $v['enabled'] = $v['enabled'] ?? true;
  250.                     return $v;
  251.                 })
  252.             ->end()
  253.             ->children()
  254.                 ->booleanNode('enabled')
  255.                     ->defaultFalse()
  256.         ;
  257.         return $this;
  258.     }
  259.     /**
  260.      * Adds an "enabled" boolean to enable the current section.
  261.      *
  262.      * By default, the section is enabled.
  263.      *
  264.      * @return $this
  265.      */
  266.     public function canBeDisabled()
  267.     {
  268.         $this
  269.             ->addDefaultsIfNotSet()
  270.             ->treatFalseLike(['enabled' => false])
  271.             ->treatTrueLike(['enabled' => true])
  272.             ->treatNullLike(['enabled' => true])
  273.             ->children()
  274.                 ->booleanNode('enabled')
  275.                     ->defaultTrue()
  276.         ;
  277.         return $this;
  278.     }
  279.     /**
  280.      * Disables the deep merging of the node.
  281.      *
  282.      * @return $this
  283.      */
  284.     public function performNoDeepMerging()
  285.     {
  286.         $this->performDeepMerging false;
  287.         return $this;
  288.     }
  289.     /**
  290.      * Allows extra config keys to be specified under an array without
  291.      * throwing an exception.
  292.      *
  293.      * Those config values are ignored and removed from the resulting
  294.      * array. This should be used only in special cases where you want
  295.      * to send an entire configuration array through a special tree that
  296.      * processes only part of the array.
  297.      *
  298.      * @param bool $remove Whether to remove the extra keys
  299.      *
  300.      * @return $this
  301.      */
  302.     public function ignoreExtraKeys(bool $remove true)
  303.     {
  304.         $this->ignoreExtraKeys true;
  305.         $this->removeExtraKeys $remove;
  306.         return $this;
  307.     }
  308.     /**
  309.      * Sets whether to enable key normalization.
  310.      *
  311.      * @return $this
  312.      */
  313.     public function normalizeKeys(bool $bool)
  314.     {
  315.         $this->normalizeKeys $bool;
  316.         return $this;
  317.     }
  318.     /**
  319.      * {@inheritdoc}
  320.      */
  321.     public function append(NodeDefinition $node)
  322.     {
  323.         $this->children[$node->name] = $node->setParent($this);
  324.         return $this;
  325.     }
  326.     /**
  327.      * Returns a node builder to be used to add children and prototype.
  328.      *
  329.      * @return NodeBuilder
  330.      */
  331.     protected function getNodeBuilder()
  332.     {
  333.         if (null === $this->nodeBuilder) {
  334.             $this->nodeBuilder = new NodeBuilder();
  335.         }
  336.         return $this->nodeBuilder->setParent($this);
  337.     }
  338.     /**
  339.      * {@inheritdoc}
  340.      */
  341.     protected function createNode()
  342.     {
  343.         if (null === $this->prototype) {
  344.             $node = new ArrayNode($this->name$this->parent$this->pathSeparator);
  345.             $this->validateConcreteNode($node);
  346.             $node->setAddIfNotSet($this->addDefaults);
  347.             foreach ($this->children as $child) {
  348.                 $child->parent $node;
  349.                 $node->addChild($child->getNode());
  350.             }
  351.         } else {
  352.             $node = new PrototypedArrayNode($this->name$this->parent$this->pathSeparator);
  353.             $this->validatePrototypeNode($node);
  354.             if (null !== $this->key) {
  355.                 $node->setKeyAttribute($this->key$this->removeKeyItem);
  356.             }
  357.             if (true === $this->atLeastOne || false === $this->allowEmptyValue) {
  358.                 $node->setMinNumberOfElements(1);
  359.             }
  360.             if ($this->default) {
  361.                 if (!\is_array($this->defaultValue)) {
  362.                     throw new \InvalidArgumentException(sprintf('%s: the default value of an array node has to be an array.'$node->getPath()));
  363.                 }
  364.                 $node->setDefaultValue($this->defaultValue);
  365.             }
  366.             if (false !== $this->addDefaultChildren) {
  367.                 $node->setAddChildrenIfNoneSet($this->addDefaultChildren);
  368.                 if ($this->prototype instanceof static && null === $this->prototype->prototype) {
  369.                     $this->prototype->addDefaultsIfNotSet();
  370.                 }
  371.             }
  372.             $this->prototype->parent $node;
  373.             $node->setPrototype($this->prototype->getNode());
  374.         }
  375.         $node->setAllowNewKeys($this->allowNewKeys);
  376.         $node->addEquivalentValue(null$this->nullEquivalent);
  377.         $node->addEquivalentValue(true$this->trueEquivalent);
  378.         $node->addEquivalentValue(false$this->falseEquivalent);
  379.         $node->setPerformDeepMerging($this->performDeepMerging);
  380.         $node->setRequired($this->required);
  381.         $node->setIgnoreExtraKeys($this->ignoreExtraKeys$this->removeExtraKeys);
  382.         $node->setNormalizeKeys($this->normalizeKeys);
  383.         if ($this->deprecation) {
  384.             $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']);
  385.         }
  386.         if (null !== $this->normalization) {
  387.             $node->setNormalizationClosures($this->normalization->before);
  388.             $node->setXmlRemappings($this->normalization->remappings);
  389.         }
  390.         if (null !== $this->merge) {
  391.             $node->setAllowOverwrite($this->merge->allowOverwrite);
  392.             $node->setAllowFalse($this->merge->allowFalse);
  393.         }
  394.         if (null !== $this->validation) {
  395.             $node->setFinalValidationClosures($this->validation->rules);
  396.         }
  397.         return $node;
  398.     }
  399.     /**
  400.      * Validate the configuration of a concrete node.
  401.      *
  402.      * @throws InvalidDefinitionException
  403.      */
  404.     protected function validateConcreteNode(ArrayNode $node)
  405.     {
  406.         $path $node->getPath();
  407.         if (null !== $this->key) {
  408.             throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s".'$path));
  409.         }
  410.         if (false === $this->allowEmptyValue) {
  411.             throw new InvalidDefinitionException(sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s".'$path));
  412.         }
  413.         if (true === $this->atLeastOne) {
  414.             throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s".'$path));
  415.         }
  416.         if ($this->default) {
  417.             throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s".'$path));
  418.         }
  419.         if (false !== $this->addDefaultChildren) {
  420.             throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s".'$path));
  421.         }
  422.     }
  423.     /**
  424.      * Validate the configuration of a prototype node.
  425.      *
  426.      * @throws InvalidDefinitionException
  427.      */
  428.     protected function validatePrototypeNode(PrototypedArrayNode $node)
  429.     {
  430.         $path $node->getPath();
  431.         if ($this->addDefaults) {
  432.             throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s".'$path));
  433.         }
  434.         if (false !== $this->addDefaultChildren) {
  435.             if ($this->default) {
  436.                 throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s".'$path));
  437.             }
  438.             if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren 0)) {
  439.                 throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s".'$path));
  440.             }
  441.             if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) {
  442.                 throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s".'$path));
  443.             }
  444.         }
  445.     }
  446.     /**
  447.      * @return NodeDefinition[]
  448.      */
  449.     public function getChildNodeDefinitions()
  450.     {
  451.         return $this->children;
  452.     }
  453.     /**
  454.      * Finds a node defined by the given $nodePath.
  455.      *
  456.      * @param string $nodePath The path of the node to find. e.g "doctrine.orm.mappings"
  457.      */
  458.     public function find(string $nodePath): NodeDefinition
  459.     {
  460.         $firstPathSegment = (false === $pathSeparatorPos strpos($nodePath$this->pathSeparator))
  461.             ? $nodePath
  462.             substr($nodePath0$pathSeparatorPos);
  463.         if (null === $node = ($this->children[$firstPathSegment] ?? null)) {
  464.             throw new \RuntimeException(sprintf('Node with name "%s" does not exist in the current node "%s".'$firstPathSegment$this->name));
  465.         }
  466.         if (false === $pathSeparatorPos) {
  467.             return $node;
  468.         }
  469.         return $node->find(substr($nodePath$pathSeparatorPos \strlen($this->pathSeparator)));
  470.     }
  471. }