Updates to vendors etc

This commit is contained in:
Chris Hunt
2025-07-11 15:57:48 +01:00
parent d972cbcd0a
commit 8fb6438254
8043 changed files with 248005 additions and 189479 deletions

View File

@@ -1,10 +1,17 @@
CHANGELOG
=========
7.3
---
* Support union type in `OptionResolver::setAllowedTypes()` method
* Add `OptionsResolver::setOptions()` and `OptionConfigurator::options()` methods
* Deprecate defining nested options via `setDefault()`, use `setOptions()` instead
6.4
---
* Improve message with full path on invalid type in nested option
* Improve message with full path on invalid type in nested option
6.3
---

View File

@@ -29,7 +29,7 @@ class OptionsResolverIntrospector
$this->get = \Closure::bind(function ($property, $option, $message) {
/** @var OptionsResolver $this */
if (!$this->isDefined($option)) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist.', $option));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist.', $option));
}
if (!\array_key_exists($option, $this->{$property})) {
@@ -45,7 +45,7 @@ class OptionsResolverIntrospector
*/
public function getDefault(string $option): mixed
{
return ($this->get)('defaults', $option, sprintf('No default value was set for the "%s" option.', $option));
return ($this->get)('defaults', $option, \sprintf('No default value was set for the "%s" option.', $option));
}
/**
@@ -55,7 +55,7 @@ class OptionsResolverIntrospector
*/
public function getLazyClosures(string $option): array
{
return ($this->get)('lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option));
return ($this->get)('lazy', $option, \sprintf('No lazy closures were set for the "%s" option.', $option));
}
/**
@@ -65,7 +65,7 @@ class OptionsResolverIntrospector
*/
public function getAllowedTypes(string $option): array
{
return ($this->get)('allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option));
return ($this->get)('allowedTypes', $option, \sprintf('No allowed types were set for the "%s" option.', $option));
}
/**
@@ -75,7 +75,7 @@ class OptionsResolverIntrospector
*/
public function getAllowedValues(string $option): array
{
return ($this->get)('allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option));
return ($this->get)('allowedValues', $option, \sprintf('No allowed values were set for the "%s" option.', $option));
}
/**
@@ -91,7 +91,7 @@ class OptionsResolverIntrospector
*/
public function getNormalizers(string $option): array
{
return ($this->get)('normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option));
return ($this->get)('normalizers', $option, \sprintf('No normalizer was set for the "%s" option.', $option));
}
/**
@@ -99,6 +99,16 @@ class OptionsResolverIntrospector
*/
public function getDeprecation(string $option): array
{
return ($this->get)('deprecated', $option, sprintf('No deprecation was set for the "%s" option.', $option));
return ($this->get)('deprecated', $option, \sprintf('No deprecation was set for the "%s" option.', $option));
}
/**
* @return \Closure[]
*
* @throws NoConfigurationException when no nested option is configured
*/
public function getNestedOptions(string $option): array
{
return ($this->get)('nested', $option, \sprintf('No nested option was set for the "%s" option.', $option));
}
}

View File

@@ -15,13 +15,10 @@ use Symfony\Component\OptionsResolver\Exception\AccessException;
final class OptionConfigurator
{
private string $name;
private OptionsResolver $resolver;
public function __construct(string $name, OptionsResolver $resolver)
{
$this->name = $name;
$this->resolver = $resolver;
public function __construct(
private string $name,
private OptionsResolver $resolver,
) {
$this->resolver->setDefined($name);
}
@@ -146,4 +143,18 @@ final class OptionConfigurator
return $this;
}
/**
* Defines nested options.
*
* @param \Closure(OptionsResolver $resolver, Options $parent): void $nested
*
* @return $this
*/
public function options(\Closure $nested): static
{
$this->resolver->setOptions($this->name, $nested);
return $this;
}
}

View File

@@ -64,6 +64,13 @@ class OptionsResolver implements Options
*/
private array $nested = [];
/**
* BC layer. Remove in Symfony 8.0.
*
* @var array<string, true>
*/
private array $deprecatedNestedOptions = [];
/**
* The names of required options.
*/
@@ -178,20 +185,6 @@ class OptionsResolver implements Options
* is spread across different locations of your code, such as base and
* sub-classes.
*
* If you want to define nested options, you can pass a closure with the
* following signature:
*
* $options->setDefault('database', function (OptionsResolver $resolver) {
* $resolver->setDefined(['dbname', 'host', 'port', 'user', 'pass']);
* }
*
* To get access to the parent options, add a second argument to the closure's
* signature:
*
* function (OptionsResolver $resolver, Options $parent) {
* // 'default' === $parent['connection']
* }
*
* @return $this
*
* @throws AccessException If called from a lazy option or normalizer
@@ -226,13 +219,22 @@ class OptionsResolver implements Options
$this->lazy[$option][] = $value;
$this->defined[$option] = true;
// Make sure the option is processed and is not nested anymore
unset($this->resolved[$option], $this->nested[$option]);
// Make sure the option is processed
unset($this->resolved[$option]);
// BC layer. Remove in Symfony 8.0.
if (isset($this->deprecatedNestedOptions[$option])) {
unset($this->nested[$option]);
}
return $this;
}
if (isset($params[0]) && null !== ($type = $params[0]->getType()) && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) {
// Remove in Symfony 8.0.
if (isset($params[0]) && ($type = $params[0]->getType()) instanceof \ReflectionNamedType && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) {
trigger_deprecation('symfony/options-resolver', '7.3', 'Defining nested options via "%s()" is deprecated and will be removed in Symfony 8.0, use "setOptions()" method instead.', __METHOD__);
$this->deprecatedNestedOptions[$option] = true;
// Store closure for later evaluation
$this->nested[$option][] = $value;
$this->defaults[$option] = [];
@@ -245,8 +247,13 @@ class OptionsResolver implements Options
}
}
// This option is not lazy nor nested anymore
unset($this->lazy[$option], $this->nested[$option]);
// This option is not lazy anymore
unset($this->lazy[$option]);
// BC layer. Remove in Symfony 8.0.
if (isset($this->deprecatedNestedOptions[$option])) {
unset($this->nested[$option]);
}
// Yet undefined options can be marked as resolved, because we only need
// to resolve options with lazy closures, normalizers or validation
@@ -403,6 +410,30 @@ class OptionsResolver implements Options
return array_keys($this->defined);
}
/**
* Defines nested options.
*
* @param \Closure(self $resolver, Options $parent): void $nested
*
* @return $this
*/
public function setOptions(string $option, \Closure $nested): static
{
if ($this->locked) {
throw new AccessException('Nested options cannot be defined from a lazy option or normalizer.');
}
// Store closure for later evaluation
$this->nested[$option][] = $nested;
$this->defaults[$option] = [];
$this->defined[$option] = true;
// Make sure the option is processed
unset($this->resolved[$option]);
return $this;
}
public function isNested(string $option): bool
{
return isset($this->nested[$option]);
@@ -438,11 +469,11 @@ class OptionsResolver implements Options
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
if (!\is_string($message) && !$message instanceof \Closure) {
throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', get_debug_type($message)));
throw new InvalidArgumentException(\sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', get_debug_type($message)));
}
// ignore if empty string
@@ -497,7 +528,7 @@ class OptionsResolver implements Options
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
$this->normalizers[$option] = [$normalizer];
@@ -538,7 +569,7 @@ class OptionsResolver implements Options
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
if ($forcePrepend) {
@@ -581,7 +612,7 @@ class OptionsResolver implements Options
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
$this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : [$allowedValues];
@@ -621,7 +652,7 @@ class OptionsResolver implements Options
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
if (!\is_array($allowedValues)) {
@@ -661,7 +692,7 @@ class OptionsResolver implements Options
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
$this->allowedTypes[$option] = (array) $allowedTypes;
@@ -695,7 +726,7 @@ class OptionsResolver implements Options
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
if (!isset($this->allowedTypes[$option])) {
@@ -716,7 +747,7 @@ class OptionsResolver implements Options
public function define(string $option): OptionConfigurator
{
if (isset($this->defined[$option])) {
throw new OptionDefinitionException(sprintf('The option "%s" is already defined.', $option));
throw new OptionDefinitionException(\sprintf('The option "%s" is already defined.', $option));
}
return new OptionConfigurator($option, $this);
@@ -737,7 +768,7 @@ class OptionsResolver implements Options
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
$this->info[$option] = $info;
@@ -751,7 +782,7 @@ class OptionsResolver implements Options
public function getInfo(string $option): ?string
{
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new UndefinedOptionsException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
return $this->info[$option] ?? null;
@@ -873,7 +904,7 @@ class OptionsResolver implements Options
ksort($clone->defined);
ksort($diff);
throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', $this->formatOptions(array_keys($diff)), implode('", "', array_keys($clone->defined))));
throw new UndefinedOptionsException(\sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', $this->formatOptions(array_keys($diff)), implode('", "', array_keys($clone->defined))));
}
// Override options set by the user
@@ -893,7 +924,7 @@ class OptionsResolver implements Options
if (\count($diff) > 0) {
ksort($diff);
throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', $this->formatOptions(array_keys($diff))));
throw new MissingOptionsException(\sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', $this->formatOptions(array_keys($diff))));
}
// Lock the container
@@ -939,26 +970,42 @@ class OptionsResolver implements Options
// Check whether the option is set at all
if (!isset($this->defaults[$option]) && !\array_key_exists($option, $this->defaults)) {
if (!isset($this->defined[$option])) {
throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
throw new NoSuchOptionException(\sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
}
throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $this->formatOptions([$option])));
throw new NoSuchOptionException(\sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $this->formatOptions([$option])));
}
$value = $this->defaults[$option];
// Resolve the option if the default value is lazily evaluated
if (isset($this->lazy[$option])) {
// If the closure is already being called, we have a cyclic dependency
if (isset($this->calling[$option])) {
throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
}
$this->calling[$option] = true;
try {
foreach ($this->lazy[$option] as $closure) {
$value = $closure($this, $value);
}
} finally {
unset($this->calling[$option]);
}
}
// Resolve the option if it is a nested definition
if (isset($this->nested[$option])) {
// If the closure is already being called, we have a cyclic dependency
if (isset($this->calling[$option])) {
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
}
if (!\is_array($value)) {
throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptions([$option]), $this->formatValue($value), get_debug_type($value)));
throw new InvalidOptionsException(\sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptions([$option]), $this->formatValue($value), get_debug_type($value)));
}
// The following section must be protected from cyclic calls.
$this->calling[$option] = true;
try {
$resolver = new self();
@@ -973,7 +1020,7 @@ class OptionsResolver implements Options
$values = [];
foreach ($value as $index => $prototypeValue) {
if (!\is_array($prototypeValue)) {
throw new InvalidOptionsException(sprintf('The value of the option "%s" is expected to be of type array of array, but is of type array of "%s".', $this->formatOptions([$option]), get_debug_type($prototypeValue)));
throw new InvalidOptionsException(\sprintf('The value of the option "%s" is expected to be of type array of array, but is of type array of "%s".', $this->formatOptions([$option]), get_debug_type($prototypeValue)));
}
$resolver->prototypeIndex = $index;
@@ -989,29 +1036,6 @@ class OptionsResolver implements Options
}
}
// Resolve the option if the default value is lazily evaluated
if (isset($this->lazy[$option])) {
// If the closure is already being called, we have a cyclic
// dependency
if (isset($this->calling[$option])) {
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
}
// The following section must be protected from cyclic
// calls. Set $calling for the current $option to detect a cyclic
// dependency
// BEGIN
$this->calling[$option] = true;
try {
foreach ($this->lazy[$option] as $closure) {
$value = $closure($this, $value);
}
} finally {
unset($this->calling[$option]);
}
// END
}
// Validate the type of the resolved option
if (isset($this->allowedTypes[$option])) {
$valid = true;
@@ -1030,10 +1054,10 @@ class OptionsResolver implements Options
$allowedContainsArrayType = \count(array_filter($this->allowedTypes[$option], static fn ($item) => str_ends_with($item, '[]'))) > 0;
if (\is_array($value) && $allowedContainsArrayType) {
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes));
throw new InvalidOptionsException(\sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes));
}
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes));
throw new InvalidOptionsException(\sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes));
}
}
@@ -1062,21 +1086,21 @@ class OptionsResolver implements Options
}
if (!$success) {
$message = sprintf(
$message = \sprintf(
'The option "%s" with value %s is invalid.',
$this->formatOptions([$option]),
$this->formatValue($value)
);
if (\count($printableAllowedValues) > 0) {
$message .= sprintf(
$message .= \sprintf(
' Accepted values are: %s.',
$this->formatValues($printableAllowedValues)
);
}
if (isset($this->info[$option])) {
$message .= sprintf(' Info: %s.', $this->info[$option]);
$message .= \sprintf(' Info: %s.', $this->info[$option]);
}
throw new InvalidOptionsException($message);
@@ -1092,13 +1116,13 @@ class OptionsResolver implements Options
if ($message instanceof \Closure) {
// If the closure is already being called, we have a cyclic dependency
if (isset($this->calling[$option])) {
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
}
$this->calling[$option] = true;
try {
if (!\is_string($message = $message($this, $value))) {
throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', get_debug_type($message)));
throw new InvalidOptionsException(\sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', get_debug_type($message)));
}
} finally {
unset($this->calling[$option]);
@@ -1115,7 +1139,7 @@ class OptionsResolver implements Options
// If the closure is already being called, we have a cyclic
// dependency
if (isset($this->calling[$option])) {
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
}
// The following section must be protected from cyclic
@@ -1139,8 +1163,29 @@ class OptionsResolver implements Options
return $value;
}
private function verifyTypes(string $type, mixed $value, array &$invalidTypes, int $level = 0): bool
private function verifyTypes(string $type, mixed $value, ?array &$invalidTypes = null, int $level = 0): bool
{
$type = trim($type);
$allowedTypes = $this->splitOutsideParenthesis($type);
if (\count($allowedTypes) > 1) {
foreach ($allowedTypes as $allowedType) {
if ($this->verifyTypes($allowedType, $value)) {
return true;
}
}
if (\is_array($invalidTypes) && (!$invalidTypes || $level > 0)) {
$invalidTypes[get_debug_type($value)] = true;
}
return false;
}
$type = $allowedTypes[0];
if (str_starts_with($type, '(') && str_ends_with($type, ')')) {
return $this->verifyTypes(substr($type, 1, -1), $value, $invalidTypes, $level);
}
if (\is_array($value) && str_ends_with($type, '[]')) {
$type = substr($type, 0, -2);
$valid = true;
@@ -1158,13 +1203,47 @@ class OptionsResolver implements Options
return true;
}
if (!$invalidTypes || $level > 0) {
if (\is_array($invalidTypes) && (!$invalidTypes || $level > 0)) {
$invalidTypes[get_debug_type($value)] = true;
}
return false;
}
/**
* @return list<string>
*/
private function splitOutsideParenthesis(string $type): array
{
$parts = [];
$currentPart = '';
$parenthesisLevel = 0;
$typeLength = \strlen($type);
for ($i = 0; $i < $typeLength; ++$i) {
$char = $type[$i];
if ('(' === $char) {
++$parenthesisLevel;
} elseif (')' === $char) {
--$parenthesisLevel;
}
if ('|' === $char && 0 === $parenthesisLevel) {
$parts[] = $currentPart;
$currentPart = '';
} else {
$currentPart .= $char;
}
}
if ('' !== $currentPart) {
$parts[] = $currentPart;
}
return $parts;
}
/**
* Returns whether a resolved option with the given name exists.
*
@@ -1293,14 +1372,14 @@ class OptionsResolver implements Options
if ($this->parentsOptions) {
$prefix = array_shift($this->parentsOptions);
if ($this->parentsOptions) {
$prefix .= sprintf('[%s]', implode('][', $this->parentsOptions));
$prefix .= \sprintf('[%s]', implode('][', $this->parentsOptions));
}
if ($this->prototype && null !== $this->prototypeIndex) {
$prefix .= sprintf('[%s]', $this->prototypeIndex);
$prefix .= \sprintf('[%s]', $this->prototypeIndex);
}
$options = array_map(static fn (string $option): string => sprintf('%s[%s]', $prefix, $option), $options);
$options = array_map(static fn (string $option): string => \sprintf('%s[%s]', $prefix, $option), $options);
}
return implode('", "', $options);