- <?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;
- use Symfony\Component\Config\Definition\Exception\Exception;
- use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
- use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
- use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
- use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
- /**
-  * The base node class.
-  *
-  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
-  */
- abstract class BaseNode implements NodeInterface
- {
-     public const DEFAULT_PATH_SEPARATOR = '.';
-     private static $placeholderUniquePrefixes = [];
-     private static $placeholders = [];
-     protected $name;
-     protected $parent;
-     protected $normalizationClosures = [];
-     protected $finalValidationClosures = [];
-     protected $allowOverwrite = true;
-     protected $required = false;
-     protected $deprecation = [];
-     protected $equivalentValues = [];
-     protected $attributes = [];
-     protected $pathSeparator;
-     private $handlingPlaceholder;
-     /**
-      * @throws \InvalidArgumentException if the name contains a period
-      */
-     public function __construct(?string $name, NodeInterface $parent = null, string $pathSeparator = self::DEFAULT_PATH_SEPARATOR)
-     {
-         if (str_contains($name = (string) $name, $pathSeparator)) {
-             throw new \InvalidArgumentException('The name must not contain ".'.$pathSeparator.'".');
-         }
-         $this->name = $name;
-         $this->parent = $parent;
-         $this->pathSeparator = $pathSeparator;
-     }
-     /**
-      * Register possible (dummy) values for a dynamic placeholder value.
-      *
-      * Matching configuration values will be processed with a provided value, one by one. After a provided value is
-      * successfully processed the configuration value is returned as is, thus preserving the placeholder.
-      *
-      * @internal
-      */
-     public static function setPlaceholder(string $placeholder, array $values): void
-     {
-         if (!$values) {
-             throw new \InvalidArgumentException('At least one value must be provided.');
-         }
-         self::$placeholders[$placeholder] = $values;
-     }
-     /**
-      * Adds a common prefix for dynamic placeholder values.
-      *
-      * Matching configuration values will be skipped from being processed and are returned as is, thus preserving the
-      * placeholder. An exact match provided by {@see setPlaceholder()} might take precedence.
-      *
-      * @internal
-      */
-     public static function setPlaceholderUniquePrefix(string $prefix): void
-     {
-         self::$placeholderUniquePrefixes[] = $prefix;
-     }
-     /**
-      * Resets all current placeholders available.
-      *
-      * @internal
-      */
-     public static function resetPlaceholders(): void
-     {
-         self::$placeholderUniquePrefixes = [];
-         self::$placeholders = [];
-     }
-     public function setAttribute(string $key, $value)
-     {
-         $this->attributes[$key] = $value;
-     }
-     /**
-      * @return mixed
-      */
-     public function getAttribute(string $key, $default = null)
-     {
-         return $this->attributes[$key] ?? $default;
-     }
-     /**
-      * @return bool
-      */
-     public function hasAttribute(string $key)
-     {
-         return isset($this->attributes[$key]);
-     }
-     /**
-      * @return array
-      */
-     public function getAttributes()
-     {
-         return $this->attributes;
-     }
-     public function setAttributes(array $attributes)
-     {
-         $this->attributes = $attributes;
-     }
-     public function removeAttribute(string $key)
-     {
-         unset($this->attributes[$key]);
-     }
-     /**
-      * Sets an info message.
-      */
-     public function setInfo(string $info)
-     {
-         $this->setAttribute('info', $info);
-     }
-     /**
-      * Returns info message.
-      *
-      * @return string|null
-      */
-     public function getInfo()
-     {
-         return $this->getAttribute('info');
-     }
-     /**
-      * Sets the example configuration for this node.
-      *
-      * @param string|array $example
-      */
-     public function setExample($example)
-     {
-         $this->setAttribute('example', $example);
-     }
-     /**
-      * Retrieves the example configuration for this node.
-      *
-      * @return string|array|null
-      */
-     public function getExample()
-     {
-         return $this->getAttribute('example');
-     }
-     /**
-      * Adds an equivalent value.
-      *
-      * @param mixed $originalValue
-      * @param mixed $equivalentValue
-      */
-     public function addEquivalentValue($originalValue, $equivalentValue)
-     {
-         $this->equivalentValues[] = [$originalValue, $equivalentValue];
-     }
-     /**
-      * Set this node as required.
-      */
-     public function setRequired(bool $boolean)
-     {
-         $this->required = $boolean;
-     }
-     /**
-      * Sets this node as deprecated.
-      *
-      * @param string $package The name of the composer package that is triggering the deprecation
-      * @param string $version The version of the package that introduced the deprecation
-      * @param string $message the deprecation message to use
-      *
-      * You can use %node% and %path% placeholders in your message to display,
-      * respectively, the node name and its complete path
-      */
-     public function setDeprecated(?string $package/* , string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.' */)
-     {
-         $args = \func_get_args();
-         if (\func_num_args() < 2) {
-             trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
-             if (!isset($args[0])) {
-                 trigger_deprecation('symfony/config', '5.1', 'Passing a null message to un-deprecate a node is deprecated.');
-                 $this->deprecation = [];
-                 return;
-             }
-             $message = (string) $args[0];
-             $package = $version = '';
-         } else {
-             $package = (string) $args[0];
-             $version = (string) $args[1];
-             $message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.');
-         }
-         $this->deprecation = [
-             'package' => $package,
-             'version' => $version,
-             'message' => $message,
-         ];
-     }
-     /**
-      * Sets if this node can be overridden.
-      */
-     public function setAllowOverwrite(bool $allow)
-     {
-         $this->allowOverwrite = $allow;
-     }
-     /**
-      * Sets the closures used for normalization.
-      *
-      * @param \Closure[] $closures An array of Closures used for normalization
-      */
-     public function setNormalizationClosures(array $closures)
-     {
-         $this->normalizationClosures = $closures;
-     }
-     /**
-      * Sets the closures used for final validation.
-      *
-      * @param \Closure[] $closures An array of Closures used for final validation
-      */
-     public function setFinalValidationClosures(array $closures)
-     {
-         $this->finalValidationClosures = $closures;
-     }
-     /**
-      * {@inheritdoc}
-      */
-     public function isRequired()
-     {
-         return $this->required;
-     }
-     /**
-      * Checks if this node is deprecated.
-      *
-      * @return bool
-      */
-     public function isDeprecated()
-     {
-         return (bool) $this->deprecation;
-     }
-     /**
-      * Returns the deprecated message.
-      *
-      * @param string $node the configuration node name
-      * @param string $path the path of the node
-      *
-      * @return string
-      *
-      * @deprecated since Symfony 5.1, use "getDeprecation()" instead.
-      */
-     public function getDeprecationMessage(string $node, string $path)
-     {
-         trigger_deprecation('symfony/config', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__);
-         return $this->getDeprecation($node, $path)['message'];
-     }
-     /**
-      * @param string $node The configuration node name
-      * @param string $path The path of the node
-      */
-     public function getDeprecation(string $node, string $path): array
-     {
-         return [
-             'package' => $this->deprecation['package'] ?? '',
-             'version' => $this->deprecation['version'] ?? '',
-             'message' => strtr($this->deprecation['message'] ?? '', ['%node%' => $node, '%path%' => $path]),
-         ];
-     }
-     /**
-      * {@inheritdoc}
-      */
-     public function getName()
-     {
-         return $this->name;
-     }
-     /**
-      * {@inheritdoc}
-      */
-     public function getPath()
-     {
-         if (null !== $this->parent) {
-             return $this->parent->getPath().$this->pathSeparator.$this->name;
-         }
-         return $this->name;
-     }
-     /**
-      * {@inheritdoc}
-      */
-     final public function merge($leftSide, $rightSide)
-     {
-         if (!$this->allowOverwrite) {
-             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()));
-         }
-         if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) {
-             foreach ($leftPlaceholders as $leftPlaceholder) {
-                 $this->handlingPlaceholder = $leftSide;
-                 try {
-                     $this->merge($leftPlaceholder, $rightSide);
-                 } finally {
-                     $this->handlingPlaceholder = null;
-                 }
-             }
-             return $rightSide;
-         }
-         if ($rightSide !== $rightPlaceholders = self::resolvePlaceholderValue($rightSide)) {
-             foreach ($rightPlaceholders as $rightPlaceholder) {
-                 $this->handlingPlaceholder = $rightSide;
-                 try {
-                     $this->merge($leftSide, $rightPlaceholder);
-                 } finally {
-                     $this->handlingPlaceholder = null;
-                 }
-             }
-             return $rightSide;
-         }
-         $this->doValidateType($leftSide);
-         $this->doValidateType($rightSide);
-         return $this->mergeValues($leftSide, $rightSide);
-     }
-     /**
-      * {@inheritdoc}
-      */
-     final public function normalize($value)
-     {
-         $value = $this->preNormalize($value);
-         // run custom normalization closures
-         foreach ($this->normalizationClosures as $closure) {
-             $value = $closure($value);
-         }
-         // resolve placeholder value
-         if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
-             foreach ($placeholders as $placeholder) {
-                 $this->handlingPlaceholder = $value;
-                 try {
-                     $this->normalize($placeholder);
-                 } finally {
-                     $this->handlingPlaceholder = null;
-                 }
-             }
-             return $value;
-         }
-         // replace value with their equivalent
-         foreach ($this->equivalentValues as $data) {
-             if ($data[0] === $value) {
-                 $value = $data[1];
-             }
-         }
-         // validate type
-         $this->doValidateType($value);
-         // normalize value
-         return $this->normalizeValue($value);
-     }
-     /**
-      * Normalizes the value before any other normalization is applied.
-      *
-      * @param mixed $value
-      *
-      * @return mixed
-      */
-     protected function preNormalize($value)
-     {
-         return $value;
-     }
-     /**
-      * Returns parent node for this node.
-      *
-      * @return NodeInterface|null
-      */
-     public function getParent()
-     {
-         return $this->parent;
-     }
-     /**
-      * {@inheritdoc}
-      */
-     final public function finalize($value)
-     {
-         if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
-             foreach ($placeholders as $placeholder) {
-                 $this->handlingPlaceholder = $value;
-                 try {
-                     $this->finalize($placeholder);
-                 } finally {
-                     $this->handlingPlaceholder = null;
-                 }
-             }
-             return $value;
-         }
-         $this->doValidateType($value);
-         $value = $this->finalizeValue($value);
-         // Perform validation on the final value if a closure has been set.
-         // The closure is also allowed to return another value.
-         foreach ($this->finalValidationClosures as $closure) {
-             try {
-                 $value = $closure($value);
-             } catch (Exception $e) {
-                 if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) {
-                     continue;
-                 }
-                 throw $e;
-             } catch (\Exception $e) {
-                 throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": ', $this->getPath()).$e->getMessage(), $e->getCode(), $e);
-             }
-         }
-         return $value;
-     }
-     /**
-      * Validates the type of a Node.
-      *
-      * @param mixed $value The value to validate
-      *
-      * @throws InvalidTypeException when the value is invalid
-      */
-     abstract protected function validateType($value);
-     /**
-      * Normalizes the value.
-      *
-      * @param mixed $value The value to normalize
-      *
-      * @return mixed
-      */
-     abstract protected function normalizeValue($value);
-     /**
-      * Merges two values together.
-      *
-      * @param mixed $leftSide
-      * @param mixed $rightSide
-      *
-      * @return mixed
-      */
-     abstract protected function mergeValues($leftSide, $rightSide);
-     /**
-      * Finalizes a value.
-      *
-      * @param mixed $value The value to finalize
-      *
-      * @return mixed
-      */
-     abstract protected function finalizeValue($value);
-     /**
-      * Tests if placeholder values are allowed for this node.
-      */
-     protected function allowPlaceholders(): bool
-     {
-         return true;
-     }
-     /**
-      * Tests if a placeholder is being handled currently.
-      */
-     protected function isHandlingPlaceholder(): bool
-     {
-         return null !== $this->handlingPlaceholder;
-     }
-     /**
-      * Gets allowed dynamic types for this node.
-      */
-     protected function getValidPlaceholderTypes(): array
-     {
-         return [];
-     }
-     private static function resolvePlaceholderValue($value)
-     {
-         if (\is_string($value)) {
-             if (isset(self::$placeholders[$value])) {
-                 return self::$placeholders[$value];
-             }
-             foreach (self::$placeholderUniquePrefixes as $placeholderUniquePrefix) {
-                 if (str_starts_with($value, $placeholderUniquePrefix)) {
-                     return [];
-                 }
-             }
-         }
-         return $value;
-     }
-     private function doValidateType($value): void
-     {
-         if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) {
-             $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath()));
-             $e->setPath($this->getPath());
-             throw $e;
-         }
-         if (null === $this->handlingPlaceholder || null === $value) {
-             $this->validateType($value);
-             return;
-         }
-         $knownTypes = array_keys(self::$placeholders[$this->handlingPlaceholder]);
-         $validTypes = $this->getValidPlaceholderTypes();
-         if ($validTypes && array_diff($knownTypes, $validTypes)) {
-             $e = new InvalidTypeException(sprintf(
-                 'Invalid type for path "%s". Expected %s, but got %s.',
-                 $this->getPath(),
-                 1 === \count($validTypes) ? '"'.reset($validTypes).'"' : 'one of "'.implode('", "', $validTypes).'"',
-                 1 === \count($knownTypes) ? '"'.reset($knownTypes).'"' : 'one of "'.implode('", "', $knownTypes).'"'
-             ));
-             if ($hint = $this->getInfo()) {
-                 $e->addHint($hint);
-             }
-             $e->setPath($this->getPath());
-             throw $e;
-         }
-         $this->validateType($value);
-     }
- }
-