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

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