vendor/symfony/config/Definition/BaseNode.php line 411

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;
  11. use Symfony\Component\Config\Definition\Exception\Exception;
  12. use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
  13. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  14. use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
  15. use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
  16. /**
  17.  * The base node class.
  18.  *
  19.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  20.  */
  21. abstract class BaseNode implements NodeInterface
  22. {
  23.     const DEFAULT_PATH_SEPARATOR '.';
  24.     private static $placeholderUniquePrefixes = [];
  25.     private static $placeholders = [];
  26.     protected $name;
  27.     protected $parent;
  28.     protected $normalizationClosures = [];
  29.     protected $finalValidationClosures = [];
  30.     protected $allowOverwrite true;
  31.     protected $required false;
  32.     protected $deprecationMessage null;
  33.     protected $equivalentValues = [];
  34.     protected $attributes = [];
  35.     protected $pathSeparator;
  36.     private $handlingPlaceholder;
  37.     /**
  38.      * @throws \InvalidArgumentException if the name contains a period
  39.      */
  40.     public function __construct(?string $nameNodeInterface $parent nullstring $pathSeparator self::DEFAULT_PATH_SEPARATOR)
  41.     {
  42.         if (false !== strpos($name = (string) $name$pathSeparator)) {
  43.             throw new \InvalidArgumentException('The name must not contain ".'.$pathSeparator.'".');
  44.         }
  45.         $this->name $name;
  46.         $this->parent $parent;
  47.         $this->pathSeparator $pathSeparator;
  48.     }
  49.     /**
  50.      * Register possible (dummy) values for a dynamic placeholder value.
  51.      *
  52.      * Matching configuration values will be processed with a provided value, one by one. After a provided value is
  53.      * successfully processed the configuration value is returned as is, thus preserving the placeholder.
  54.      *
  55.      * @internal
  56.      */
  57.     public static function setPlaceholder(string $placeholder, array $values): void
  58.     {
  59.         if (!$values) {
  60.             throw new \InvalidArgumentException('At least one value must be provided.');
  61.         }
  62.         self::$placeholders[$placeholder] = $values;
  63.     }
  64.     /**
  65.      * Adds a common prefix for dynamic placeholder values.
  66.      *
  67.      * Matching configuration values will be skipped from being processed and are returned as is, thus preserving the
  68.      * placeholder. An exact match provided by {@see setPlaceholder()} might take precedence.
  69.      *
  70.      * @internal
  71.      */
  72.     public static function setPlaceholderUniquePrefix(string $prefix): void
  73.     {
  74.         self::$placeholderUniquePrefixes[] = $prefix;
  75.     }
  76.     /**
  77.      * Resets all current placeholders available.
  78.      *
  79.      * @internal
  80.      */
  81.     public static function resetPlaceholders(): void
  82.     {
  83.         self::$placeholderUniquePrefixes = [];
  84.         self::$placeholders = [];
  85.     }
  86.     public function setAttribute(string $key$value)
  87.     {
  88.         $this->attributes[$key] = $value;
  89.     }
  90.     /**
  91.      * @return mixed
  92.      */
  93.     public function getAttribute(string $key$default null)
  94.     {
  95.         return isset($this->attributes[$key]) ? $this->attributes[$key] : $default;
  96.     }
  97.     /**
  98.      * @return bool
  99.      */
  100.     public function hasAttribute(string $key)
  101.     {
  102.         return isset($this->attributes[$key]);
  103.     }
  104.     /**
  105.      * @return array
  106.      */
  107.     public function getAttributes()
  108.     {
  109.         return $this->attributes;
  110.     }
  111.     public function setAttributes(array $attributes)
  112.     {
  113.         $this->attributes $attributes;
  114.     }
  115.     public function removeAttribute(string $key)
  116.     {
  117.         unset($this->attributes[$key]);
  118.     }
  119.     /**
  120.      * Sets an info message.
  121.      */
  122.     public function setInfo(string $info)
  123.     {
  124.         $this->setAttribute('info'$info);
  125.     }
  126.     /**
  127.      * Returns info message.
  128.      *
  129.      * @return string|null The info text
  130.      */
  131.     public function getInfo()
  132.     {
  133.         return $this->getAttribute('info');
  134.     }
  135.     /**
  136.      * Sets the example configuration for this node.
  137.      *
  138.      * @param string|array $example
  139.      */
  140.     public function setExample($example)
  141.     {
  142.         $this->setAttribute('example'$example);
  143.     }
  144.     /**
  145.      * Retrieves the example configuration for this node.
  146.      *
  147.      * @return string|array|null The example
  148.      */
  149.     public function getExample()
  150.     {
  151.         return $this->getAttribute('example');
  152.     }
  153.     /**
  154.      * Adds an equivalent value.
  155.      *
  156.      * @param mixed $originalValue
  157.      * @param mixed $equivalentValue
  158.      */
  159.     public function addEquivalentValue($originalValue$equivalentValue)
  160.     {
  161.         $this->equivalentValues[] = [$originalValue$equivalentValue];
  162.     }
  163.     /**
  164.      * Set this node as required.
  165.      *
  166.      * @param bool $boolean Required node
  167.      */
  168.     public function setRequired(bool $boolean)
  169.     {
  170.         $this->required $boolean;
  171.     }
  172.     /**
  173.      * Sets this node as deprecated.
  174.      *
  175.      * You can use %node% and %path% placeholders in your message to display,
  176.      * respectively, the node name and its complete path.
  177.      */
  178.     public function setDeprecated(?string $message)
  179.     {
  180.         $this->deprecationMessage $message;
  181.     }
  182.     /**
  183.      * Sets if this node can be overridden.
  184.      */
  185.     public function setAllowOverwrite(bool $allow)
  186.     {
  187.         $this->allowOverwrite $allow;
  188.     }
  189.     /**
  190.      * Sets the closures used for normalization.
  191.      *
  192.      * @param \Closure[] $closures An array of Closures used for normalization
  193.      */
  194.     public function setNormalizationClosures(array $closures)
  195.     {
  196.         $this->normalizationClosures $closures;
  197.     }
  198.     /**
  199.      * Sets the closures used for final validation.
  200.      *
  201.      * @param \Closure[] $closures An array of Closures used for final validation
  202.      */
  203.     public function setFinalValidationClosures(array $closures)
  204.     {
  205.         $this->finalValidationClosures $closures;
  206.     }
  207.     /**
  208.      * {@inheritdoc}
  209.      */
  210.     public function isRequired()
  211.     {
  212.         return $this->required;
  213.     }
  214.     /**
  215.      * Checks if this node is deprecated.
  216.      *
  217.      * @return bool
  218.      */
  219.     public function isDeprecated()
  220.     {
  221.         return null !== $this->deprecationMessage;
  222.     }
  223.     /**
  224.      * Returns the deprecated message.
  225.      *
  226.      * @param string $node the configuration node name
  227.      * @param string $path the path of the node
  228.      *
  229.      * @return string
  230.      */
  231.     public function getDeprecationMessage(string $nodestring $path)
  232.     {
  233.         return strtr($this->deprecationMessage, ['%node%' => $node'%path%' => $path]);
  234.     }
  235.     /**
  236.      * {@inheritdoc}
  237.      */
  238.     public function getName()
  239.     {
  240.         return $this->name;
  241.     }
  242.     /**
  243.      * {@inheritdoc}
  244.      */
  245.     public function getPath()
  246.     {
  247.         if (null !== $this->parent) {
  248.             return $this->parent->getPath().$this->pathSeparator.$this->name;
  249.         }
  250.         return $this->name;
  251.     }
  252.     /**
  253.      * {@inheritdoc}
  254.      */
  255.     final public function merge($leftSide$rightSide)
  256.     {
  257.         if (!$this->allowOverwrite) {
  258.             throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.'$this->getPath()));
  259.         }
  260.         if ($leftSide !== $leftPlaceholders self::resolvePlaceholderValue($leftSide)) {
  261.             foreach ($leftPlaceholders as $leftPlaceholder) {
  262.                 $this->handlingPlaceholder $leftSide;
  263.                 try {
  264.                     $this->merge($leftPlaceholder$rightSide);
  265.                 } finally {
  266.                     $this->handlingPlaceholder null;
  267.                 }
  268.             }
  269.             return $rightSide;
  270.         }
  271.         if ($rightSide !== $rightPlaceholders self::resolvePlaceholderValue($rightSide)) {
  272.             foreach ($rightPlaceholders as $rightPlaceholder) {
  273.                 $this->handlingPlaceholder $rightSide;
  274.                 try {
  275.                     $this->merge($leftSide$rightPlaceholder);
  276.                 } finally {
  277.                     $this->handlingPlaceholder null;
  278.                 }
  279.             }
  280.             return $rightSide;
  281.         }
  282.         $this->doValidateType($leftSide);
  283.         $this->doValidateType($rightSide);
  284.         return $this->mergeValues($leftSide$rightSide);
  285.     }
  286.     /**
  287.      * {@inheritdoc}
  288.      */
  289.     final public function normalize($value)
  290.     {
  291.         $value $this->preNormalize($value);
  292.         // run custom normalization closures
  293.         foreach ($this->normalizationClosures as $closure) {
  294.             $value $closure($value);
  295.         }
  296.         // resolve placeholder value
  297.         if ($value !== $placeholders self::resolvePlaceholderValue($value)) {
  298.             foreach ($placeholders as $placeholder) {
  299.                 $this->handlingPlaceholder $value;
  300.                 try {
  301.                     $this->normalize($placeholder);
  302.                 } finally {
  303.                     $this->handlingPlaceholder null;
  304.                 }
  305.             }
  306.             return $value;
  307.         }
  308.         // replace value with their equivalent
  309.         foreach ($this->equivalentValues as $data) {
  310.             if ($data[0] === $value) {
  311.                 $value $data[1];
  312.             }
  313.         }
  314.         // validate type
  315.         $this->doValidateType($value);
  316.         // normalize value
  317.         return $this->normalizeValue($value);
  318.     }
  319.     /**
  320.      * Normalizes the value before any other normalization is applied.
  321.      *
  322.      * @param mixed $value
  323.      *
  324.      * @return mixed The normalized array value
  325.      */
  326.     protected function preNormalize($value)
  327.     {
  328.         return $value;
  329.     }
  330.     /**
  331.      * Returns parent node for this node.
  332.      *
  333.      * @return NodeInterface|null
  334.      */
  335.     public function getParent()
  336.     {
  337.         return $this->parent;
  338.     }
  339.     /**
  340.      * {@inheritdoc}
  341.      */
  342.     final public function finalize($value)
  343.     {
  344.         if ($value !== $placeholders self::resolvePlaceholderValue($value)) {
  345.             foreach ($placeholders as $placeholder) {
  346.                 $this->handlingPlaceholder $value;
  347.                 try {
  348.                     $this->finalize($placeholder);
  349.                 } finally {
  350.                     $this->handlingPlaceholder null;
  351.                 }
  352.             }
  353.             return $value;
  354.         }
  355.         $this->doValidateType($value);
  356.         $value $this->finalizeValue($value);
  357.         // Perform validation on the final value if a closure has been set.
  358.         // The closure is also allowed to return another value.
  359.         foreach ($this->finalValidationClosures as $closure) {
  360.             try {
  361.                 $value $closure($value);
  362.             } catch (Exception $e) {
  363.                 if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) {
  364.                     continue;
  365.                 }
  366.                 throw $e;
  367.             } catch (\Exception $e) {
  368.                 throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": '$this->getPath()).$e->getMessage(), $e->getCode(), $e);
  369.             }
  370.         }
  371.         return $value;
  372.     }
  373.     /**
  374.      * Validates the type of a Node.
  375.      *
  376.      * @param mixed $value The value to validate
  377.      *
  378.      * @throws InvalidTypeException when the value is invalid
  379.      */
  380.     abstract protected function validateType($value);
  381.     /**
  382.      * Normalizes the value.
  383.      *
  384.      * @param mixed $value The value to normalize
  385.      *
  386.      * @return mixed The normalized value
  387.      */
  388.     abstract protected function normalizeValue($value);
  389.     /**
  390.      * Merges two values together.
  391.      *
  392.      * @param mixed $leftSide
  393.      * @param mixed $rightSide
  394.      *
  395.      * @return mixed The merged value
  396.      */
  397.     abstract protected function mergeValues($leftSide$rightSide);
  398.     /**
  399.      * Finalizes a value.
  400.      *
  401.      * @param mixed $value The value to finalize
  402.      *
  403.      * @return mixed The finalized value
  404.      */
  405.     abstract protected function finalizeValue($value);
  406.     /**
  407.      * Tests if placeholder values are allowed for this node.
  408.      */
  409.     protected function allowPlaceholders(): bool
  410.     {
  411.         return true;
  412.     }
  413.     /**
  414.      * Tests if a placeholder is being handled currently.
  415.      */
  416.     protected function isHandlingPlaceholder(): bool
  417.     {
  418.         return null !== $this->handlingPlaceholder;
  419.     }
  420.     /**
  421.      * Gets allowed dynamic types for this node.
  422.      */
  423.     protected function getValidPlaceholderTypes(): array
  424.     {
  425.         return [];
  426.     }
  427.     private static function resolvePlaceholderValue($value)
  428.     {
  429.         if (\is_string($value)) {
  430.             if (isset(self::$placeholders[$value])) {
  431.                 return self::$placeholders[$value];
  432.             }
  433.             foreach (self::$placeholderUniquePrefixes as $placeholderUniquePrefix) {
  434.                 if (=== strpos($value$placeholderUniquePrefix)) {
  435.                     return [];
  436.                 }
  437.             }
  438.         }
  439.         return $value;
  440.     }
  441.     private function doValidateType($value): void
  442.     {
  443.         if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) {
  444.             $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath()));
  445.             $e->setPath($this->getPath());
  446.             throw $e;
  447.         }
  448.         if (null === $this->handlingPlaceholder || null === $value) {
  449.             $this->validateType($value);
  450.             return;
  451.         }
  452.         $knownTypes array_keys(self::$placeholders[$this->handlingPlaceholder]);
  453.         $validTypes $this->getValidPlaceholderTypes();
  454.         if ($validTypes && array_diff($knownTypes$validTypes)) {
  455.             $e = new InvalidTypeException(sprintf(
  456.                 'Invalid type for path "%s". Expected %s, but got %s.',
  457.                 $this->getPath(),
  458.                 === \count($validTypes) ? '"'.reset($validTypes).'"' 'one of "'.implode('", "'$validTypes).'"',
  459.                 === \count($knownTypes) ? '"'.reset($knownTypes).'"' 'one of "'.implode('", "'$knownTypes).'"'
  460.             ));
  461.             if ($hint $this->getInfo()) {
  462.                 $e->addHint($hint);
  463.             }
  464.             $e->setPath($this->getPath());
  465.             throw $e;
  466.         }
  467.         $this->validateType($value);
  468.     }
  469. }