diff --git a/Attribute/ExtendsValidationFor.php b/Attribute/ExtendsValidationFor.php
new file mode 100644
index 000000000..1d7b50cbb
--- /dev/null
+++ b/Attribute/ExtendsValidationFor.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Attribute;
+
+/**
+ * Declares that constraints listed on the current class should be added to the given class.
+ *
+ * Classes that use this attribute should contain only properties and methods that
+ * exist on the target class (not necessarily all of them).
+ *
+ * @author Nicolas Grekas
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+final class ExtendsValidationFor
+{
+ /**
+ * @param class-string $class
+ */
+ public function __construct(
+ public string $class,
+ ) {
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8146d2a5..b0d349a68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,194 @@
CHANGELOG
=========
+7.4
+---
+
+ * Deprecate handling associative arrays in `GroupSequence`
+
+ Before:
+
+ ```php
+ $groupSequence = GroupSequence(['value' => ['group 1', 'group 2']]);
+ ```
+
+ After:
+
+ ```php
+ $groupSequence = GroupSequence(['group 1', 'group 2']);
+ ```
+ * Deprecate configuring constraint options implicitly with the XML format
+
+ Before:
+
+ ```xml
+
+
+ Symfony\Component\Validator\Tests\Fixtures\CallbackClass
+ callback
+
+
+ ```
+
+ After:
+
+ ```xml
+
+
+
+
+
+ ```
+ * Deprecate configuring constraint options implicitly with the YAML format
+
+ Before:
+
+ ```yaml
+ Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity:
+ constraints:
+ - Callback: validateMeStatic
+ - Callback: [Symfony\Component\Validator\Tests\Fixtures\CallbackClass, callback]
+ ```
+
+ After:
+
+ ```yaml
+ Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity:
+ constraints:
+ - Callback:
+ callback: validateMeStatic
+ - Callback:
+ callback: [Symfony\Component\Validator\Tests\Fixtures\CallbackClass, callback]
+ ```
+ * Add `#[ExtendsValidationFor]` to declare new constraints for a class
+ * Add `ValidatorBuilder::addAttributeMappings()` and `AttributeMetadataPass` to declare compile-time constraint metadata using attributes
+ * Add the `Video` constraint for validating video files
+ * Deprecate implementing `__sleep/wakeup()` on `GenericMetadata` implementations; use `__(un)serialize()` instead
+ * Deprecate passing a list of choices to the first argument of the `Choice` constraint. Use the `choices` option instead
+ * Add the `min` and `max` parameter to the `Length` constraint violation
+ * Deprecate `getRequiredOptions()` and `getDefaultOption()` methods of the `All`, `AtLeastOneOf`, `CardScheme`, `Collection`,
+ `CssColor`, `Expression`, `Regex`, `Sequentially`, `Type`, and `When` constraints
+ * Deprecate evaluating options in the base `Constraint` class. Initialize properties in the constructor of the concrete constraint
+ class instead.
+
+ Before:
+
+ ```php
+ class CustomConstraint extends Constraint
+ {
+ public $option1;
+ public $option2;
+
+ public function __construct(?array $options = null)
+ {
+ parent::__construct($options);
+ }
+ }
+ ```
+
+ After:
+
+ ```php
+ use Symfony\Component\Validator\Attribute\HasNamedArguments;
+
+ class CustomConstraint extends Constraint
+ {
+ #[HasNamedArguments]
+ public function __construct(
+ public $option1 = null,
+ public $option2 = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct(null, $groups, $payload);
+ }
+ }
+ ```
+ * Deprecate the `getRequiredOptions()` method of the base `Constraint` class. Use mandatory constructor arguments instead.
+
+ Before:
+
+ ```php
+ class CustomConstraint extends Constraint
+ {
+ public $option1;
+ public $option2;
+
+ public function __construct(?array $options = null)
+ {
+ parent::__construct($options);
+ }
+
+ public function getRequiredOptions()
+ {
+ return ['option1'];
+ }
+ }
+ ```
+
+ After:
+
+ ```php
+ use Symfony\Component\Validator\Attribute\HasNamedArguments;
+
+ class CustomConstraint extends Constraint
+ {
+ #[HasNamedArguments]
+ public function __construct(
+ public $option1,
+ public $option2 = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct(null, $groups, $payload);
+ }
+ }
+ ```
+ * Deprecate the `normalizeOptions()` and `getDefaultOption()` methods of the base `Constraint` class without replacements.
+ Overriding them in child constraint will not have any effects starting with Symfony 8.0.
+ * Deprecate passing an array of options to the `Composite` constraint class. Initialize the properties referenced with `getNestedConstraints()`
+ in child classes before calling the constructor of `Composite`.
+
+ Before:
+
+ ```php
+ class CustomCompositeConstraint extends Composite
+ {
+ public array $constraints = [];
+
+ public function __construct(?array $options = null)
+ {
+ parent::__construct($options);
+ }
+
+ protected function getCompositeOption(): string
+ {
+ return 'constraints';
+ }
+ }
+ ```
+
+ After:
+
+ ```php
+ use Symfony\Component\Validator\Attribute\HasNamedArguments;
+
+ class CustomCompositeConstraint extends Composite
+ {
+ #[HasNamedArguments]
+ public function __construct(
+ public array $constraints,
+ ?array $groups = null,
+ mixed $payload = null)
+ {
+ parent::__construct(null, $groups, $payload);
+ }
+ }
+ ```
+
7.3
---
diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php
index 70b508d09..e6a076c97 100644
--- a/Command/DebugCommand.php
+++ b/Command/DebugCommand.php
@@ -50,10 +50,10 @@ protected function configure(): void
->addArgument('class', InputArgument::REQUIRED, 'A fully qualified class name or a path')
->addOption('show-all', null, InputOption::VALUE_NONE, 'Show all classes even if they have no validation constraints')
->setHelp(<<<'EOF'
-The %command.name% 'App\Entity\Dummy' command dumps the validators for the dummy class.
+ The %command.name% 'App\Entity\Dummy' command dumps the validators for the dummy class.
-The %command.name% src/ command dumps the validators for the `src` directory.
-EOF
+ The %command.name% src/ command dumps the validators for the `src` directory.
+ EOF
)
;
}
diff --git a/Constraint.php b/Constraint.php
index 5fd8ce84c..47dd9b42d 100644
--- a/Constraint.php
+++ b/Constraint.php
@@ -110,6 +110,17 @@ public function __construct(mixed $options = null, ?array $groups = null, mixed
{
unset($this->groups); // enable lazy initialization
+ if (null === $options && (\func_num_args() > 0 || self::class === (new \ReflectionMethod($this, 'getRequiredOptions'))->getDeclaringClass()->getName())) {
+ if (null !== $groups) {
+ $this->groups = $groups;
+ }
+ $this->payload = $payload;
+
+ return;
+ }
+
+ trigger_deprecation('symfony/validator', '7.4', 'Support for evaluating options in the base Constraint class is deprecated. Initialize properties in the constructor of %s instead.', static::class);
+
$options = $this->normalizeOptions($options);
if (null !== $groups) {
$options['groups'] = $groups;
@@ -122,14 +133,16 @@ public function __construct(mixed $options = null, ?array $groups = null, mixed
}
/**
+ * @deprecated since Symfony 7.4
+ *
* @return array
*/
protected function normalizeOptions(mixed $options): array
{
$normalizedOptions = [];
- $defaultOption = $this->getDefaultOption();
+ $defaultOption = $this->getDefaultOption(false);
$invalidOptions = [];
- $missingOptions = array_flip($this->getRequiredOptions());
+ $missingOptions = array_flip($this->getRequiredOptions(false));
$knownOptions = get_class_vars(static::class);
if (\is_array($options) && isset($options['value']) && !property_exists($this, 'value')) {
@@ -241,10 +254,15 @@ public function addImplicitGroupName(string $group): void
*
* Override this method to define a default option.
*
+ * @deprecated since Symfony 7.4
* @see __construct()
*/
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return null;
}
@@ -255,10 +273,15 @@ public function getDefaultOption(): ?string
*
* @return string[]
*
+ * @deprecated since Symfony 7.4
* @see __construct()
*/
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return [];
}
@@ -287,14 +310,23 @@ public function getTargets(): string|array
/**
* Optimizes the serialized value to minimize storage space.
- *
- * @internal
*/
- public function __sleep(): array
+ public function __serialize(): array
{
// Initialize "groups" option if it is not set
$this->groups;
- return array_keys(get_object_vars($this));
+ $data = [];
+ $class = $this::class;
+ foreach ((array) $this as $k => $v) {
+ $data[match (true) {
+ '' === $k || "\0" !== $k[0] => $k,
+ str_starts_with($k, "\0*\0") => substr($k, 3),
+ str_starts_with($k, "\0{$class}\0") => substr($k, 2 + \strlen($class)),
+ default => $k,
+ }] = $v;
+ }
+
+ return $data;
}
}
diff --git a/Constraints/AbstractComparison.php b/Constraints/AbstractComparison.php
index 3830da789..786d86803 100644
--- a/Constraints/AbstractComparison.php
+++ b/Constraints/AbstractComparison.php
@@ -36,19 +36,19 @@ public function __construct(mixed $value = null, ?string $propertyPath = null, ?
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($value, $options ?? []);
+ $value = null;
} elseif (null !== $value) {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
- }
- $options['value'] = $value;
+ $options['value'] = $value;
+ }
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
+ $this->value = $value ?? $this->value;
$this->propertyPath = $propertyPath ?? $this->propertyPath;
if (null === $this->value && null === $this->propertyPath) {
@@ -64,8 +64,15 @@ public function __construct(mixed $value = null, ?string $propertyPath = null, ?
}
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'value';
}
}
diff --git a/Constraints/All.php b/Constraints/All.php
index 92ded329b..533599ad0 100644
--- a/Constraints/All.php
+++ b/Constraints/All.php
@@ -13,6 +13,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* When applied to an array (or Traversable object), this constraint allows you to apply
@@ -26,26 +27,48 @@ class All extends Composite
public array|Constraint $constraints = [];
/**
- * @param array|array|Constraint|null $constraints
- * @param string[]|null $groups
+ * @param array|Constraint|null $constraints
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null)
{
- if (\is_array($constraints) && !array_is_list($constraints)) {
- trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
+ if (null === $constraints || [] === $constraints) {
+ throw new MissingOptionsException(\sprintf('The options "constraints" must be set for constraint "%s".', self::class), ['constraints']);
}
- parent::__construct($constraints ?? [], $groups, $payload);
+ if (!$constraints instanceof Constraint && !\is_array($constraints) || \is_array($constraints) && !array_is_list($constraints)) {
+ trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
+
+ parent::__construct($constraints, $groups, $payload);
+ } else {
+ $this->constraints = $constraints;
+
+ parent::__construct(null, $groups, $payload);
+ }
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'constraints';
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['constraints'];
}
diff --git a/Constraints/AtLeastOneOf.php b/Constraints/AtLeastOneOf.php
index 20d55f458..bc99b3385 100644
--- a/Constraints/AtLeastOneOf.php
+++ b/Constraints/AtLeastOneOf.php
@@ -13,6 +13,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* Checks that at least one of the given constraint is satisfied.
@@ -34,33 +35,54 @@ class AtLeastOneOf extends Composite
public bool $includeInternalMessages = true;
/**
- * @param array|array|null $constraints An array of validation constraints
- * @param string[]|null $groups
- * @param string|null $message Intro of the failure message that will be followed by the failed constraint(s) message(s)
- * @param string|null $messageCollection Failure message for All and Collection inner constraints
- * @param bool|null $includeInternalMessages Whether to include inner constraint messages (defaults to true)
+ * @param array|null $constraints An array of validation constraints
+ * @param string[]|null $groups
+ * @param string|null $message Intro of the failure message that will be followed by the failed constraint(s) message(s)
+ * @param string|null $messageCollection Failure message for All and Collection inner constraints
+ * @param bool|null $includeInternalMessages Whether to include inner constraint messages (defaults to true)
*/
#[HasNamedArguments]
public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null, ?string $message = null, ?string $messageCollection = null, ?bool $includeInternalMessages = null)
{
- if (\is_array($constraints) && !array_is_list($constraints)) {
+ if (null === $constraints || [] === $constraints) {
+ throw new MissingOptionsException(\sprintf('The options "constraints" must be set for constraint "%s".', self::class), ['constraints']);
+ }
+
+ if (!$constraints instanceof Constraint && !\is_array($constraints) || \is_array($constraints) && !array_is_list($constraints)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
+ $options = $constraints;
+ } else {
+ $this->constraints = $constraints;
}
- parent::__construct($constraints ?? [], $groups, $payload);
+ parent::__construct($options ?? null, $groups, $payload);
$this->message = $message ?? $this->message;
$this->messageCollection = $messageCollection ?? $this->messageCollection;
$this->includeInternalMessages = $includeInternalMessages ?? $this->includeInternalMessages;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'constraints';
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['constraints'];
}
diff --git a/Constraints/Bic.php b/Constraints/Bic.php
index 5390d5556..ef1ae1660 100644
--- a/Constraints/Bic.php
+++ b/Constraints/Bic.php
@@ -65,7 +65,6 @@ class Bic extends Constraint
public string $mode = self::VALIDATION_MODE_STRICT;
/**
- * @param array|null $options
* @param string|null $iban An IBAN value to validate that its country code is the same as the BIC's one
* @param string|null $ibanPropertyPath Property path to the IBAN value when validating objects
* @param string[]|null $groups
diff --git a/Constraints/BicValidator.php b/Constraints/BicValidator.php
index 55b6bbbc0..0c3e42b98 100644
--- a/Constraints/BicValidator.php
+++ b/Constraints/BicValidator.php
@@ -78,7 +78,7 @@ public function validate(mixed $value, Constraint $constraint): void
$canonicalize = str_replace(' ', '', $value);
// the bic must be either 8 or 11 characters long
- if (!\in_array(\strlen($canonicalize), [8, 11])) {
+ if (!\in_array(\strlen($canonicalize), [8, 11], true)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Bic::INVALID_LENGTH_ERROR)
diff --git a/Constraints/Blank.php b/Constraints/Blank.php
index 72fbae57a..b03586933 100644
--- a/Constraints/Blank.php
+++ b/Constraints/Blank.php
@@ -31,8 +31,7 @@ class Blank extends Constraint
public string $message = 'This value should be blank.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
@@ -41,7 +40,7 @@ public function __construct(?array $options = null, ?string $message = null, ?ar
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- parent::__construct($options ?? [], $groups, $payload);
+ parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
diff --git a/Constraints/Callback.php b/Constraints/Callback.php
index 44b51ac2a..326125a45 100644
--- a/Constraints/Callback.php
+++ b/Constraints/Callback.php
@@ -28,8 +28,8 @@ class Callback extends Constraint
public $callback;
/**
- * @param string|string[]|callable|array|null $callback The callback definition
- * @param string[]|null $groups
+ * @param string|string[]|callable|null $callback The callback definition
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(array|string|callable|null $callback = null, ?array $groups = null, mixed $payload = null, ?array $options = null)
@@ -44,22 +44,28 @@ public function __construct(array|string|callable|null $callback = null, ?array
if (!\is_array($callback) || (!isset($callback['callback']) && !isset($callback['groups']) && !isset($callback['payload']))) {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
}
-
- $options['callback'] = $callback;
} else {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($callback, $options ?? []);
+ $callback = null;
}
parent::__construct($options, $groups, $payload);
+
+ $this->callback = $callback ?? $this->callback;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'callback';
}
diff --git a/Constraints/CardScheme.php b/Constraints/CardScheme.php
index 81de342f5..706969796 100644
--- a/Constraints/CardScheme.php
+++ b/Constraints/CardScheme.php
@@ -13,6 +13,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* Validates a credit card number for a given credit card company.
@@ -48,41 +49,54 @@ class CardScheme extends Constraint
public array|string|null $schemes = null;
/**
- * @param non-empty-string|non-empty-string[]|array|null $schemes Name(s) of the number scheme(s) used to validate the credit card number
- * @param string[]|null $groups
- * @param array|null $options
+ * @param non-empty-string|non-empty-string[]|null $schemes Name(s) of the number scheme(s) used to validate the credit card number
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(array|string|null $schemes, ?string $message = null, ?array $groups = null, mixed $payload = null, ?array $options = null)
{
+ if (null === $schemes && !isset($options['schemes'])) {
+ throw new MissingOptionsException(\sprintf('The options "schemes" must be set for constraint "%s".', self::class), ['schemes']);
+ }
+
if (\is_array($schemes) && \is_string(key($schemes))) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($schemes, $options ?? []);
+ $schemes = null;
} else {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
- }
-
- if (null !== $schemes) {
- $options['value'] = $schemes;
}
}
parent::__construct($options, $groups, $payload);
+ $this->schemes = $schemes ?? $this->schemes;
$this->message = $message ?? $this->message;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'schemes';
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['schemes'];
}
}
diff --git a/Constraints/Cascade.php b/Constraints/Cascade.php
index 7d90cfcf7..86419d7e6 100644
--- a/Constraints/Cascade.php
+++ b/Constraints/Cascade.php
@@ -26,8 +26,7 @@ class Cascade extends Constraint
public array $exclude = [];
/**
- * @param non-empty-string[]|non-empty-string|array|null $exclude Properties excluded from validation
- * @param array|null $options
+ * @param non-empty-string[]|non-empty-string|null $exclude Properties excluded from validation
*/
#[HasNamedArguments]
public function __construct(array|string|null $exclude = null, ?array $options = null)
@@ -37,19 +36,23 @@ public function __construct(array|string|null $exclude = null, ?array $options =
$options = array_merge($exclude, $options ?? []);
$options['exclude'] = array_flip((array) ($options['exclude'] ?? []));
+ $exclude = $options['exclude'] ?? null;
} else {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- $this->exclude = array_flip((array) $exclude);
+ $exclude = array_flip((array) $exclude);
+ $this->exclude = $exclude;
}
if (\is_array($options) && \array_key_exists('groups', $options)) {
throw new ConstraintDefinitionException(\sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
}
- parent::__construct($options);
+ parent::__construct($options, null, $options['payload'] ?? null);
+
+ $this->exclude = $exclude ?? $this->exclude;
}
public function getTargets(): string|array
diff --git a/Constraints/Choice.php b/Constraints/Choice.php
index 1435a762b..cf353907d 100644
--- a/Constraints/Choice.php
+++ b/Constraints/Choice.php
@@ -45,8 +45,15 @@ class Choice extends Constraint
public string $maxMessage = 'You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.';
public bool $match = true;
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'choices';
}
@@ -62,7 +69,7 @@ public function getDefaultOption(): ?string
*/
#[HasNamedArguments]
public function __construct(
- string|array $options = [],
+ string|array|null $options = null,
?array $choices = null,
callable|string|null $callback = null,
?bool $multiple = null,
@@ -78,18 +85,16 @@ public function __construct(
?bool $match = null,
) {
if (\is_array($options) && $options && array_is_list($options)) {
+ trigger_deprecation('symfony/validator', '7.4', 'Support for passing the choices as the first argument to %s is deprecated.', static::class);
$choices ??= $options;
- $options = [];
+ $options = null;
} elseif (\is_array($options) && [] !== $options) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- if (null !== $choices) {
- $options['value'] = $choices;
- }
-
parent::__construct($options, $groups, $payload);
+ $this->choices = $choices ?? $this->choices;
$this->callback = $callback ?? $this->callback;
$this->multiple = $multiple ?? $this->multiple;
$this->strict = $strict ?? $this->strict;
diff --git a/Constraints/Collection.php b/Constraints/Collection.php
index b59caa89d..3954a6c38 100644
--- a/Constraints/Collection.php
+++ b/Constraints/Collection.php
@@ -13,6 +13,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* Validates a collection with constraints defined for specific keys.
@@ -37,27 +38,27 @@ class Collection extends Composite
public string $missingFieldsMessage = 'This field is missing.';
/**
- * @param array|array|null $fields An associative array defining keys in the collection and their constraints
- * @param string[]|null $groups
- * @param bool|null $allowExtraFields Whether to allow additional keys not declared in the configured fields (defaults to false)
- * @param bool|null $allowMissingFields Whether to allow the collection to lack some fields declared in the configured fields (defaults to false)
+ * @param array>|null $fields An associative array defining keys in the collection and their constraints
+ * @param string[]|null $groups
+ * @param bool|null $allowExtraFields Whether to allow additional keys not declared in the configured fields (defaults to false)
+ * @param bool|null $allowMissingFields Whether to allow the collection to lack some fields declared in the configured fields (defaults to false)
*/
#[HasNamedArguments]
public function __construct(mixed $fields = null, ?array $groups = null, mixed $payload = null, ?bool $allowExtraFields = null, ?bool $allowMissingFields = null, ?string $extraFieldsMessage = null, ?string $missingFieldsMessage = null)
{
- $options = $fields;
+ if (null === $fields) {
+ throw new MissingOptionsException(\sprintf('The options "fields" must be set for constraint "%s".', self::class), ['fields']);
+ }
if (self::isFieldsOption($fields)) {
- $options = [];
-
- if (null !== $fields) {
- $options['fields'] = $fields;
- }
+ $this->fields = $fields;
} else {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
+
+ $options = $fields;
}
- parent::__construct($options, $groups, $payload);
+ parent::__construct($options ?? null, $groups, $payload);
$this->allowExtraFields = $allowExtraFields ?? $this->allowExtraFields;
$this->allowMissingFields = $allowMissingFields ?? $this->allowMissingFields;
@@ -82,8 +83,15 @@ protected function initializeNestedConstraints(): void
}
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['fields'];
}
@@ -94,10 +102,6 @@ protected function getCompositeOption(): string
private static function isFieldsOption($options): bool
{
- if (null === $options) {
- return true;
- }
-
if (!\is_array($options)) {
return false;
}
diff --git a/Constraints/Composite.php b/Constraints/Composite.php
index 1710d9a49..fdfaacc2e 100644
--- a/Constraints/Composite.php
+++ b/Constraints/Composite.php
@@ -53,6 +53,10 @@ abstract class Composite extends Constraint
#[HasNamedArguments]
public function __construct(mixed $options = null, ?array $groups = null, mixed $payload = null)
{
+ if (null !== $options) {
+ trigger_deprecation('symfony/validator', '7.4', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
+ }
+
parent::__construct($options, $groups, $payload);
$this->initializeNestedConstraints();
diff --git a/Constraints/Count.php b/Constraints/Count.php
index 108872904..c947c9225 100644
--- a/Constraints/Count.php
+++ b/Constraints/Count.php
@@ -44,12 +44,12 @@ class Count extends Constraint
public ?int $divisibleBy = null;
/**
- * @param int<0, max>|array|null $exactly The exact expected number of elements
- * @param int<0, max>|null $min Minimum expected number of elements
- * @param int<0, max>|null $max Maximum expected number of elements
- * @param positive-int|null $divisibleBy The number the collection count should be divisible by
- * @param string[]|null $groups
- * @param array|null $options
+ * @param int<0, max>|null $exactly The exact expected number of elements
+ * @param int<0, max>|null $min Minimum expected number of elements
+ * @param int<0, max>|null $max Maximum expected number of elements
+ * @param positive-int|null $divisibleBy The number the collection count should be divisible by
+ * @param string[]|null $groups
+ * @param array|null $options
*/
#[HasNamedArguments]
public function __construct(
@@ -72,8 +72,6 @@ public function __construct(
$exactly = $options['value'] ?? null;
} elseif (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
}
$min ??= $options['min'] ?? null;
diff --git a/Constraints/Country.php b/Constraints/Country.php
index 135f996dd..89d4717b4 100644
--- a/Constraints/Country.php
+++ b/Constraints/Country.php
@@ -36,9 +36,8 @@ class Country extends Constraint
public bool $alpha3 = false;
/**
- * @param array|null $options
- * @param bool|null $alpha3 Whether to check for alpha-3 codes instead of alpha-2 (defaults to false)
- * @param string[]|null $groups
+ * @param bool|null $alpha3 Whether to check for alpha-3 codes instead of alpha-2 (defaults to false)
+ * @param string[]|null $groups
*
* @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3#Current_codes
*/
diff --git a/Constraints/CssColor.php b/Constraints/CssColor.php
index 793a4a576..f73d2941b 100644
--- a/Constraints/CssColor.php
+++ b/Constraints/CssColor.php
@@ -63,9 +63,8 @@ class CssColor extends Constraint
public array|string $formats;
/**
- * @param non-empty-string[]|non-empty-string|array $formats The types of CSS colors allowed ({@see https://symfony.com/doc/current/reference/constraints/CssColor.html#formats})
- * @param string[]|null $groups
- * @param array|null $options
+ * @param non-empty-string[]|non-empty-string $formats The types of CSS colors allowed ({@see https://symfony.com/doc/current/reference/constraints/CssColor.html#formats})
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(array|string $formats = [], ?string $message = null, ?array $groups = null, $payload = null, ?array $options = null)
@@ -73,39 +72,53 @@ public function __construct(array|string $formats = [], ?string $message = null,
$validationModesAsString = implode(', ', self::$validationModes);
if (!$formats) {
- $options['value'] = self::$validationModes;
+ $formats = self::$validationModes;
} elseif (\is_array($formats) && \is_string(key($formats))) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($formats, $options ?? []);
+ $formats = null;
} elseif (\is_array($formats)) {
if ([] === array_intersect(self::$validationModes, $formats)) {
throw new InvalidArgumentException(\sprintf('The "formats" parameter value is not valid. It must contain one or more of the following values: "%s".', $validationModesAsString));
}
-
- $options['value'] = $formats;
} elseif (\is_string($formats)) {
if (!\in_array($formats, self::$validationModes, true)) {
throw new InvalidArgumentException(\sprintf('The "formats" parameter value is not valid. It must contain one or more of the following values: "%s".', $validationModesAsString));
}
- $options['value'] = [$formats];
+ $formats = [$formats];
} else {
throw new InvalidArgumentException('The "formats" parameter type is not valid. It should be a string or an array.');
}
parent::__construct($options, $groups, $payload);
+ $this->formats = $formats ?? $this->formats;
$this->message = $message ?? $this->message;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'formats';
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['formats'];
}
}
diff --git a/Constraints/Currency.php b/Constraints/Currency.php
index c8f6417b3..678538a8a 100644
--- a/Constraints/Currency.php
+++ b/Constraints/Currency.php
@@ -36,8 +36,7 @@ class Currency extends Constraint
public string $message = 'This value is not a valid currency.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
diff --git a/Constraints/Date.php b/Constraints/Date.php
index adb48474f..f2ae75691 100644
--- a/Constraints/Date.php
+++ b/Constraints/Date.php
@@ -35,8 +35,7 @@ class Date extends Constraint
public string $message = 'This value is not a valid date.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
diff --git a/Constraints/DateTime.php b/Constraints/DateTime.php
index 6b287be75..63c8bda90 100644
--- a/Constraints/DateTime.php
+++ b/Constraints/DateTime.php
@@ -38,9 +38,8 @@ class DateTime extends Constraint
public string $message = 'This value is not a valid datetime.';
/**
- * @param non-empty-string|array|null $format The datetime format to match (defaults to 'Y-m-d H:i:s')
- * @param string[]|null $groups
- * @param array|null $options
+ * @param non-empty-string|null $format The datetime format to match (defaults to 'Y-m-d H:i:s')
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(string|array|null $format = null, ?string $message = null, ?array $groups = null, mixed $payload = null, ?array $options = null)
@@ -49,23 +48,30 @@ public function __construct(string|array|null $format = null, ?string $message =
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($format, $options ?? []);
+ $format = null;
} elseif (null !== $format) {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
- }
- $options['value'] = $format;
+ $options['value'] = $format;
+ }
}
parent::__construct($options, $groups, $payload);
+ $this->format = $format ?? $this->format;
$this->message = $message ?? $this->message;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'format';
}
}
diff --git a/Constraints/DisableAutoMapping.php b/Constraints/DisableAutoMapping.php
index 7cbea8b38..926d8be25 100644
--- a/Constraints/DisableAutoMapping.php
+++ b/Constraints/DisableAutoMapping.php
@@ -26,9 +26,6 @@
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
class DisableAutoMapping extends Constraint
{
- /**
- * @param array|null $options
- */
#[HasNamedArguments]
public function __construct(?array $options = null, mixed $payload = null)
{
diff --git a/Constraints/Email.php b/Constraints/Email.php
index 4a66986b2..193384019 100644
--- a/Constraints/Email.php
+++ b/Constraints/Email.php
@@ -47,8 +47,7 @@ class Email extends Constraint
public $normalizer;
/**
- * @param array|null $options
- * @param self::VALIDATION_MODE_*|null $mode The pattern used to validate the email address; pass null to use the default mode configured for the EmailValidator
+ * @param self::VALIDATION_MODE_*|null $mode The pattern used to validate the email address; pass null to use the default mode configured for the EmailValidator
* @param string[]|null $groups
*/
#[HasNamedArguments]
diff --git a/Constraints/EnableAutoMapping.php b/Constraints/EnableAutoMapping.php
index 873430677..58e77805a 100644
--- a/Constraints/EnableAutoMapping.php
+++ b/Constraints/EnableAutoMapping.php
@@ -26,9 +26,6 @@
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
class EnableAutoMapping extends Constraint
{
- /**
- * @param array|null $options
- */
#[HasNamedArguments]
public function __construct(?array $options = null, mixed $payload = null)
{
diff --git a/Constraints/Existence.php b/Constraints/Existence.php
index 72bc1da61..a867f09e5 100644
--- a/Constraints/Existence.php
+++ b/Constraints/Existence.php
@@ -20,8 +20,26 @@ abstract class Existence extends Composite
{
public array|Constraint $constraints = [];
+ public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null)
+ {
+ if (!$constraints instanceof Constraint && !\is_array($constraints) || \is_array($constraints) && !array_is_list($constraints)) {
+ parent::__construct($constraints, $groups, $payload);
+ } else {
+ $this->constraints = $constraints;
+
+ parent::__construct(null, $groups, $payload);
+ }
+ }
+
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'constraints';
}
diff --git a/Constraints/Expression.php b/Constraints/Expression.php
index f40577d7b..1c990cf6e 100644
--- a/Constraints/Expression.php
+++ b/Constraints/Expression.php
@@ -16,6 +16,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* Validates a value using an expression from the Expression Language component.
@@ -43,8 +44,7 @@ class Expression extends Constraint
* @param string|ExpressionObject|array|null $expression The expression to evaluate
* @param array|null $values The values of the custom variables used in the expression (defaults to an empty array)
* @param string[]|null $groups
- * @param array|null $options
- * @param bool|null $negate When set to true, if the expression returns true, the validation will pass (defaults to true)
+ * @param bool|null $negate Whether to fail if the expression evaluates to true (defaults to false)
*/
#[HasNamedArguments]
public function __construct(
@@ -60,36 +60,50 @@ public function __construct(
throw new LogicException(\sprintf('The "symfony/expression-language" component is required to use the "%s" constraint. Try running "composer require symfony/expression-language".', __CLASS__));
}
+ if (null === $expression && !isset($options['expression'])) {
+ throw new MissingOptionsException(\sprintf('The options "expression" must be set for constraint "%s".', self::class), ['expression']);
+ }
+
if (\is_array($expression)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($expression, $options ?? []);
+ $expression = null;
} else {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
- }
-
- if (null !== $expression) {
- $options['value'] = $expression;
}
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
+ $this->expression = $expression ?? $this->expression;
$this->values = $values ?? $this->values;
$this->negate = $negate ?? $this->negate;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'expression';
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['expression'];
}
diff --git a/Constraints/ExpressionSyntax.php b/Constraints/ExpressionSyntax.php
index 5a0a09de1..55ed3c542 100644
--- a/Constraints/ExpressionSyntax.php
+++ b/Constraints/ExpressionSyntax.php
@@ -33,10 +33,9 @@ class ExpressionSyntax extends Constraint
public ?array $allowedVariables = null;
/**
- * @param array|null $options
- * @param non-empty-string|null $service The service used to validate the constraint instead of the default one
- * @param string[]|null $allowedVariables Restrict the available variables in the expression to these values (defaults to null that allows any variable)
- * @param string[]|null $groups
+ * @param non-empty-string|null $service The service used to validate the constraint instead of the default one
+ * @param string[]|null $allowedVariables Restrict the available variables in the expression to these values (defaults to null that allows any variable)
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $allowedVariables = null, ?array $groups = null, mixed $payload = null)
diff --git a/Constraints/File.php b/Constraints/File.php
index 7d93a2084..169e94154 100644
--- a/Constraints/File.php
+++ b/Constraints/File.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Validator\Constraints;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
@@ -21,7 +22,7 @@
*
* A file can be one of the following:
* - A string (or object with a __toString() method) path to an existing file;
- * - A valid {@see \Symfony\Component\HttpFoundation\File\File File} object (including objects of {@see \Symfony\Component\HttpFoundation\File\UploadedFile UploadedFile} class).
+ * - A valid {@see \Symfony\Component\HttpFoundation\File\File} object (including objects of {@see UploadedFile} class).
*
* @property int $maxSize
*
@@ -91,7 +92,6 @@ class File extends Constraint
protected int|string|null $maxSize = null;
/**
- * @param array|null $options
* @param positive-int|string|null $maxSize The max size of the underlying file
* @param bool|null $binaryFormat Pass true to use binary-prefixed units (KiB, MiB, etc.) or false to use SI-prefixed units (kB, MB) in displayed messages. Pass null to guess the format from the maxSize option. (defaults to null)
* @param string[]|string|null $mimeTypes Acceptable media type(s). Prefer the extensions option that also enforce the file's extension consistency.
@@ -106,7 +106,7 @@ class File extends Constraint
* @param string[]|null $groups
* @param array|string|null $extensions A list of valid extensions to check. Related media types are also enforced ({@see https://symfony.com/doc/current/reference/constraints/File.html#extensions})
* @param string|null $filenameCharset The charset to be used when computing filename length (defaults to null)
- * @param self::FILENAME_COUNT_*|null $filenameCountUnit The character count unit used for checking the filename length (defaults to {@see File::FILENAME_COUNT_BYTES})
+ * @param self::FILENAME_COUNT_*|null $filenameCountUnit The character count unit used for checking the filename length (defaults to {@see self::FILENAME_COUNT_BYTES})
*
* @see https://www.iana.org/assignments/media-types/media-types.xhtml Existing media types
*/
diff --git a/Constraints/GroupSequence.php b/Constraints/GroupSequence.php
index e3e4f47f9..440c29003 100644
--- a/Constraints/GroupSequence.php
+++ b/Constraints/GroupSequence.php
@@ -80,6 +80,10 @@ class GroupSequence
#[HasNamedArguments]
public function __construct(array $groups)
{
+ if (!array_is_list($groups)) {
+ trigger_deprecation('symfony/validator', '7.4', 'Support for passing an array of options to "%s()" is deprecated.', __METHOD__);
+ }
+
$this->groups = $groups['value'] ?? $groups;
}
}
diff --git a/Constraints/Hostname.php b/Constraints/Hostname.php
index ca9bc3a32..f388c950c 100644
--- a/Constraints/Hostname.php
+++ b/Constraints/Hostname.php
@@ -32,9 +32,8 @@ class Hostname extends Constraint
public bool $requireTld = true;
/**
- * @param array|null $options
- * @param bool|null $requireTld Whether to require the hostname to include its top-level domain (defaults to true)
- * @param string[]|null $groups
+ * @param bool|null $requireTld Whether to require the hostname to include its top-level domain (defaults to true)
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/Iban.php b/Constraints/Iban.php
index 459fb5fb0..4898155c1 100644
--- a/Constraints/Iban.php
+++ b/Constraints/Iban.php
@@ -43,8 +43,7 @@ class Iban extends Constraint
public string $message = 'This is not a valid International Bank Account Number (IBAN).';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
diff --git a/Constraints/Image.php b/Constraints/Image.php
index d9b7c8822..b47dc8ba6 100644
--- a/Constraints/Image.php
+++ b/Constraints/Image.php
@@ -91,7 +91,6 @@ class Image extends File
public string $corruptedMessage = 'The image file is corrupted.';
/**
- * @param array|null $options
* @param positive-int|string|null $maxSize The max size of the underlying file
* @param bool|null $binaryFormat Pass true to use binary-prefixed units (KiB, MiB, etc.) or false to use SI-prefixed units (kB, MB) in displayed messages. Pass null to guess the format from the maxSize option. (defaults to null)
* @param non-empty-string[]|null $mimeTypes Acceptable media types
diff --git a/Constraints/Ip.php b/Constraints/Ip.php
index 4db552a76..91f247156 100644
--- a/Constraints/Ip.php
+++ b/Constraints/Ip.php
@@ -108,7 +108,6 @@ class Ip extends Constraint
public $normalizer;
/**
- * @param array|null $options
* @param self::V4*|self::V6*|self::ALL*|null $version The IP version to validate (defaults to {@see self::V4})
* @param string[]|null $groups
*/
diff --git a/Constraints/IsFalse.php b/Constraints/IsFalse.php
index bcdadeaf9..722f2a247 100644
--- a/Constraints/IsFalse.php
+++ b/Constraints/IsFalse.php
@@ -31,8 +31,7 @@ class IsFalse extends Constraint
public string $message = 'This value should be false.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
@@ -41,7 +40,7 @@ public function __construct(?array $options = null, ?string $message = null, ?ar
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- parent::__construct($options ?? [], $groups, $payload);
+ parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
diff --git a/Constraints/IsNull.php b/Constraints/IsNull.php
index fa04703ea..7447aed9f 100644
--- a/Constraints/IsNull.php
+++ b/Constraints/IsNull.php
@@ -31,8 +31,7 @@ class IsNull extends Constraint
public string $message = 'This value should be null.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
@@ -41,7 +40,7 @@ public function __construct(?array $options = null, ?string $message = null, ?ar
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- parent::__construct($options ?? [], $groups, $payload);
+ parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
diff --git a/Constraints/IsTrue.php b/Constraints/IsTrue.php
index 3c0345e77..58d25b594 100644
--- a/Constraints/IsTrue.php
+++ b/Constraints/IsTrue.php
@@ -31,8 +31,7 @@ class IsTrue extends Constraint
public string $message = 'This value should be true.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
@@ -41,7 +40,7 @@ public function __construct(?array $options = null, ?string $message = null, ?ar
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- parent::__construct($options ?? [], $groups, $payload);
+ parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
diff --git a/Constraints/Isbn.php b/Constraints/Isbn.php
index 45ca4e4b8..5251150f3 100644
--- a/Constraints/Isbn.php
+++ b/Constraints/Isbn.php
@@ -50,10 +50,9 @@ class Isbn extends Constraint
public ?string $message = null;
/**
- * @param self::ISBN_*|array|null $type The type of ISBN to validate (i.e. {@see Isbn::ISBN_10}, {@see Isbn::ISBN_13} or null to accept both, defaults to null)
- * @param string|null $message If defined, this message has priority over the others
- * @param string[]|null $groups
- * @param array|null $options
+ * @param self::ISBN_*|null $type The type of ISBN to validate (i.e. {@see Isbn::ISBN_10}, {@see Isbn::ISBN_13} or null to accept both, defaults to null)
+ * @param string|null $message If defined, this message has priority over the others
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
@@ -70,14 +69,9 @@ public function __construct(
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($type, $options ?? []);
- } elseif (null !== $type) {
- if (\is_array($options)) {
- trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
- }
-
- $options['value'] = $type;
+ $type = $options['type'] ?? null;
+ } elseif (\is_array($options)) {
+ trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
parent::__construct($options, $groups, $payload);
@@ -86,10 +80,18 @@ public function __construct(
$this->isbn10Message = $isbn10Message ?? $this->isbn10Message;
$this->isbn13Message = $isbn13Message ?? $this->isbn13Message;
$this->bothIsbnMessage = $bothIsbnMessage ?? $this->bothIsbnMessage;
+ $this->type = $type ?? $this->type;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'type';
}
}
diff --git a/Constraints/Isin.php b/Constraints/Isin.php
index 7bd9abe2d..821ec62d5 100644
--- a/Constraints/Isin.php
+++ b/Constraints/Isin.php
@@ -40,8 +40,7 @@ class Isin extends Constraint
public string $message = 'This value is not a valid International Securities Identification Number (ISIN).';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
diff --git a/Constraints/Issn.php b/Constraints/Issn.php
index 048c18f5e..1c4ba88d0 100644
--- a/Constraints/Issn.php
+++ b/Constraints/Issn.php
@@ -46,10 +46,9 @@ class Issn extends Constraint
public bool $requireHyphen = false;
/**
- * @param array|null $options
- * @param bool|null $caseSensitive Whether to allow the value to end with a lowercase character (defaults to false)
- * @param bool|null $requireHyphen Whether to require a hyphenated ISSN value (defaults to false)
- * @param string[]|null $groups
+ * @param bool|null $caseSensitive Whether to allow the value to end with a lowercase character (defaults to false)
+ * @param bool|null $requireHyphen Whether to require a hyphenated ISSN value (defaults to false)
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/Json.php b/Constraints/Json.php
index 18078a2fe..8798c94aa 100644
--- a/Constraints/Json.php
+++ b/Constraints/Json.php
@@ -31,8 +31,7 @@ class Json extends Constraint
public string $message = 'This value should be valid JSON.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
diff --git a/Constraints/Language.php b/Constraints/Language.php
index 61ac4644b..dfa91b4f7 100644
--- a/Constraints/Language.php
+++ b/Constraints/Language.php
@@ -36,9 +36,8 @@ class Language extends Constraint
public bool $alpha3 = false;
/**
- * @param array|null $options
- * @param bool|null $alpha3 Pass true to validate the language with three-letter code (ISO 639-2 (2T)) or false with two-letter code (ISO 639-1) (defaults to false)
- * @param string[]|null $groups
+ * @param bool|null $alpha3 Pass true to validate the language with three-letter code (ISO 639-2 (2T)) or false with two-letter code (ISO 639-1) (defaults to false)
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/Length.php b/Constraints/Length.php
index ce1460c6e..3c7f93ae6 100644
--- a/Constraints/Length.php
+++ b/Constraints/Length.php
@@ -59,14 +59,13 @@ class Length extends Constraint
public string $countUnit = self::COUNT_CODEPOINTS;
/**
- * @param positive-int|array|null $exactly The exact expected length
- * @param int<0, max>|null $min The minimum expected length
- * @param positive-int|null $max The maximum expected length
- * @param string|null $charset The charset to be used when computing value's length (defaults to UTF-8)
- * @param callable|null $normalizer A callable to normalize value before it is validated
- * @param self::COUNT_*|null $countUnit The character count unit for the length check (defaults to {@see Length::COUNT_CODEPOINTS})
- * @param string[]|null $groups
- * @param array|null $options
+ * @param positive-int|null $exactly The exact expected length
+ * @param int<0, max>|null $min The minimum expected length
+ * @param positive-int|null $max The maximum expected length
+ * @param string|null $charset The charset to be used when computing value's length (defaults to UTF-8)
+ * @param callable|null $normalizer A callable to normalize value before it is validated
+ * @param self::COUNT_*|null $countUnit The character count unit for the length check (defaults to {@see Length::COUNT_CODEPOINTS})
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
@@ -91,8 +90,6 @@ public function __construct(
$exactly = $options['value'] ?? null;
} elseif (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
}
$min ??= $options['min'] ?? null;
diff --git a/Constraints/LengthValidator.php b/Constraints/LengthValidator.php
index 985660bc2..b30f6722d 100644
--- a/Constraints/LengthValidator.php
+++ b/Constraints/LengthValidator.php
@@ -71,9 +71,15 @@ public function validate(mixed $value, Constraint $constraint): void
if (null !== $constraint->max && $length > $constraint->max) {
$exactlyOptionEnabled = $constraint->min == $constraint->max;
- $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->maxMessage)
+ $builder = $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->maxMessage);
+ if (null !== $constraint->min) {
+ $builder->setParameter('{{ min }}', $constraint->min);
+ }
+
+ $builder
->setParameter('{{ value }}', $this->formatValue($stringValue))
->setParameter('{{ limit }}', $constraint->max)
+ ->setParameter('{{ max }}', $constraint->max) // To be consistent with the min error message
->setParameter('{{ value_length }}', $length)
->setInvalidValue($value)
->setPlural($constraint->max)
@@ -86,9 +92,15 @@ public function validate(mixed $value, Constraint $constraint): void
if (null !== $constraint->min && $length < $constraint->min) {
$exactlyOptionEnabled = $constraint->min == $constraint->max;
- $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->minMessage)
+ $builder = $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->minMessage);
+ if (null !== $constraint->max) {
+ $builder->setParameter('{{ max }}', $constraint->max);
+ }
+
+ $builder
->setParameter('{{ value }}', $this->formatValue($stringValue))
->setParameter('{{ limit }}', $constraint->min)
+ ->setParameter('{{ min }}', $constraint->min) // To be consistent with the max error message
->setParameter('{{ value_length }}', $length)
->setInvalidValue($value)
->setPlural($constraint->min)
diff --git a/Constraints/Locale.php b/Constraints/Locale.php
index 0ffe4b0e8..d309fecee 100644
--- a/Constraints/Locale.php
+++ b/Constraints/Locale.php
@@ -36,9 +36,8 @@ class Locale extends Constraint
public bool $canonicalize = true;
/**
- * @param array|null $options
- * @param bool|null $canonicalize Whether to canonicalize the value before validation (defaults to true) (see {@see https://www.php.net/manual/en/locale.canonicalize.php})
- * @param string[]|null $groups
+ * @param bool|null $canonicalize Whether to canonicalize the value before validation (defaults to true) (see {@see https://www.php.net/manual/en/locale.canonicalize.php})
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/Luhn.php b/Constraints/Luhn.php
index 9421fc3c7..f2e93a867 100644
--- a/Constraints/Luhn.php
+++ b/Constraints/Luhn.php
@@ -37,8 +37,7 @@ class Luhn extends Constraint
public string $message = 'Invalid card number.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/NoSuspiciousCharacters.php b/Constraints/NoSuspiciousCharacters.php
index f0d28dba2..2a5ba530c 100644
--- a/Constraints/NoSuspiciousCharacters.php
+++ b/Constraints/NoSuspiciousCharacters.php
@@ -83,7 +83,6 @@ class NoSuspiciousCharacters extends Constraint
public ?array $locales = null;
/**
- * @param array|null $options
* @param int-mask-of|null $checks A bitmask of the checks to perform on the string (defaults to all checks)
* @param int-mask-of|null $restrictionLevel Configures the set of acceptable characters for the validated string through a specified "level" (defaults to
* {@see NoSuspiciousCharacters::RESTRICTION_LEVEL_MODERATE} on ICU >= 58, {@see NoSuspiciousCharacters::RESTRICTION_LEVEL_SINGLE_SCRIPT} otherwise)
diff --git a/Constraints/NotBlank.php b/Constraints/NotBlank.php
index 725e7eede..f26f6aff8 100644
--- a/Constraints/NotBlank.php
+++ b/Constraints/NotBlank.php
@@ -36,9 +36,8 @@ class NotBlank extends Constraint
public $normalizer;
/**
- * @param array|null $options
- * @param bool|null $allowNull Whether to allow null values (defaults to false)
- * @param string[]|null $groups
+ * @param bool|null $allowNull Whether to allow null values (defaults to false)
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?bool $allowNull = null, ?callable $normalizer = null, ?array $groups = null, mixed $payload = null)
@@ -47,7 +46,7 @@ public function __construct(?array $options = null, ?string $message = null, ?bo
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- parent::__construct($options ?? [], $groups, $payload);
+ parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->allowNull = $allowNull ?? $this->allowNull;
diff --git a/Constraints/NotCompromisedPassword.php b/Constraints/NotCompromisedPassword.php
index ef1e03da9..8a6219580 100644
--- a/Constraints/NotCompromisedPassword.php
+++ b/Constraints/NotCompromisedPassword.php
@@ -33,10 +33,9 @@ class NotCompromisedPassword extends Constraint
public bool $skipOnError = false;
/**
- * @param array|null $options
- * @param positive-int|null $threshold The number of times the password should have been leaked to consider it is compromised (defaults to 1)
- * @param bool|null $skipOnError Whether to ignore HTTP errors while requesting the API and thus consider the password valid (defaults to false)
- * @param string[]|null $groups
+ * @param positive-int|null $threshold The number of times the password should have been leaked to consider it is compromised (defaults to 1)
+ * @param bool|null $skipOnError Whether to ignore HTTP errors while requesting the API and thus consider the password valid (defaults to false)
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/NotNull.php b/Constraints/NotNull.php
index 28596925e..b00c72bed 100644
--- a/Constraints/NotNull.php
+++ b/Constraints/NotNull.php
@@ -31,8 +31,7 @@ class NotNull extends Constraint
public string $message = 'This value should not be null.';
/**
- * @param array|null $options
- * @param string[]|null $groups
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
@@ -41,7 +40,7 @@ public function __construct(?array $options = null, ?string $message = null, ?ar
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- parent::__construct($options ?? [], $groups, $payload);
+ parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
diff --git a/Constraints/PasswordStrength.php b/Constraints/PasswordStrength.php
index 3867cfbda..7ad2b13fd 100644
--- a/Constraints/PasswordStrength.php
+++ b/Constraints/PasswordStrength.php
@@ -40,18 +40,19 @@ final class PasswordStrength extends Constraint
public int $minScore;
/**
- * @param array|null $options
- * @param self::STRENGTH_*|null $minScore The minimum required strength of the password (defaults to {@see PasswordStrength::STRENGTH_MEDIUM})
- * @param string[]|null $groups
+ * @param self::STRENGTH_*|null $minScore The minimum required strength of the password (defaults to {@see PasswordStrength::STRENGTH_MEDIUM})
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?int $minScore = null, ?array $groups = null, mixed $payload = null, ?string $message = null)
{
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- }
- $options['minScore'] ??= self::STRENGTH_MEDIUM;
+ $options['minScore'] ??= self::STRENGTH_MEDIUM;
+ } else {
+ $minScore ??= self::STRENGTH_MEDIUM;
+ }
parent::__construct($options, $groups, $payload);
diff --git a/Constraints/Range.php b/Constraints/Range.php
index aac582430..231058aec 100644
--- a/Constraints/Range.php
+++ b/Constraints/Range.php
@@ -49,7 +49,6 @@ class Range extends Constraint
public ?string $maxPropertyPath = null;
/**
- * @param array|null $options
* @param string|null $invalidMessage The message if min and max values are numeric but the given value is not
* @param string|null $invalidDateTimeMessage The message if min and max values are PHP datetimes but the given value is not
* @param int|float|non-empty-string|null $min The minimum value, either numeric or a datetime string representation
diff --git a/Constraints/Regex.php b/Constraints/Regex.php
index 5c8501fa0..8ab6d0619 100644
--- a/Constraints/Regex.php
+++ b/Constraints/Regex.php
@@ -14,6 +14,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\InvalidArgumentException;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* Validates that a value matches a regular expression.
@@ -37,11 +38,10 @@ class Regex extends Constraint
public $normalizer;
/**
- * @param string|array|null $pattern The regular expression to match
- * @param string|null $htmlPattern The pattern to use in the HTML5 pattern attribute
- * @param bool|null $match Whether to validate the value matches the configured pattern or not (defaults to true)
- * @param string[]|null $groups
- * @param array|null $options
+ * @param string|null $pattern The regular expression to match
+ * @param string|null $htmlPattern The pattern to use in the HTML5 pattern attribute
+ * @param bool|null $match Whether to validate the value matches the configured pattern or not (defaults to true)
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(
@@ -54,22 +54,22 @@ public function __construct(
mixed $payload = null,
?array $options = null,
) {
+ if (null === $pattern && !isset($options['pattern'])) {
+ throw new MissingOptionsException(\sprintf('The options "pattern" must be set for constraint "%s".', self::class), ['pattern']);
+ }
+
if (\is_array($pattern)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($pattern, $options ?? []);
- } elseif (null !== $pattern) {
- if (\is_array($options)) {
- trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
- }
-
- $options['value'] = $pattern;
+ $pattern = $options['pattern'] ?? null;
+ } elseif (\is_array($options)) {
+ trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
parent::__construct($options, $groups, $payload);
+ $this->pattern = $pattern ?? $this->pattern;
$this->message = $message ?? $this->message;
$this->htmlPattern = $htmlPattern ?? $this->htmlPattern;
$this->match = $match ?? $this->match;
@@ -80,13 +80,27 @@ public function __construct(
}
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'pattern';
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['pattern'];
}
diff --git a/Constraints/Sequentially.php b/Constraints/Sequentially.php
index 6389ebb89..ff53be7af 100644
--- a/Constraints/Sequentially.php
+++ b/Constraints/Sequentially.php
@@ -13,6 +13,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* Use this constraint to sequentially validate nested constraints.
@@ -26,26 +27,47 @@ class Sequentially extends Composite
public array|Constraint $constraints = [];
/**
- * @param Constraint[]|array|null $constraints An array of validation constraints
- * @param string[]|null $groups
+ * @param Constraint[]|null $constraints An array of validation constraints
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null)
{
- if (\is_array($constraints) && !array_is_list($constraints)) {
+ if (null === $constraints || [] === $constraints) {
+ throw new MissingOptionsException(\sprintf('The options "constraints" must be set for constraint "%s".', self::class), ['constraints']);
+ }
+
+ if (!$constraints instanceof Constraint && !\is_array($constraints) || \is_array($constraints) && !array_is_list($constraints)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
+ $options = $constraints;
+ } else {
+ $this->constraints = $constraints;
}
- parent::__construct($constraints ?? [], $groups, $payload);
+ parent::__construct($options ?? null, $groups, $payload);
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'constraints';
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['constraints'];
}
diff --git a/Constraints/Time.php b/Constraints/Time.php
index a99702cb2..e166ec0ff 100644
--- a/Constraints/Time.php
+++ b/Constraints/Time.php
@@ -34,9 +34,8 @@ class Time extends Constraint
public string $message = 'This value is not a valid time.';
/**
- * @param array|null $options
- * @param string[]|null $groups
- * @param bool|null $withSeconds Whether to allow seconds in the given value (defaults to true)
+ * @param string[]|null $groups
+ * @param bool|null $withSeconds Whether to allow seconds in the given value (defaults to true)
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/Timezone.php b/Constraints/Timezone.php
index 93b0692ef..0a05085a1 100644
--- a/Constraints/Timezone.php
+++ b/Constraints/Timezone.php
@@ -42,11 +42,10 @@ class Timezone extends Constraint
];
/**
- * @param int|array|null $zone Restrict valid timezones to this geographical zone (defaults to {@see \DateTimeZone::ALL})
- * @param string|null $countryCode Restrict the valid timezones to this country if the zone option is {@see \DateTimeZone::PER_COUNTRY}
- * @param bool|null $intlCompatible Whether to restrict valid timezones to ones available in PHP's intl (defaults to false)
- * @param string[]|null $groups
- * @param array|null $options
+ * @param int|null $zone Restrict valid timezones to this geographical zone (defaults to {@see \DateTimeZone::ALL})
+ * @param string|null $countryCode Restrict the valid timezones to this country if the zone option is {@see \DateTimeZone::PER_COUNTRY}
+ * @param bool|null $intlCompatible Whether to restrict valid timezones to ones available in PHP's intl (defaults to false)
+ * @param string[]|null $groups
*
* @see \DateTimeZone
*/
@@ -64,18 +63,18 @@ public function __construct(
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($zone, $options ?? []);
+ $zone = null;
} elseif (null !== $zone) {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
- }
- $options['value'] = $zone;
+ $options['value'] = $zone;
+ }
}
parent::__construct($options, $groups, $payload);
+ $this->zone = $zone ?? $this->zone;
$this->message = $message ?? $this->message;
$this->countryCode = $countryCode ?? $this->countryCode;
$this->intlCompatible = $intlCompatible ?? $this->intlCompatible;
@@ -92,8 +91,15 @@ public function __construct(
}
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'zone';
}
}
diff --git a/Constraints/Traverse.php b/Constraints/Traverse.php
index d8546e323..6571b31f1 100644
--- a/Constraints/Traverse.php
+++ b/Constraints/Traverse.php
@@ -26,7 +26,7 @@ class Traverse extends Constraint
public bool $traverse = true;
/**
- * @param bool|array|null $traverse Whether to traverse the given object or not (defaults to true). Pass an associative array to configure the constraint's options (e.g. payload).
+ * @param bool|null $traverse Whether to traverse the given object or not (defaults to true). Pass an associative array to configure the constraint's options (e.g. payload).
*/
#[HasNamedArguments]
public function __construct(bool|array|null $traverse = null, mixed $payload = null)
@@ -37,13 +37,24 @@ public function __construct(bool|array|null $traverse = null, mixed $payload = n
if (\is_array($traverse)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
+ $options = $traverse;
+ $traverse = $options['traverse'] ?? null;
}
- parent::__construct($traverse, null, $payload);
+ parent::__construct($options ?? null, $payload);
+
+ $this->traverse = $traverse ?? $this->traverse;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'traverse';
}
diff --git a/Constraints/Type.php b/Constraints/Type.php
index f3fe56dbb..a2050e11d 100644
--- a/Constraints/Type.php
+++ b/Constraints/Type.php
@@ -13,6 +13,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* Validates that a value is of a specific data type.
@@ -32,25 +33,25 @@ class Type extends Constraint
public string|array|null $type = null;
/**
- * @param string|list|array|null $type The type(s) to enforce on the value
- * @param string[]|null $groups
- * @param array|null $options
+ * @param string|list|null $type The type(s) to enforce on the value
+ * @param string[]|null $groups
*/
#[HasNamedArguments]
public function __construct(string|array|null $type, ?string $message = null, ?array $groups = null, mixed $payload = null, ?array $options = null)
{
+ if (null === $type && !isset($options['type'])) {
+ throw new MissingOptionsException(\sprintf('The options "type" must be set for constraint "%s".', self::class), ['type']);
+ }
+
if (\is_array($type) && \is_string(key($type))) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
$options = array_merge($type, $options ?? []);
+ $type = $options['type'] ?? null;
} elseif (null !== $type) {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
- } else {
- $options = [];
}
-
- $options['value'] = $type;
} elseif (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
@@ -58,15 +59,30 @@ public function __construct(string|array|null $type, ?string $message = null, ?a
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
+ $this->type = $type ?? $this->type;
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getDefaultOption(): ?string
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return 'type';
}
+ /**
+ * @deprecated since Symfony 7.4
+ */
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['type'];
}
}
diff --git a/Constraints/Ulid.php b/Constraints/Ulid.php
index 91d395fd2..c9f9dbaf6 100644
--- a/Constraints/Ulid.php
+++ b/Constraints/Ulid.php
@@ -47,9 +47,8 @@ class Ulid extends Constraint
public string $format = self::FORMAT_BASE_32;
/**
- * @param array|null $options
- * @param string[]|null $groups
- * @param self::FORMAT_*|null $format
+ * @param string[]|null $groups
+ * @param self::FORMAT_*|null $format
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/Unique.php b/Constraints/Unique.php
index 1e6503785..3794b192c 100644
--- a/Constraints/Unique.php
+++ b/Constraints/Unique.php
@@ -38,9 +38,8 @@ class Unique extends Constraint
public $normalizer;
/**
- * @param array|null $options
- * @param string[]|null $groups
- * @param string[]|string|null $fields Defines the key or keys in the collection that should be checked for uniqueness (defaults to null, which ensure uniqueness for all keys)
+ * @param string[]|null $groups
+ * @param string[]|string|null $fields Defines the key or keys in the collection that should be checked for uniqueness (defaults to null, which ensure uniqueness for all keys)
*/
#[HasNamedArguments]
public function __construct(
diff --git a/Constraints/Url.php b/Constraints/Url.php
index b3e7256a0..08d391e38 100644
--- a/Constraints/Url.php
+++ b/Constraints/Url.php
@@ -40,17 +40,16 @@ class Url extends Constraint
public $normalizer;
/**
- * @param array|null $options
- * @param string[]|null $protocols The protocols considered to be valid for the URL (e.g. http, https, ftp, etc.) (defaults to ['http', 'https']
- * @param bool|null $relativeProtocol Whether to accept URL without the protocol (i.e. //example.com) (defaults to false)
- * @param string[]|null $groups
- * @param bool|null $requireTld Whether to require the URL to include a top-level domain (defaults to false)
+ * @param string[]|string|null $protocols The protocols considered to be valid for the URL (e.g. http, https, ftp, etc.) (defaults to ['http', 'https']; use '*' to allow any protocol)
+ * @param bool|null $relativeProtocol Whether to accept URL without the protocol (i.e. //example.com) (defaults to false)
+ * @param string[]|null $groups
+ * @param bool|null $requireTld Whether to require the URL to include a top-level domain (defaults to false)
*/
#[HasNamedArguments]
public function __construct(
?array $options = null,
?string $message = null,
- ?array $protocols = null,
+ array|string|null $protocols = null,
?bool $relativeProtocol = null,
?callable $normalizer = null,
?array $groups = null,
@@ -68,6 +67,10 @@ public function __construct(
trigger_deprecation('symfony/validator', '7.1', 'Not passing a value for the "requireTld" option to the Url constraint is deprecated. Its default value will change to "true".');
}
+ if (\is_string($protocols)) {
+ $protocols = (array) $protocols;
+ }
+
$this->message = $message ?? $this->message;
$this->protocols = $protocols ?? $this->protocols;
$this->relativeProtocol = $relativeProtocol ?? $this->relativeProtocol;
diff --git a/Constraints/UrlValidator.php b/Constraints/UrlValidator.php
index dc4c2486a..ad17f5d25 100644
--- a/Constraints/UrlValidator.php
+++ b/Constraints/UrlValidator.php
@@ -75,8 +75,15 @@ public function validate(mixed $value, Constraint $constraint): void
$value = ($constraint->normalizer)($value);
}
+ if (['*'] === $constraint->protocols) {
+ // Use RFC 3986 compliant scheme pattern: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ $protocols = '[a-zA-Z][a-zA-Z0-9+.-]*';
+ } else {
+ $protocols = implode('|', $constraint->protocols);
+ }
+
$pattern = $constraint->relativeProtocol ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN;
- $pattern = \sprintf($pattern, implode('|', $constraint->protocols));
+ $pattern = sprintf($pattern, $protocols);
if (!preg_match($pattern, $value)) {
$this->context->buildViolation($constraint->message)
diff --git a/Constraints/Uuid.php b/Constraints/Uuid.php
index 9c6526457..3602c2fb8 100644
--- a/Constraints/Uuid.php
+++ b/Constraints/Uuid.php
@@ -96,7 +96,6 @@ class Uuid extends Constraint
public $normalizer;
/**
- * @param array|null $options
* @param self::V*[]|self::V*|null $versions Specific UUID versions (defaults to {@see Uuid::ALL_VERSIONS})
* @param bool|null $strict Whether to force the value to follow the RFC's input format rules; pass false to allow alternate formats (defaults to true)
* @param string[]|null $groups
diff --git a/Constraints/Valid.php b/Constraints/Valid.php
index 48deae8ac..6106627c6 100644
--- a/Constraints/Valid.php
+++ b/Constraints/Valid.php
@@ -25,9 +25,8 @@ class Valid extends Constraint
public bool $traverse = true;
/**
- * @param array|null $options
- * @param string[]|null $groups
- * @param bool|null $traverse Whether to validate {@see \Traversable} objects (defaults to true)
+ * @param string[]|null $groups
+ * @param bool|null $traverse Whether to validate {@see \Traversable} objects (defaults to true)
*/
#[HasNamedArguments]
public function __construct(?array $options = null, ?array $groups = null, $payload = null, ?bool $traverse = null)
@@ -36,7 +35,7 @@ public function __construct(?array $options = null, ?array $groups = null, $payl
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
}
- parent::__construct($options ?? [], $groups, $payload);
+ parent::__construct($options, $groups, $payload);
$this->traverse = $traverse ?? $this->traverse;
}
diff --git a/Constraints/Video.php b/Constraints/Video.php
new file mode 100644
index 000000000..796dfb94e
--- /dev/null
+++ b/Constraints/Video.php
@@ -0,0 +1,254 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Process\ExecutableFinder;
+use Symfony\Component\Process\Process;
+use Symfony\Component\Validator\Attribute\HasNamedArguments;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @author Kev
+ * @author Nicolas Grekas
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Video extends File
+{
+ public const SIZE_NOT_DETECTED_ERROR = '5dab98df-43c8-481b-94f9-46a3c958285c';
+ public const TOO_WIDE_ERROR = '9e18d6a4-aeda-4644-be8e-9e29dbfd6c4a';
+ public const TOO_NARROW_ERROR = 'b267f54b-d994-46d4-9ca6-338fc4f7962f';
+ public const TOO_HIGH_ERROR = '44f4c411-0199-48c2-b597-df1f5944ccde';
+ public const TOO_LOW_ERROR = '0b6bc3ce-df90-40f9-90aa-5bbb840cb481';
+ public const TOO_FEW_PIXEL_ERROR = '510ddf98-2eda-436e-be7e-b6f107bc0e22';
+ public const TOO_MANY_PIXEL_ERROR = 'ff0a8ee8-951d-4c97-afe2-03c0d61a2a02';
+ public const RATIO_TOO_BIG_ERROR = '5e6b9c21-d4d8-444d-9f4c-e3ff1e25a9a6';
+ public const RATIO_TOO_SMALL_ERROR = '26985857-7447-49dc-b271-1477a76cc63c';
+ public const SQUARE_NOT_ALLOWED_ERROR = '18500335-b868-4056-b2a2-aa2aeeb0cbdf';
+ public const LANDSCAPE_NOT_ALLOWED_ERROR = 'cbf38fbc-04c0-457a-8c29-a6f3080e415a';
+ public const PORTRAIT_NOT_ALLOWED_ERROR = '6c3e34a8-94d5-4434-9f20-fb9c0f3ab531';
+ public const CORRUPTED_VIDEO_ERROR = '591b9c4d-d357-425f-8672-6b187816550e';
+ public const MULTIPLE_VIDEO_STREAMS_ERROR = '2d1b2b2e-3f37-4fdd-9a2a-8b6b77b2a6a3';
+ public const UNSUPPORTED_VIDEO_CODEC_ERROR = 'a9f2f6f7-2b5a-4f3c-b746-d3e2e9d1b2a1';
+ public const UNSUPPORTED_VIDEO_CONTAINER_ERROR = 'b7c9d2a4-5e1f-4aa0-8f9d-1c3e2b4a6d7e';
+
+ // Include the mapping from the base class
+
+ protected const ERROR_NAMES = [
+ self::NOT_FOUND_ERROR => 'NOT_FOUND_ERROR',
+ self::NOT_READABLE_ERROR => 'NOT_READABLE_ERROR',
+ self::EMPTY_ERROR => 'EMPTY_ERROR',
+ self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
+ self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR',
+ self::FILENAME_TOO_LONG => 'FILENAME_TOO_LONG',
+ self::SIZE_NOT_DETECTED_ERROR => 'SIZE_NOT_DETECTED_ERROR',
+ self::TOO_WIDE_ERROR => 'TOO_WIDE_ERROR',
+ self::TOO_NARROW_ERROR => 'TOO_NARROW_ERROR',
+ self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR',
+ self::TOO_LOW_ERROR => 'TOO_LOW_ERROR',
+ self::TOO_FEW_PIXEL_ERROR => 'TOO_FEW_PIXEL_ERROR',
+ self::TOO_MANY_PIXEL_ERROR => 'TOO_MANY_PIXEL_ERROR',
+ self::RATIO_TOO_BIG_ERROR => 'RATIO_TOO_BIG_ERROR',
+ self::RATIO_TOO_SMALL_ERROR => 'RATIO_TOO_SMALL_ERROR',
+ self::SQUARE_NOT_ALLOWED_ERROR => 'SQUARE_NOT_ALLOWED_ERROR',
+ self::LANDSCAPE_NOT_ALLOWED_ERROR => 'LANDSCAPE_NOT_ALLOWED_ERROR',
+ self::PORTRAIT_NOT_ALLOWED_ERROR => 'PORTRAIT_NOT_ALLOWED_ERROR',
+ self::CORRUPTED_VIDEO_ERROR => 'CORRUPTED_VIDEO_ERROR',
+ self::MULTIPLE_VIDEO_STREAMS_ERROR => 'MULTIPLE_VIDEO_STREAMS_ERROR',
+ self::UNSUPPORTED_VIDEO_CODEC_ERROR => 'UNSUPPORTED_VIDEO_CODEC_ERROR',
+ self::UNSUPPORTED_VIDEO_CONTAINER_ERROR => 'UNSUPPORTED_VIDEO_CONTAINER_ERROR',
+ ];
+
+ public array|string $mimeTypes = 'video/*';
+ public ?int $minWidth = null;
+ public ?int $maxWidth = null;
+ public ?int $maxHeight = null;
+ public ?int $minHeight = null;
+ public int|float|null $maxRatio = null;
+ public int|float|null $minRatio = null;
+ public int|float|null $minPixels = null;
+ public int|float|null $maxPixels = null;
+ public ?bool $allowSquare = true;
+ public ?bool $allowLandscape = true;
+ public ?bool $allowPortrait = true;
+ public array $allowedCodecs = ['h264', 'hevc', 'h265', 'vp9', 'av1', 'mpeg4', 'mpeg2video'];
+ public array $allowedContainers = ['mp4', 'mov', 'mkv', 'webm', 'avi'];
+
+ // The constant for a wrong MIME type is taken from the parent class.
+ public string $mimeTypesMessage = 'This file is not a valid video.';
+ public string $sizeNotDetectedMessage = 'The size of the video could not be detected.';
+ public string $maxWidthMessage = 'The video width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.';
+ public string $minWidthMessage = 'The video width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.';
+ public string $maxHeightMessage = 'The video height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.';
+ public string $minHeightMessage = 'The video height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.';
+ public string $minPixelsMessage = 'The video has too few pixels ({{ pixels }} pixels). Minimum amount expected is {{ min_pixels }} pixels.';
+ public string $maxPixelsMessage = 'The video has too many pixels ({{ pixels }} pixels). Maximum amount expected is {{ max_pixels }} pixels.';
+ public string $maxRatioMessage = 'The video ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.';
+ public string $minRatioMessage = 'The video ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.';
+ public string $allowSquareMessage = 'The video is square ({{ width }}x{{ height }}px). Square videos are not allowed.';
+ public string $allowLandscapeMessage = 'The video is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented videos are not allowed.';
+ public string $allowPortraitMessage = 'The video is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented videos are not allowed.';
+ public string $corruptedMessage = 'The video file is corrupted.';
+ public string $multipleVideoStreamsMessage = 'The video contains multiple streams. Only one stream is allowed.';
+ public string $unsupportedCodecMessage = 'Unsupported video codec "{{ codec }}".';
+ public string $unsupportedContainerMessage = 'Unsupported video container "{{ container }}".';
+
+ /**
+ * @param positive-int|string|null $maxSize The max size of the underlying file
+ * @param bool|null $binaryFormat Pass true to use binary-prefixed units (KiB, MiB, etc.) or false to use SI-prefixed units (kB, MB) in displayed messages. Pass null to guess the format from the maxSize option. (defaults to null)
+ * @param non-empty-string[]|null $mimeTypes Acceptable media types
+ * @param positive-int|null $filenameMaxLength Maximum length of the file name
+ * @param string|null $disallowEmptyMessage Enable empty upload validation with this message in case of error
+ * @param string|null $uploadIniSizeErrorMessage Message if the file size exceeds the max size configured in php.ini
+ * @param string|null $uploadFormSizeErrorMessage Message if the file size exceeds the max size configured in the HTML input field
+ * @param string|null $uploadPartialErrorMessage Message if the file is only partially uploaded
+ * @param string|null $uploadNoTmpDirErrorMessage Message if there is no upload_tmp_dir in php.ini
+ * @param string|null $uploadCantWriteErrorMessage Message if the uploaded file can not be stored in the temporary directory
+ * @param string|null $uploadErrorMessage Message if an unknown error occurred on upload
+ * @param string[]|null $groups
+ * @param int<0, int>|null $minWidth Minimum video width
+ * @param positive-int|null $maxWidth Maximum video width
+ * @param positive-int|null $maxHeight Maximum video height
+ * @param int<0, int>|null $minHeight Minimum video weight
+ * @param positive-int|float|null $maxRatio Maximum video ratio
+ * @param int<0, max>|float|null $minRatio Minimum video ratio
+ * @param int<0, max>|float|null $minPixels Minimum amount of pixels
+ * @param positive-int|float|null $maxPixels Maximum amount of pixels
+ * @param bool|null $allowSquare Whether to allow a square video (defaults to true)
+ * @param bool|null $allowLandscape Whether to allow a landscape video (defaults to true)
+ * @param bool|null $allowPortrait Whether to allow a portrait video (defaults to true)
+ * @param string|null $sizeNotDetectedMessage Message if the system can not determine video size and there is a size constraint to validate
+ * @param string[]|null $allowedCodecs Allowed codec names
+ * @param string[]|null $allowedContainers Allowed container names
+ *
+ * @see https://www.iana.org/assignments/media-types/media-types.xhtml Existing media types
+ */
+ #[HasNamedArguments]
+ public function __construct(
+ int|string|null $maxSize = null,
+ ?bool $binaryFormat = null,
+ array|string|null $mimeTypes = null,
+ ?int $filenameMaxLength = null,
+ ?int $minWidth = null,
+ ?int $maxWidth = null,
+ ?int $maxHeight = null,
+ ?int $minHeight = null,
+ int|float|null $maxRatio = null,
+ int|float|null $minRatio = null,
+ int|float|null $minPixels = null,
+ int|float|null $maxPixels = null,
+ ?bool $allowSquare = null,
+ ?bool $allowLandscape = null,
+ ?bool $allowPortrait = null,
+ ?array $allowedCodecs = null,
+ ?array $allowedContainers = null,
+ ?string $notFoundMessage = null,
+ ?string $notReadableMessage = null,
+ ?string $maxSizeMessage = null,
+ ?string $mimeTypesMessage = null,
+ ?string $disallowEmptyMessage = null,
+ ?string $filenameTooLongMessage = null,
+ ?string $uploadIniSizeErrorMessage = null,
+ ?string $uploadFormSizeErrorMessage = null,
+ ?string $uploadPartialErrorMessage = null,
+ ?string $uploadNoFileErrorMessage = null,
+ ?string $uploadNoTmpDirErrorMessage = null,
+ ?string $uploadCantWriteErrorMessage = null,
+ ?string $uploadExtensionErrorMessage = null,
+ ?string $uploadErrorMessage = null,
+ ?string $sizeNotDetectedMessage = null,
+ ?string $maxWidthMessage = null,
+ ?string $minWidthMessage = null,
+ ?string $maxHeightMessage = null,
+ ?string $minHeightMessage = null,
+ ?string $minPixelsMessage = null,
+ ?string $maxPixelsMessage = null,
+ ?string $maxRatioMessage = null,
+ ?string $minRatioMessage = null,
+ ?string $allowSquareMessage = null,
+ ?string $allowLandscapeMessage = null,
+ ?string $allowPortraitMessage = null,
+ ?string $corruptedMessage = null,
+ ?string $multipleVideoStreamsMessage = null,
+ ?string $unsupportedCodecMessage = null,
+ ?string $unsupportedContainerMessage = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ static $hasFfprobe;
+ if (!$hasFfprobe) {
+ if (!class_exists(Process::class)) {
+ throw new LogicException('The Process component is required to use the Video constraint. Try running "composer require symfony/process".');
+ }
+ if (!$hasFfprobe ??= (new ExecutableFinder())->find('ffprobe')) {
+ throw new LogicException('The ffprobe binary is required to use the Video constraint.');
+ }
+ }
+
+ parent::__construct(
+ null,
+ $maxSize,
+ $binaryFormat,
+ $mimeTypes,
+ $filenameMaxLength,
+ $notFoundMessage,
+ $notReadableMessage,
+ $maxSizeMessage,
+ $mimeTypesMessage,
+ $disallowEmptyMessage,
+ $filenameTooLongMessage,
+ $uploadIniSizeErrorMessage,
+ $uploadFormSizeErrorMessage,
+ $uploadPartialErrorMessage,
+ $uploadNoFileErrorMessage,
+ $uploadNoTmpDirErrorMessage,
+ $uploadCantWriteErrorMessage,
+ $uploadExtensionErrorMessage,
+ $uploadErrorMessage,
+ $groups,
+ $payload
+ );
+
+ $this->minWidth = $minWidth ?? $this->minWidth;
+ $this->maxWidth = $maxWidth ?? $this->maxWidth;
+ $this->maxHeight = $maxHeight ?? $this->maxHeight;
+ $this->minHeight = $minHeight ?? $this->minHeight;
+ $this->maxRatio = $maxRatio ?? $this->maxRatio;
+ $this->minRatio = $minRatio ?? $this->minRatio;
+ $this->minPixels = $minPixels ?? $this->minPixels;
+ $this->maxPixels = $maxPixels ?? $this->maxPixels;
+ $this->allowSquare = $allowSquare ?? $this->allowSquare;
+ $this->allowLandscape = $allowLandscape ?? $this->allowLandscape;
+ $this->allowPortrait = $allowPortrait ?? $this->allowPortrait;
+ $this->allowedCodecs = $allowedCodecs ?? $this->allowedCodecs;
+ $this->allowedContainers = $allowedContainers ?? $this->allowedContainers;
+ $this->sizeNotDetectedMessage = $sizeNotDetectedMessage ?? $this->sizeNotDetectedMessage;
+ $this->maxWidthMessage = $maxWidthMessage ?? $this->maxWidthMessage;
+ $this->minWidthMessage = $minWidthMessage ?? $this->minWidthMessage;
+ $this->maxHeightMessage = $maxHeightMessage ?? $this->maxHeightMessage;
+ $this->minHeightMessage = $minHeightMessage ?? $this->minHeightMessage;
+ $this->minPixelsMessage = $minPixelsMessage ?? $this->minPixelsMessage;
+ $this->maxPixelsMessage = $maxPixelsMessage ?? $this->maxPixelsMessage;
+ $this->maxRatioMessage = $maxRatioMessage ?? $this->maxRatioMessage;
+ $this->minRatioMessage = $minRatioMessage ?? $this->minRatioMessage;
+ $this->allowSquareMessage = $allowSquareMessage ?? $this->allowSquareMessage;
+ $this->allowLandscapeMessage = $allowLandscapeMessage ?? $this->allowLandscapeMessage;
+ $this->allowPortraitMessage = $allowPortraitMessage ?? $this->allowPortraitMessage;
+ $this->corruptedMessage = $corruptedMessage ?? $this->corruptedMessage;
+ $this->multipleVideoStreamsMessage = $multipleVideoStreamsMessage ?? $this->multipleVideoStreamsMessage;
+ $this->unsupportedCodecMessage = $unsupportedCodecMessage ?? $this->unsupportedCodecMessage;
+ $this->unsupportedContainerMessage = $unsupportedContainerMessage ?? $this->unsupportedContainerMessage;
+
+ if (!\in_array('video/*', (array) $this->mimeTypes, true) && null === $mimeTypesMessage) {
+ $this->mimeTypesMessage = 'The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.';
+ }
+ }
+}
diff --git a/Constraints/VideoValidator.php b/Constraints/VideoValidator.php
new file mode 100644
index 000000000..afbe30b5f
--- /dev/null
+++ b/Constraints/VideoValidator.php
@@ -0,0 +1,265 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Process\Process;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Kev
+ * @author Nicolas Grekas
+ */
+class VideoValidator extends FileValidator
+{
+ public function validate(mixed $value, Constraint $constraint): void
+ {
+ if (!$constraint instanceof Video) {
+ throw new UnexpectedTypeException($constraint, Video::class);
+ }
+
+ $violations = \count($this->context->getViolations());
+
+ parent::validate($value, $constraint);
+
+ $failed = \count($this->context->getViolations()) !== $violations;
+
+ if ($failed || null === $value || '' === $value) {
+ return;
+ }
+
+ if (null === $constraint->minWidth && null === $constraint->maxWidth
+ && null === $constraint->minHeight && null === $constraint->maxHeight
+ && null === $constraint->minPixels && null === $constraint->maxPixels
+ && null === $constraint->minRatio && null === $constraint->maxRatio
+ && $constraint->allowSquare && $constraint->allowLandscape && $constraint->allowPortrait
+ ) {
+ return;
+ }
+
+ $process = new Process([
+ 'ffprobe',
+ '-v', 'error',
+ '-select_streams', 'v',
+ '-show_entries', 'stream=index,codec_type,codec_name,width,height',
+ '-show_entries', 'format=format_name',
+ '-of', 'json',
+ (string) $value,
+ ]);
+ $process->run();
+
+ if (!$process->isSuccessful()) {
+ $this->context->buildViolation($constraint->corruptedMessage)
+ ->setCode(Video::CORRUPTED_VIDEO_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $meta = json_decode($process->getOutput(), true) ?: [];
+ $streams = $meta['streams'] ?? [];
+ $formats = explode(',', strtolower($meta['format']['format_name'] ?? 'unknown'));
+
+ if (!($streams[0]['width'] ?? false) || !($streams[0]['height'] ?? false)) {
+ $this->context->buildViolation($constraint->sizeNotDetectedMessage)
+ ->setCode(Video::SIZE_NOT_DETECTED_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $width = $streams[0]['width'];
+ $height = $streams[0]['height'];
+
+ if (1 !== \count($streams)) {
+ $this->context->buildViolation($constraint->multipleVideoStreamsMessage)
+ ->setCode(Video::MULTIPLE_VIDEO_STREAMS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if ($constraint->allowedCodecs) {
+ foreach ($streams as $stream) {
+ $codec = strtolower($stream['codec_name'] ?? 'unknown');
+ if (!\in_array($codec, $constraint->allowedCodecs, true)) {
+ $this->context->buildViolation($constraint->unsupportedCodecMessage)
+ ->setParameter('{{ codec }}', $codec)
+ ->setCode(Video::UNSUPPORTED_VIDEO_CODEC_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+ }
+
+ if ($constraint->allowedContainers && !array_intersect($formats, $constraint->allowedContainers)) {
+ $this->context->buildViolation($constraint->unsupportedContainerMessage)
+ ->setParameter('{{ container }}', $formats[0])
+ ->setCode(Video::UNSUPPORTED_VIDEO_CONTAINER_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if ($constraint->minWidth) {
+ if ($constraint->minWidth < 0) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum width.', $constraint->minWidth));
+ }
+
+ if ($width < $constraint->minWidth) {
+ $this->context->buildViolation($constraint->minWidthMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ min_width }}', $constraint->minWidth)
+ ->setCode(Video::TOO_NARROW_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ if ($constraint->maxWidth) {
+ if ($constraint->maxWidth < 0) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum width.', $constraint->maxWidth));
+ }
+
+ if ($width > $constraint->maxWidth) {
+ $this->context->buildViolation($constraint->maxWidthMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ max_width }}', $constraint->maxWidth)
+ ->setCode(Video::TOO_WIDE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ if ($constraint->minHeight) {
+ if ($constraint->minHeight < 0) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum height.', $constraint->minHeight));
+ }
+
+ if ($height < $constraint->minHeight) {
+ $this->context->buildViolation($constraint->minHeightMessage)
+ ->setParameter('{{ height }}', $height)
+ ->setParameter('{{ min_height }}', $constraint->minHeight)
+ ->setCode(Video::TOO_LOW_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ if ($constraint->maxHeight) {
+ if ($constraint->maxHeight < 0) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum height.', $constraint->maxHeight));
+ }
+
+ if ($height > $constraint->maxHeight) {
+ $this->context->buildViolation($constraint->maxHeightMessage)
+ ->setParameter('{{ height }}', $height)
+ ->setParameter('{{ max_height }}', $constraint->maxHeight)
+ ->setCode(Video::TOO_HIGH_ERROR)
+ ->addViolation();
+ }
+ }
+
+ $pixels = $width * $height;
+
+ if (null !== $constraint->minPixels) {
+ if ($constraint->minPixels < 0) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum amount of pixels.', $constraint->minPixels));
+ }
+
+ if ($pixels < $constraint->minPixels) {
+ $this->context->buildViolation($constraint->minPixelsMessage)
+ ->setParameter('{{ pixels }}', $pixels)
+ ->setParameter('{{ min_pixels }}', $constraint->minPixels)
+ ->setParameter('{{ height }}', $height)
+ ->setParameter('{{ width }}', $width)
+ ->setCode(Video::TOO_FEW_PIXEL_ERROR)
+ ->addViolation();
+ }
+ }
+
+ if (null !== $constraint->maxPixels) {
+ if ($constraint->maxPixels < 0) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum amount of pixels.', $constraint->maxPixels));
+ }
+
+ if ($pixels > $constraint->maxPixels) {
+ $this->context->buildViolation($constraint->maxPixelsMessage)
+ ->setParameter('{{ pixels }}', $pixels)
+ ->setParameter('{{ max_pixels }}', $constraint->maxPixels)
+ ->setParameter('{{ height }}', $height)
+ ->setParameter('{{ width }}', $width)
+ ->setCode(Video::TOO_MANY_PIXEL_ERROR)
+ ->addViolation();
+ }
+ }
+
+ $ratio = round($height > 0 ? $width / $height : 0, 2);
+
+ if (null !== $constraint->minRatio) {
+ if (!is_numeric((string) $constraint->minRatio)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum ratio.', $constraint->minRatio));
+ }
+
+ if ($ratio < round($constraint->minRatio, 2)) {
+ $this->context->buildViolation($constraint->minRatioMessage)
+ ->setParameter('{{ ratio }}', $ratio)
+ ->setParameter('{{ min_ratio }}', round($constraint->minRatio, 2))
+ ->setCode(Video::RATIO_TOO_SMALL_ERROR)
+ ->addViolation();
+ }
+ }
+
+ if (null !== $constraint->maxRatio) {
+ if (!is_numeric((string) $constraint->maxRatio)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum ratio.', $constraint->maxRatio));
+ }
+
+ if ($ratio > round($constraint->maxRatio, 2)) {
+ $this->context->buildViolation($constraint->maxRatioMessage)
+ ->setParameter('{{ ratio }}', $ratio)
+ ->setParameter('{{ max_ratio }}', round($constraint->maxRatio, 2))
+ ->setCode(Video::RATIO_TOO_BIG_ERROR)
+ ->addViolation();
+ }
+ }
+
+ if (!$constraint->allowSquare && $width == $height) {
+ $this->context->buildViolation($constraint->allowSquareMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ height }}', $height)
+ ->setCode(Video::SQUARE_NOT_ALLOWED_ERROR)
+ ->addViolation();
+ }
+
+ if (!$constraint->allowLandscape && $width > $height) {
+ $this->context->buildViolation($constraint->allowLandscapeMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ height }}', $height)
+ ->setCode(Video::LANDSCAPE_NOT_ALLOWED_ERROR)
+ ->addViolation();
+ }
+
+ if (!$constraint->allowPortrait && $width < $height) {
+ $this->context->buildViolation($constraint->allowPortraitMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ height }}', $height)
+ ->setCode(Video::PORTRAIT_NOT_ALLOWED_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/Constraints/When.php b/Constraints/When.php
index 74601c1d1..b7482f938 100644
--- a/Constraints/When.php
+++ b/Constraints/When.php
@@ -16,6 +16,7 @@
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* Conditionally apply validation constraints based on an expression using the ExpressionLanguage syntax.
@@ -31,12 +32,11 @@ class When extends Composite
public array|Constraint $otherwise = [];
/**
- * @param string|Expression|array|\Closure(object): bool $expression The condition to evaluate, either as a closure or using the ExpressionLanguage syntax
- * @param Constraint[]|Constraint|null $constraints One or multiple constraints that are applied if the expression returns true
- * @param array|null $values The values of the custom variables used in the expression (defaults to [])
- * @param string[]|null $groups
- * @param array|null $options
- * @param Constraint[]|Constraint $otherwise One or multiple constraints that are applied if the expression returns false
+ * @param string|Expression|\Closure(object): bool $expression The condition to evaluate, either as a closure or using the ExpressionLanguage syntax
+ * @param Constraint[]|Constraint|null $constraints One or multiple constraints that are applied if the expression returns true
+ * @param array|null $values The values of the custom variables used in the expression (defaults to [])
+ * @param string[]|null $groups
+ * @param Constraint[]|Constraint $otherwise One or multiple constraints that are applied if the expression returns false
*/
#[HasNamedArguments]
public function __construct(string|Expression|array|\Closure $expression, array|Constraint|null $constraints = null, ?array $values = null, ?array $groups = null, $payload = null, ?array $options = null, array|Constraint $otherwise = [])
@@ -52,15 +52,21 @@ public function __construct(string|Expression|array|\Closure $expression, array|
} else {
if (\is_array($options)) {
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
+
+ $options['expression'] = $expression;
+ if (null !== $constraints) {
+ $options['constraints'] = $constraints;
+ }
+ $options['otherwise'] = $otherwise;
} else {
- $options = [];
- }
+ if (null === $constraints) {
+ throw new MissingOptionsException(\sprintf('The options "constraints" must be set for constraint "%s".', self::class), ['constraints']);
+ }
- $options['expression'] = $expression;
- if (null !== $constraints) {
- $options['constraints'] = $constraints;
+ $this->expression = $expression;
+ $this->constraints = $constraints;
+ $this->otherwise = $otherwise;
}
- $options['otherwise'] = $otherwise;
}
if (!\is_array($options['constraints'] ?? [])) {
@@ -71,21 +77,17 @@ public function __construct(string|Expression|array|\Closure $expression, array|
$options['otherwise'] = [$options['otherwise']];
}
- if (null !== $groups) {
- $options['groups'] = $groups;
- }
-
- if (null !== $payload) {
- $options['payload'] = $payload;
- }
-
- parent::__construct($options);
+ parent::__construct($options, $groups, $payload);
$this->values = $values ?? $this->values;
}
public function getRequiredOptions(): array
{
+ if (0 === \func_num_args() || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
return ['expression', 'constraints'];
}
diff --git a/DependencyInjection/AttributeMetadataPass.php b/DependencyInjection/AttributeMetadataPass.php
new file mode 100644
index 000000000..a337504a5
--- /dev/null
+++ b/DependencyInjection/AttributeMetadataPass.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\Validator\Exception\MappingException;
+
+/**
+ * @author Nicolas Grekas
+ */
+final class AttributeMetadataPass implements CompilerPassInterface
+{
+ public function process(ContainerBuilder $container): void
+ {
+ if (!$container->hasDefinition('validator.builder')) {
+ return;
+ }
+
+ $resolve = $container->getParameterBag()->resolveValue(...);
+ $mappedClasses = [];
+ foreach ($container->getDefinitions() as $id => $definition) {
+ if (!$definition->hasTag('validator.attribute_metadata')) {
+ continue;
+ }
+ if (!$definition->hasTag('container.excluded')) {
+ throw new InvalidArgumentException(\sprintf('The resource "%s" tagged "validator.attribute_metadata" is missing the "container.excluded" tag.', $id));
+ }
+ $class = $resolve($definition->getClass());
+ foreach ($definition->getTag('validator.attribute_metadata') as $attributes) {
+ if ($class !== $for = $attributes['for'] ?? $class) {
+ $this->checkSourceMapsToTarget($container, $class, $for);
+ }
+
+ $mappedClasses[$for][$class] = true;
+ }
+ }
+
+ if (!$mappedClasses) {
+ return;
+ }
+
+ ksort($mappedClasses);
+
+ $container->getDefinition('validator.builder')
+ ->addMethodCall('addAttributeMappings', [array_map('array_keys', $mappedClasses)]);
+ }
+
+ private function checkSourceMapsToTarget(ContainerBuilder $container, string $source, string $target): void
+ {
+ $source = $container->getReflectionClass($source);
+ $target = $container->getReflectionClass($target);
+
+ foreach ($source->getProperties() as $p) {
+ if ($p->class === $source->name && !($target->hasProperty($p->name) && $target->getProperty($p->name)->class === $target->name)) {
+ throw new MappingException(\sprintf('The property "%s" on "%s" is not present on "%s".', $p->name, $source->name, $target->name));
+ }
+ }
+
+ foreach ($source->getMethods() as $m) {
+ if ($m->class === $source->name && !($target->hasMethod($m->name) && $target->getMethod($m->name)->class === $target->name)) {
+ throw new MappingException(\sprintf('The method "%s" on "%s" is not present on "%s".', $m->name, $source->name, $target->name));
+ }
+ }
+ }
+}
diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php
index d812255c4..b9ac2ec41 100644
--- a/Mapping/ClassMetadata.php
+++ b/Mapping/ClassMetadata.php
@@ -31,67 +31,36 @@
*/
class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
{
- /**
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getClassName()} instead.
- */
- public string $name;
-
- /**
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getDefaultGroup()} instead.
- */
- public string $defaultGroup;
+ private string $name;
+ private string $defaultGroup;
/**
* @var MemberMetadata[][]
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getPropertyMetadata()} instead.
*/
- public array $members = [];
+ private array $members = [];
/**
* @var PropertyMetadata[]
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getPropertyMetadata()} instead.
*/
- public array $properties = [];
+ private array $properties = [];
/**
* @var GetterMetadata[]
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getPropertyMetadata()} instead.
*/
- public array $getters = [];
+ private array $getters = [];
- /**
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getGroupSequence()} instead.
- */
- public ?GroupSequence $groupSequence = null;
+ private ?GroupSequence $groupSequence = null;
+ private bool $groupSequenceProvider = false;
+ private ?string $groupProvider = null;
/**
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link isGroupSequenceProvider()} instead.
- */
- public bool $groupSequenceProvider = false;
-
- /**
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getGroupProvider()} instead.
+ * The strategy for cascading objects.
+ *
+ * By default, objects are not cascaded.
+ *
+ * @var CascadingStrategy::*
*/
- public ?string $groupProvider = null;
+ private int $cascadingStrategy = CascadingStrategy::NONE;
/**
* The strategy for traversing traversable objects.
@@ -99,12 +68,8 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
* By default, only instances of {@link \Traversable} are traversed.
*
* @var TraversalStrategy::*
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getTraversalStrategy()} instead.
*/
- public int $traversalStrategy = TraversalStrategy::IMPLICIT;
+ private int $traversalStrategy = TraversalStrategy::IMPLICIT;
private \ReflectionClass $reflClass;
@@ -119,14 +84,52 @@ public function __construct(string $class)
}
}
- public function __sleep(): array
+ public function __serialize(): array
{
- $parentProperties = parent::__sleep();
+ if (self::class === (new \ReflectionMethod($this, '__sleep'))->class || self::class !== (new \ReflectionMethod($this, '__serialize'))->class) {
+ return array_filter([
+ 'cascadingStrategy' => CascadingStrategy::NONE !== $this->cascadingStrategy ? $this->cascadingStrategy : null,
+ 'traversalStrategy' => TraversalStrategy::IMPLICIT !== $this->traversalStrategy ? $this->traversalStrategy : null,
+ ] + parent::__serialize() + [
+ 'getters' => $this->getters,
+ 'groupSequence' => $this->groupSequence,
+ 'groupSequenceProvider' => $this->groupSequenceProvider,
+ 'groupProvider' => $this->groupProvider,
+ 'members' => $this->members,
+ 'name' => $this->name,
+ 'properties' => $this->properties,
+ 'defaultGroup' => $this->defaultGroup,
+ ]);
+ }
- // Don't store the cascading strategy. Classes never cascade.
- unset($parentProperties[array_search('cascadingStrategy', $parentProperties)]);
+ trigger_deprecation('symfony/validator', '7.4', 'Implementing "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
- return array_merge($parentProperties, [
+ $data = [];
+ foreach ($this->__sleep() as $key) {
+ try {
+ if (($r = new \ReflectionProperty($this, $key))->isInitialized($this)) {
+ $data[$key] = $r->getValue($this);
+ }
+ } catch (\ReflectionException) {
+ $data[$key] = $this->$key;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @deprecated since Symfony 7.4, will be removed in 8.0
+ */
+ public function __sleep(): array
+ {
+ trigger_deprecation('symfony/validator', '7.4', 'Calling "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
+
+ return [
+ 'constraints',
+ 'constraintsByGroup',
+ 'traversalStrategy',
+ 'autoMappingStrategy',
'getters',
'groupSequence',
'groupSequenceProvider',
@@ -135,7 +138,7 @@ public function __sleep(): array
'name',
'properties',
'defaultGroup',
- ]);
+ ];
}
public function getClassName(): string
@@ -178,13 +181,7 @@ public function addConstraint(Constraint $constraint): static
$this->checkConstraint($constraint);
if ($constraint instanceof Traverse) {
- if ($constraint->traverse) {
- // If traverse is true, traversal should be explicitly enabled
- $this->traversalStrategy = TraversalStrategy::TRAVERSE;
- } else {
- // If traverse is false, traversal should be explicitly disabled
- $this->traversalStrategy = TraversalStrategy::NONE;
- }
+ $this->traversalStrategy = $constraint->traverse ? TraversalStrategy::TRAVERSE : TraversalStrategy::NONE;
// The constraint is not added
return $this;
@@ -209,6 +206,14 @@ public function addConstraint(Constraint $constraint): static
$constraint->addImplicitGroupName($this->getDefaultGroup());
+ if ($constraint instanceof Valid && null === $constraint->groups) {
+ $this->cascadingStrategy = CascadingStrategy::CASCADE;
+ $this->traversalStrategy = $constraint->traverse ? TraversalStrategy::IMPLICIT : TraversalStrategy::NONE;
+
+ // The constraint is not added
+ return $this;
+ }
+
parent::addConstraint($constraint);
return $this;
@@ -338,11 +343,8 @@ public function mergeConstraints(self $source): void
$member = clone $member;
foreach ($member->getConstraints() as $constraint) {
- if (\in_array($constraint::DEFAULT_GROUP, $constraint->groups, true)) {
- $member->constraintsByGroup[$this->getDefaultGroup()][] = $constraint;
- }
-
$constraint->addImplicitGroupName($this->getDefaultGroup());
+ $member->addConstraint($constraint);
}
if ($member instanceof MemberMetadata && !$member->isPrivate($this->name)) {
@@ -464,6 +466,11 @@ public function getCascadingStrategy(): int
return $this->cascadingStrategy;
}
+ public function getTraversalStrategy(): int
+ {
+ return $this->traversalStrategy;
+ }
+
private function addPropertyMetadata(PropertyMetadataInterface $metadata): void
{
$property = $metadata->getPropertyName();
diff --git a/Mapping/GenericMetadata.php b/Mapping/GenericMetadata.php
index 43992255c..ce743e84b 100644
--- a/Mapping/GenericMetadata.php
+++ b/Mapping/GenericMetadata.php
@@ -30,21 +30,13 @@ class GenericMetadata implements MetadataInterface
{
/**
* @var Constraint[]
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getConstraints()} and {@link findConstraints()} instead.
*/
- public array $constraints = [];
+ private array $constraints = [];
/**
* @var array
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link findConstraints()} instead.
*/
- public array $constraintsByGroup = [];
+ private array $constraintsByGroup = [];
/**
* The strategy for cascading objects.
@@ -52,12 +44,8 @@ class GenericMetadata implements MetadataInterface
* By default, objects are not cascaded.
*
* @var CascadingStrategy::*
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getCascadingStrategy()} instead.
*/
- public int $cascadingStrategy = CascadingStrategy::NONE;
+ private int $cascadingStrategy = CascadingStrategy::NONE;
/**
* The strategy for traversing traversable objects.
@@ -65,31 +53,51 @@ class GenericMetadata implements MetadataInterface
* By default, traversable objects are not traversed.
*
* @var TraversalStrategy::*
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getTraversalStrategy()} instead.
*/
- public int $traversalStrategy = TraversalStrategy::NONE;
+ private int $traversalStrategy = TraversalStrategy::NONE;
/**
* Is auto-mapping enabled?
*
* @var AutoMappingStrategy::*
- *
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getAutoMappingStrategy()} instead.
*/
- public int $autoMappingStrategy = AutoMappingStrategy::NONE;
+ private int $autoMappingStrategy = AutoMappingStrategy::NONE;
+
+ public function __serialize(): array
+ {
+ if (self::class === (new \ReflectionMethod($this, '__sleep'))->class || self::class !== (new \ReflectionMethod($this, '__serialize'))->class) {
+ return array_filter([
+ 'constraints' => $this->constraints,
+ 'constraintsByGroup' => $this->constraintsByGroup,
+ 'cascadingStrategy' => CascadingStrategy::NONE !== $this->cascadingStrategy ? $this->cascadingStrategy : null,
+ 'traversalStrategy' => TraversalStrategy::NONE !== $this->traversalStrategy ? $this->traversalStrategy : null,
+ 'autoMappingStrategy' => AutoMappingStrategy::NONE !== $this->autoMappingStrategy ? $this->autoMappingStrategy : null,
+ ]);
+ }
+
+ trigger_deprecation('symfony/validator', '7.4', 'Implementing "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
+
+ $data = [];
+ foreach ($this->__sleep() as $key) {
+ try {
+ if (($r = new \ReflectionProperty($this, $key))->isInitialized($this)) {
+ $data[$key] = $r->getValue($this);
+ }
+ } catch (\ReflectionException) {
+ $data[$key] = $this->$key;
+ }
+ }
+
+ return $data;
+ }
/**
- * Returns the names of the properties that should be serialized.
- *
- * @return string[]
+ * @deprecated since Symfony 7.4, will be replaced by `__serialize()` in 8.0
*/
public function __sleep(): array
{
+ trigger_deprecation('symfony/validator', '7.4', 'Calling "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
+
return [
'constraints',
'constraintsByGroup',
@@ -99,9 +107,6 @@ public function __sleep(): array
];
}
- /**
- * Clones this object.
- */
public function __clone()
{
$constraints = $this->constraints;
@@ -138,13 +143,9 @@ public function addConstraint(Constraint $constraint): static
if ($constraint instanceof Valid && null === $constraint->groups) {
$this->cascadingStrategy = CascadingStrategy::CASCADE;
+ $this->traversalStrategy = $constraint->traverse ? TraversalStrategy::IMPLICIT : TraversalStrategy::NONE;
- if ($constraint->traverse) {
- $this->traversalStrategy = TraversalStrategy::IMPLICIT;
- } else {
- $this->traversalStrategy = TraversalStrategy::NONE;
- }
-
+ // The constraint is not added
return $this;
}
@@ -155,19 +156,23 @@ public function addConstraint(Constraint $constraint): static
return $this;
}
- $this->constraints[] = $constraint;
+ if (!\in_array($constraint, $this->constraints, true)) {
+ $this->constraints[] = $constraint;
+ }
foreach ($constraint->groups as $group) {
- $this->constraintsByGroup[$group][] = $constraint;
+ if (!\in_array($constraint, $this->constraintsByGroup[$group] ??= [], true)) {
+ $this->constraintsByGroup[$group][] = $constraint;
+ }
}
return $this;
}
/**
- * Adds an list of constraints.
+ * Adds a list of constraints.
*
- * @param Constraint[] $constraints The constraints to add
+ * @param Constraint[] $constraints
*
* @return $this
*/
diff --git a/Mapping/Loader/AbstractLoader.php b/Mapping/Loader/AbstractLoader.php
index ebff64fd5..d6b1ab777 100644
--- a/Mapping/Loader/AbstractLoader.php
+++ b/Mapping/Loader/AbstractLoader.php
@@ -94,10 +94,16 @@ protected function newConstraint(string $name, mixed $options = null): Constrain
}
if (1 === \count($options) && isset($options['value'])) {
+ if (\func_num_args() < 3 || !func_get_arg(2)) {
+ trigger_deprecation('symfony/validator', '7.4', 'Using the "value" option to configure the "%s" constraint is deprecated.', $className);
+ }
+
return new $className($options['value']);
}
if (array_is_list($options)) {
+ trigger_deprecation('symfony/validator', '7.4', 'Configuring the "%s" without passing its option names is deprecated.', $className);
+
return new $className($options);
}
@@ -105,6 +111,8 @@ protected function newConstraint(string $name, mixed $options = null): Constrain
return new $className(...$options);
} catch (\Error $e) {
if (str_starts_with($e->getMessage(), 'Unknown named parameter ')) {
+ trigger_deprecation('symfony/validator', '7.4', 'Using option names not matching the named arguments of the "%s" constraint is deprecated.', $className);
+
return new $className($options);
}
diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php
index ddf0b78b7..0852823aa 100644
--- a/Mapping/Loader/AttributeLoader.php
+++ b/Mapping/Loader/AttributeLoader.php
@@ -27,9 +27,40 @@
*/
class AttributeLoader implements LoaderInterface
{
+ /**
+ * @param array $mappedClasses
+ */
+ public function __construct(
+ private bool $allowAnyClass = true,
+ private array $mappedClasses = [],
+ ) {
+ }
+
+ /**
+ * @return class-string[]
+ */
+ public function getMappedClasses(): array
+ {
+ return array_keys($this->mappedClasses);
+ }
+
public function loadClassMetadata(ClassMetadata $metadata): bool
{
- $reflClass = $metadata->getReflectionClass();
+ if (!$sourceClasses = $this->mappedClasses[$metadata->getClassName()] ??= $this->allowAnyClass ? [$metadata->getClassName()] : []) {
+ return false;
+ }
+
+ $success = false;
+ foreach ($sourceClasses as $sourceClass) {
+ $reflClass = $metadata->getClassName() === $sourceClass ? $metadata->getReflectionClass() : new \ReflectionClass($sourceClass);
+ $success = $this->doLoadClassMetadata($reflClass, $metadata) || $success;
+ }
+
+ return $success;
+ }
+
+ public function doLoadClassMetadata(\ReflectionClass $reflClass, ClassMetadata $metadata): bool
+ {
$className = $reflClass->name;
$success = false;
diff --git a/Mapping/Loader/XmlFileLoader.php b/Mapping/Loader/XmlFileLoader.php
index 83a14b2e8..25af501d8 100644
--- a/Mapping/Loader/XmlFileLoader.php
+++ b/Mapping/Loader/XmlFileLoader.php
@@ -26,7 +26,7 @@ class XmlFileLoader extends FileLoader
/**
* The XML nodes of the mapping file.
*
- * @var \SimpleXMLElement[]
+ * @var array
*/
protected array $classes;
@@ -55,7 +55,7 @@ public function loadClassMetadata(ClassMetadata $metadata): bool
/**
* Return the names of the classes mapped in this file.
*
- * @return string[]
+ * @return class-string[]
*/
public function getMappedClasses(): array
{
@@ -80,6 +80,8 @@ protected function parseConstraints(\SimpleXMLElement $nodes): array
foreach ($nodes as $node) {
if (\count($node) > 0) {
if (\count($node->value) > 0) {
+ trigger_deprecation('symfony/validator', '7.4', 'Using the "value" XML element to configure an option for the "%s" is deprecated. Use the "option" element instead.', (string) $node['name']);
+
$options = [
'value' => $this->parseValues($node->value),
];
@@ -100,7 +102,7 @@ protected function parseConstraints(\SimpleXMLElement $nodes): array
$options['groups'] = (array) $options['groups'];
}
- $constraints[] = $this->newConstraint((string) $node['name'], $options);
+ $constraints[] = $this->newConstraint((string) $node['name'], $options, true);
}
return $constraints;
diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php
index 78d1b0ef9..9c168aff6 100644
--- a/Mapping/Loader/YamlFileLoader.php
+++ b/Mapping/Loader/YamlFileLoader.php
@@ -56,7 +56,7 @@ public function loadClassMetadata(ClassMetadata $metadata): bool
/**
* Return the names of the classes mapped in this file.
*
- * @return string[]
+ * @return class-string[]
*/
public function getMappedClasses(): array
{
@@ -87,12 +87,14 @@ protected function parseNodes(array $nodes): array
}
if (null !== $options && (!\is_array($options) || array_is_list($options))) {
+ trigger_deprecation('symfony/validator', '7.4', 'Not using a YAML mapping of constraint option names to their values to configure the "%s" constraint is deprecated.', key($childNodes));
+
$options = [
'value' => $options,
];
}
- $values[] = $this->newConstraint(key($childNodes), $options);
+ $values[] = $this->newConstraint(key($childNodes), $options, true);
} else {
if (\is_array($childNodes)) {
$childNodes = $this->parseNodes($childNodes);
diff --git a/Mapping/Loader/schema/validation.schema.json b/Mapping/Loader/schema/validation.schema.json
new file mode 100644
index 000000000..0778929c4
--- /dev/null
+++ b/Mapping/Loader/schema/validation.schema.json
@@ -0,0 +1,123 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Symfony Validation Mapping Schema",
+ "description": "JSON schema for Symfony's validation mapping",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "namespaces": {
+ "type": "object",
+ "description": "Namespace aliases for constraint classes",
+ "additionalProperties": {
+ "type": "string",
+ "description": "Full namespace path"
+ }
+ }
+ },
+ "patternProperties": {
+ "^[A-Za-z0-9\\\\_]+$": {
+ "type": "object",
+ "description": "Class metadata configuration",
+ "additionalProperties": false,
+ "properties": {
+ "group_sequence": {
+ "oneOf": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Array of validation group names"
+ },
+ {
+ "type": "boolean"
+ }
+ ],
+ "description": "Validation group sequence or group sequence provider flag"
+ },
+ "group_sequence_provider": {
+ "oneOf": [
+ {
+ "type": "boolean",
+ "description": "Enable/disable group sequence provider"
+ },
+ {
+ "type": "string",
+ "description": "Class name of the group sequence provider"
+ }
+ ]
+ },
+ "constraints": {
+ "$ref": "#/definitions/constraintArray"
+ },
+ "properties": {
+ "type": "object",
+ "description": "Property-level constraints",
+ "additionalProperties": {
+ "$ref": "#/definitions/constraintArrayOrNull"
+ }
+ },
+ "getters": {
+ "type": "object",
+ "description": "Getter method constraints",
+ "additionalProperties": {
+ "$ref": "#/definitions/constraintArrayOrNull"
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "constraintArray": {
+ "type": "array",
+ "description": "Array of constraints",
+ "items": {
+ "oneOf": [
+ {
+ "type": "object",
+ "description": "Constraint with options",
+ "minProperties": 1,
+ "maxProperties": 1,
+ "additionalProperties": {
+ "oneOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ },
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "array"
+ },
+ {
+ "type": "object"
+ }
+ ]
+ }
+ },
+ {
+ "type": "string",
+ "description": "Simple constraint name"
+ }
+ ]
+ }
+ },
+ "constraintArrayOrNull": {
+ "oneOf": [
+ {
+ "type": "null",
+ "description": "No constraints"
+ },
+ {
+ "$ref": "#/definitions/constraintArray"
+ }
+ ]
+ }
+ }
+}
diff --git a/Mapping/MemberMetadata.php b/Mapping/MemberMetadata.php
index 6df8add12..7487fa0ba 100644
--- a/Mapping/MemberMetadata.php
+++ b/Mapping/MemberMetadata.php
@@ -29,26 +29,9 @@
*/
abstract class MemberMetadata extends GenericMetadata implements PropertyMetadataInterface
{
- /**
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getClassName()} instead.
- */
- public string $class;
-
- /**
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getName()} instead.
- */
- public string $name;
-
- /**
- * @internal This property is public in order to reduce the size of the
- * class' serialized representation. Do not access it. Use
- * {@link getPropertyName()} instead.
- */
- public string $property;
+ private string $class;
+ private string $name;
+ private string $property;
/**
* @var \ReflectionMethod[]|\ReflectionProperty[]
@@ -76,17 +59,53 @@ public function addConstraint(Constraint $constraint): static
return $this;
}
+ public function __serialize(): array
+ {
+ if (self::class === (new \ReflectionMethod($this, '__sleep'))->class || self::class !== (new \ReflectionMethod($this, '__serialize'))->class) {
+ return parent::__serialize() + [
+ 'class' => $this->class,
+ 'name' => $this->name,
+ 'property' => $this->property,
+ ];
+ }
+
+ trigger_deprecation('symfony/validator', '7.4', 'Implementing "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
+
+ $data = [];
+ foreach ($this->__sleep() as $key) {
+ try {
+ if (($r = new \ReflectionProperty($this, $key))->isInitialized($this)) {
+ $data[$key] = $r->getValue($this);
+ }
+ } catch (\ReflectionException) {
+ $data[$key] = $this->$key;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @deprecated since Symfony 7.4, will be replaced by `__serialize()` in 8.0
+ */
public function __sleep(): array
{
- return array_merge(parent::__sleep(), [
+ trigger_deprecation('symfony/validator', '7.4', 'Calling "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
+
+ return [
+ 'constraints',
+ 'constraintsByGroup',
+ 'cascadingStrategy',
+ 'traversalStrategy',
+ 'autoMappingStrategy',
'class',
'name',
'property',
- ]);
+ ];
}
/**
- * Returns the name of the member.
+ * Returns the name of the property or its getter.
*/
public function getName(): string
{
@@ -103,33 +122,21 @@ public function getPropertyName(): string
return $this->property;
}
- /**
- * Returns whether this member is public.
- */
public function isPublic(object|string $objectOrClassName): bool
{
return $this->getReflectionMember($objectOrClassName)->isPublic();
}
- /**
- * Returns whether this member is protected.
- */
public function isProtected(object|string $objectOrClassName): bool
{
return $this->getReflectionMember($objectOrClassName)->isProtected();
}
- /**
- * Returns whether this member is private.
- */
public function isPrivate(object|string $objectOrClassName): bool
{
return $this->getReflectionMember($objectOrClassName)->isPrivate();
}
- /**
- * Returns the reflection instance for accessing the member's value.
- */
public function getReflectionMember(object|string $objectOrClassName): \ReflectionMethod|\ReflectionProperty
{
$className = \is_string($objectOrClassName) ? $objectOrClassName : $objectOrClassName::class;
diff --git a/Resources/translations/validators.pt.xlf b/Resources/translations/validators.pt.xlf
index 62c3c5fc2..6f628c740 100644
--- a/Resources/translations/validators.pt.xlf
+++ b/Resources/translations/validators.pt.xlf
@@ -472,87 +472,87 @@
This file is not a valid video.
- Este ficheiro não é um vídeo válido.
+ Este ficheiro não é um vídeo válido.The size of the video could not be detected.
- Não foi possível detetar o tamanho do vídeo.
+ Não foi possível detetar o tamanho do vídeo.The video width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.
- A largura do vídeo é demasiado grande ({{ width }}px). A largura máxima permitida é {{ max_width }}px.
+ A largura do vídeo é demasiado grande ({{ width }}px). A largura máxima permitida é {{ max_width }}px.The video width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.
- A largura do vídeo é muito pequena ({{ width }}px). A largura mínima esperada é {{ min_width }}px.
+ A largura do vídeo é demasiado pequena ({{ width }}px). A largura mínima esperada é {{ min_width }}px.The video height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.
- A altura do vídeo é demasiado grande ({{ height }}px). A altura máxima permitida é {{ max_height }}px.
+ A altura do vídeo é demasiado grande ({{ height }}px). A altura máxima permitida é {{ max_height }}px.The video height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.
- A altura do vídeo é muito pequena ({{ height }}px). A altura mínima esperada é {{ min_height }}px.
+ A altura do vídeo é demasiado pequena ({{ height }}px). A altura mínima esperada é {{ min_height }}px.The video has too few pixels ({{ pixels }} pixels). Minimum amount expected is {{ min_pixels }} pixels.
- O vídeo tem poucos píxeis ({{ pixels }}). A quantidade mínima esperada é {{ min_pixels }}.
+ O vídeo tem poucos píxeis ({{ pixels }}). A quantidade mínima esperada é {{ min_pixels }}.The video has too many pixels ({{ pixels }} pixels). Maximum amount expected is {{ max_pixels }} pixels.
- O vídeo tem píxeis a mais ({{ pixels }}). A quantidade máxima esperada é {{ max_pixels }}.
+ O vídeo tem píxeis a mais ({{ pixels }}). A quantidade máxima esperada é {{ max_pixels }}.The video ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.
- A proporção do vídeo é muito grande ({{ ratio }}). A proporção máxima permitida é {{ max_ratio }}.
+ A proporção do vídeo é demasiado grande ({{ ratio }}). A proporção máxima permitida é {{ max_ratio }}.The video ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.
- A proporção do vídeo é muito pequena ({{ ratio }}). A proporção mínima esperada é {{ min_ratio }}.
+ A proporção do vídeo é demasiado pequena ({{ ratio }}). A proporção mínima esperada é {{ min_ratio }}.The video is square ({{ width }}x{{ height }}px). Square videos are not allowed.
- O vídeo é quadrado ({{ width }}x{{ height }}px). Vídeos quadrados não são permitidos.
+ O vídeo é quadrado ({{ width }}x{{ height }}px). Vídeos quadrados não são permitidos.The video is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented videos are not allowed.
- O vídeo está em modo paisagem ({{ width }}x{{ height }} px). Vídeos em paisagem não são permitidos.
+ O vídeo está em orientação horizontal ({{ width }}x{{ height }}px). Vídeos em orientação horizontal não são permitidos.The video is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented videos are not allowed.
- O vídeo está em orientação vertical ({{ width }}x{{ height }}px). Vídeos em orientação vertical não são permitidos.
+ O vídeo está em orientação vertical ({{ width }}x{{ height }}px). Vídeos em orientação vertical não são permitidos.The video file is corrupted.
- O ficheiro de vídeo está corrompido.
+ O ficheiro de vídeo está corrompido.The video contains multiple streams. Only one stream is allowed.
- O vídeo contém vários fluxos. É permitido apenas um fluxo.
+ O vídeo contém vários fluxos. Apenas é permitido um fluxo.Unsupported video codec "{{ codec }}".
- Codec de vídeo não suportado «{{ codec }}».
+ Codec de vídeo não suportado «{{ codec }}».Unsupported video container "{{ container }}".
- Contentor de vídeo não suportado "{{ container }}".
+ Contentor de vídeo não suportado «{{ container }}».The image file is corrupted.
- O ficheiro de imagem está corrompido.
+ O ficheiro de imagem está corrompido.The image has too few pixels ({{ pixels }} pixels). Minimum amount expected is {{ min_pixels }} pixels.
- A imagem tem píxeis a menos ({{ pixels }}). A quantidade mínima esperada é {{ min_pixels }}.
+ A imagem tem píxeis a menos ({{ pixels }}). A quantidade mínima esperada é {{ min_pixels }}.The image has too many pixels ({{ pixels }} pixels). Maximum amount expected is {{ max_pixels }} pixels.
- A imagem tem píxeis a mais ({{ pixels }}). A quantidade máxima esperada é {{ max_pixels }}.
+ A imagem tem píxeis a mais ({{ pixels }}). A quantidade máxima esperada é {{ max_pixels }}.This filename does not match the expected charset.
- Este nome de ficheiro não corresponde ao conjunto de caracteres esperado.
+ Este nome de ficheiro não corresponde ao conjunto de caracteres esperado.