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 The normalized array value
*/
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 The normalized value
*/
abstract protected function normalizeValue($value);
/**
* Merges two values together.
*
* @param mixed $leftSide
* @param mixed $rightSide
*
* @return mixed The merged value
*/
abstract protected function mergeValues($leftSide, $rightSide);
/**
* Finalizes a value.
*
* @param mixed $value The value to finalize
*
* @return mixed The finalized value
*/
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) {