| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 | <?php/* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */namespace Symfony\Component\Config\Definition\Builder;use Symfony\Component\Config\Definition\ArrayNode;use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;use Symfony\Component\Config\Definition\PrototypedArrayNode;/** * This class provides a fluent interface for defining an array node. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> */class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface{    protected $performDeepMerging = true;    protected $ignoreExtraKeys = false;    protected $removeExtraKeys = true;    protected $children = [];    protected $prototype;    protected $atLeastOne = false;    protected $allowNewKeys = true;    protected $key;    protected $removeKeyItem;    protected $addDefaults = false;    protected $addDefaultChildren = false;    protected $nodeBuilder;    protected $normalizeKeys = true;    /**     * {@inheritdoc}     */    public function __construct(?string $name, ?NodeParentInterface $parent = null)    {        parent::__construct($name, $parent);        $this->nullEquivalent = [];        $this->trueEquivalent = [];    }    /**     * {@inheritdoc}     */    public function setBuilder(NodeBuilder $builder)    {        $this->nodeBuilder = $builder;    }    /**     * {@inheritdoc}     */    public function children()    {        return $this->getNodeBuilder();    }    /**     * Sets a prototype for child nodes.     *     * @return NodeDefinition     */    public function prototype(string $type)    {        return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);    }    /**     * @return VariableNodeDefinition     */    public function variablePrototype()    {        return $this->prototype('variable');    }    /**     * @return ScalarNodeDefinition     */    public function scalarPrototype()    {        return $this->prototype('scalar');    }    /**     * @return BooleanNodeDefinition     */    public function booleanPrototype()    {        return $this->prototype('boolean');    }    /**     * @return IntegerNodeDefinition     */    public function integerPrototype()    {        return $this->prototype('integer');    }    /**     * @return FloatNodeDefinition     */    public function floatPrototype()    {        return $this->prototype('float');    }    /**     * @return ArrayNodeDefinition     */    public function arrayPrototype()    {        return $this->prototype('array');    }    /**     * @return EnumNodeDefinition     */    public function enumPrototype()    {        return $this->prototype('enum');    }    /**     * Adds the default value if the node is not set in the configuration.     *     * This method is applicable to concrete nodes only (not to prototype nodes).     * If this function has been called and the node is not set during the finalization     * phase, it's default value will be derived from its children default values.     *     * @return $this     */    public function addDefaultsIfNotSet()    {        $this->addDefaults = true;        return $this;    }    /**     * Adds children with a default value when none are defined.     *     * This method is applicable to prototype nodes only.     *     * @param int|string|array|null $children The number of children|The child name|The children names to be added     *     * @return $this     */    public function addDefaultChildrenIfNoneSet($children = null)    {        $this->addDefaultChildren = $children;        return $this;    }    /**     * Requires the node to have at least one element.     *     * This method is applicable to prototype nodes only.     *     * @return $this     */    public function requiresAtLeastOneElement()    {        $this->atLeastOne = true;        return $this;    }    /**     * Disallows adding news keys in a subsequent configuration.     *     * If used all keys have to be defined in the same configuration file.     *     * @return $this     */    public function disallowNewKeysInSubsequentConfigs()    {        $this->allowNewKeys = false;        return $this;    }    /**     * Sets a normalization rule for XML configurations.     *     * @param string      $singular The key to remap     * @param string|null $plural   The plural of the key for irregular plurals     *     * @return $this     */    public function fixXmlConfig(string $singular, ?string $plural = null)    {        $this->normalization()->remap($singular, $plural);        return $this;    }    /**     * Sets the attribute which value is to be used as key.     *     * This is useful when you have an indexed array that should be an     * associative array. You can select an item from within the array     * to be the key of the particular item. For example, if "id" is the     * "key", then:     *     *     [     *         ['id' => 'my_name', 'foo' => 'bar'],     *     ];     *     *   becomes     *     *     [     *         'my_name' => ['foo' => 'bar'],     *     ];     *     * If you'd like "'id' => 'my_name'" to still be present in the resulting     * array, then you can set the second argument of this method to false.     *     * This method is applicable to prototype nodes only.     *     * @param string $name          The name of the key     * @param bool   $removeKeyItem Whether or not the key item should be removed     *     * @return $this     */    public function useAttributeAsKey(string $name, bool $removeKeyItem = true)    {        $this->key = $name;        $this->removeKeyItem = $removeKeyItem;        return $this;    }    /**     * Sets whether the node can be unset.     *     * @return $this     */    public function canBeUnset(bool $allow = true)    {        $this->merge()->allowUnset($allow);        return $this;    }    /**     * Adds an "enabled" boolean to enable the current section.     *     * By default, the section is disabled. If any configuration is specified then     * the node will be automatically enabled:     *     * enableableArrayNode: {enabled: true, ...}   # The config is enabled & default values get overridden     * enableableArrayNode: ~                      # The config is enabled & use the default values     * enableableArrayNode: true                   # The config is enabled & use the default values     * enableableArrayNode: {other: value, ...}    # The config is enabled & default values get overridden     * enableableArrayNode: {enabled: false, ...}  # The config is disabled     * enableableArrayNode: false                  # The config is disabled     *     * @return $this     */    public function canBeEnabled()    {        $this            ->addDefaultsIfNotSet()            ->treatFalseLike(['enabled' => false])            ->treatTrueLike(['enabled' => true])            ->treatNullLike(['enabled' => true])            ->beforeNormalization()                ->ifArray()                ->then(function (array $v) {                    $v['enabled'] = $v['enabled'] ?? true;                    return $v;                })            ->end()            ->children()                ->booleanNode('enabled')                    ->defaultFalse()        ;        return $this;    }    /**     * Adds an "enabled" boolean to enable the current section.     *     * By default, the section is enabled.     *     * @return $this     */    public function canBeDisabled()    {        $this            ->addDefaultsIfNotSet()            ->treatFalseLike(['enabled' => false])            ->treatTrueLike(['enabled' => true])            ->treatNullLike(['enabled' => true])            ->children()                ->booleanNode('enabled')                    ->defaultTrue()        ;        return $this;    }    /**     * Disables the deep merging of the node.     *     * @return $this     */    public function performNoDeepMerging()    {        $this->performDeepMerging = false;        return $this;    }    /**     * Allows extra config keys to be specified under an array without     * throwing an exception.     *     * Those config values are ignored and removed from the resulting     * array. This should be used only in special cases where you want     * to send an entire configuration array through a special tree that     * processes only part of the array.     *     * @param bool $remove Whether to remove the extra keys     *     * @return $this     */    public function ignoreExtraKeys(bool $remove = true)    {        $this->ignoreExtraKeys = true;        $this->removeExtraKeys = $remove;        return $this;    }    /**     * Sets whether to enable key normalization.     *     * @return $this     */    public function normalizeKeys(bool $bool)    {        $this->normalizeKeys = $bool;        return $this;    }    /**     * {@inheritdoc}     */    public function append(NodeDefinition $node)    {        $this->children[$node->name] = $node->setParent($this);        return $this;    }    /**     * Returns a node builder to be used to add children and prototype.     *     * @return NodeBuilder     */    protected function getNodeBuilder()    {        if (null === $this->nodeBuilder) {            $this->nodeBuilder = new NodeBuilder();        }        return $this->nodeBuilder->setParent($this);    }    /**     * {@inheritdoc}     */    protected function createNode()    {        if (null === $this->prototype) {            $node = new ArrayNode($this->name, $this->parent, $this->pathSeparator);            $this->validateConcreteNode($node);            $node->setAddIfNotSet($this->addDefaults);            foreach ($this->children as $child) {                $child->parent = $node;                $node->addChild($child->getNode());            }        } else {            $node = new PrototypedArrayNode($this->name, $this->parent, $this->pathSeparator);            $this->validatePrototypeNode($node);            if (null !== $this->key) {                $node->setKeyAttribute($this->key, $this->removeKeyItem);            }            if (true === $this->atLeastOne || false === $this->allowEmptyValue) {                $node->setMinNumberOfElements(1);            }            if ($this->default) {                if (!\is_array($this->defaultValue)) {                    throw new \InvalidArgumentException(sprintf('%s: the default value of an array node has to be an array.', $node->getPath()));                }                $node->setDefaultValue($this->defaultValue);            }            if (false !== $this->addDefaultChildren) {                $node->setAddChildrenIfNoneSet($this->addDefaultChildren);                if ($this->prototype instanceof static && null === $this->prototype->prototype) {                    $this->prototype->addDefaultsIfNotSet();                }            }            $this->prototype->parent = $node;            $node->setPrototype($this->prototype->getNode());        }        $node->setAllowNewKeys($this->allowNewKeys);        $node->addEquivalentValue(null, $this->nullEquivalent);        $node->addEquivalentValue(true, $this->trueEquivalent);        $node->addEquivalentValue(false, $this->falseEquivalent);        $node->setPerformDeepMerging($this->performDeepMerging);        $node->setRequired($this->required);        $node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys);        $node->setNormalizeKeys($this->normalizeKeys);        if ($this->deprecation) {            $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']);        }        if (null !== $this->normalization) {            $node->setNormalizationClosures($this->normalization->before);            $node->setXmlRemappings($this->normalization->remappings);        }        if (null !== $this->merge) {            $node->setAllowOverwrite($this->merge->allowOverwrite);            $node->setAllowFalse($this->merge->allowFalse);        }        if (null !== $this->validation) {            $node->setFinalValidationClosures($this->validation->rules);        }        return $node;    }    /**     * Validate the configuration of a concrete node.     *     * @throws InvalidDefinitionException     */    protected function validateConcreteNode(ArrayNode $node)    {        $path = $node->getPath();        if (null !== $this->key) {            throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s".', $path));        }        if (false === $this->allowEmptyValue) {            throw new InvalidDefinitionException(sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s".', $path));        }        if (true === $this->atLeastOne) {            throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s".', $path));        }        if ($this->default) {            throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s".', $path));        }        if (false !== $this->addDefaultChildren) {            throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s".', $path));        }    }    /**     * Validate the configuration of a prototype node.     *     * @throws InvalidDefinitionException     */    protected function validatePrototypeNode(PrototypedArrayNode $node)    {        $path = $node->getPath();        if ($this->addDefaults) {            throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s".', $path));        }        if (false !== $this->addDefaultChildren) {            if ($this->default) {                throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s".', $path));            }            if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {                throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s".', $path));            }            if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) {                throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s".', $path));            }        }    }    /**     * @return NodeDefinition[]     */    public function getChildNodeDefinitions()    {        return $this->children;    }    /**     * Finds a node defined by the given $nodePath.     *     * @param string $nodePath The path of the node to find. e.g "doctrine.orm.mappings"     */    public function find(string $nodePath): NodeDefinition    {        $firstPathSegment = (false === $pathSeparatorPos = strpos($nodePath, $this->pathSeparator))            ? $nodePath            : substr($nodePath, 0, $pathSeparatorPos);        if (null === $node = ($this->children[$firstPathSegment] ?? null)) {            throw new \RuntimeException(sprintf('Node with name "%s" does not exist in the current node "%s".', $firstPathSegment, $this->name));        }        if (false === $pathSeparatorPos) {            return $node;        }        return $node->find(substr($nodePath, $pathSeparatorPos + \strlen($this->pathSeparator)));    }}
 |