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

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