Skip to content

[Validator] Constraints as php 8 Attributes #38309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/patch-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
case false !== strpos($file, '/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures'):
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/LotsOfAttributes.php'):
case false !== strpos($file, '/src/Symfony/Component/Validator/Tests/Fixtures/Attribute/'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/MyAttribute.php'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/Php74.php') && \PHP_VERSION_ID < 70400:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@

namespace Symfony\Component\HttpKernel\Tests\Fixtures\Attribute;

use Attribute;
use Symfony\Component\HttpKernel\Attribute\ArgumentInterface;

#[Attribute(Attribute::TARGET_PARAMETER)]
#[\Attribute(\Attribute::TARGET_PARAMETER)]
class Foo implements ArgumentInterface
{
private $foo;
Expand Down
4 changes: 1 addition & 3 deletions src/Symfony/Component/Routing/Annotation/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

namespace Symfony\Component\Routing\Annotation;

use Attribute;

/**
* Annotation class for @Route().
*
Expand All @@ -22,7 +20,7 @@
* @author Fabien Potencier <fabien@symfony.com>
* @author Alexander M. Turek <me@derrabus.de>
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class Route
{
private $path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@

namespace Symfony\Component\Security\Http\Attribute;

use Attribute;
use Symfony\Component\HttpKernel\Attribute\ArgumentInterface;

/**
* Indicates that a controller argument should receive the current logged user.
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
#[\Attribute(\Attribute::TARGET_PARAMETER)]
class CurrentUser implements ArgumentInterface
{
}
1 change: 1 addition & 0 deletions src/Symfony/Component/Validator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ CHANGELOG
* added the `Isin` constraint and validator
* added the `ULID` constraint and validator
* added support for UUIDv6 in `Uuid` constraint
* enabled the validator to load constraints from PHP attributes

5.1.0
-----
Expand Down
18 changes: 13 additions & 5 deletions src/Symfony/Component/Validator/Constraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ public static function getErrorName($errorCode)
* getRequiredOptions() to return the names of these options. If any
* option is not set here, an exception is thrown.
*
* @param mixed $options The options (as associative array)
* or the value for the default
* option (any other type)
* @param mixed $options The options (as associative array)
* or the value for the default
* option (any other type)
* @param string[] $groups An array of validation groups
* @param mixed $payload Domain-specific data attached to a constraint
*
* @throws InvalidOptionsException When you pass the names of non-existing
* options
Expand All @@ -103,9 +105,15 @@ public static function getErrorName($errorCode)
* array, but getDefaultOption() returns
* null
*/
public function __construct($options = null)
public function __construct($options = null, array $groups = null, $payload = null)
{
foreach ($this->normalizeOptions($options) as $name => $value) {
$options = $this->normalizeOptions($options);
if (null !== $groups) {
$options['groups'] = $groups;
}
$options['payload'] = $payload ?? $options['payload'] ?? null;

foreach ($options as $name => $value) {
$this->$name = $value;
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/Symfony/Component/Validator/Constraints/Blank.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Blank extends Constraint
{
const NOT_BLANK_ERROR = '183ad2de-533d-4796-a439-6d3c3852b549';
Expand All @@ -28,4 +29,11 @@ class Blank extends Constraint
];

public $message = 'This value should be blank.';

public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);

$this->message = $message ?? $this->message;
}
}
17 changes: 11 additions & 6 deletions src/Symfony/Component/Validator/Constraints/Callback.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Callback extends Constraint
{
/**
Expand All @@ -28,19 +29,23 @@ class Callback extends Constraint

/**
* {@inheritdoc}
*
* @param array|string|callable $callback The callback or a set of options
*/
public function __construct($options = null)
public function __construct($callback = null, array $groups = null, $payload = null, array $options = [])
{
// Invocation through annotations with an array parameter only
if (\is_array($options) && 1 === \count($options) && isset($options['value'])) {
$options = $options['value'];
if (\is_array($callback) && 1 === \count($callback) && isset($callback['value'])) {
$callback = $callback['value'];
}

if (\is_array($options) && !isset($options['callback']) && !isset($options['groups']) && !isset($options['payload'])) {
$options = ['callback' => $options];
if (!\is_array($callback) || (!isset($callback['callback']) && !isset($callback['groups']) && !isset($callback['payload']))) {
$options['callback'] = $callback;
} else {
$options = array_merge($callback, $options);
}

parent::__construct($options);
parent::__construct($options, $groups, $payload);
}

/**
Expand Down
35 changes: 35 additions & 0 deletions src/Symfony/Component/Validator/Constraints/Choice.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Choice extends Constraint
{
const NO_SUCH_CHOICE_ERROR = '8e179f1b-97aa-4560-a02f-2a8b42e49df7';
Expand Down Expand Up @@ -49,4 +50,38 @@ public function getDefaultOption()
{
return 'choices';
}

public function __construct(
$choices = null,
$callback = null,
bool $multiple = null,
bool $strict = null,
int $min = null,
int $max = null,
string $message = null,
string $multipleMessage = null,
string $minMessage = null,
string $maxMessage = null,
$groups = null,
$payload = null,
array $options = []
) {
if (\is_array($choices) && \is_string(key($choices))) {
$options = array_merge($choices, $options);
Comment on lines +69 to +70

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@derrabus I often use associative arrays as choices - using the translation key as choice key ; one reason would be to share this constant array with the ChoiceType choices option. It was completely ok with Annotations. But this compatibility rule for the new Attribute usage is now concidering my choices keys as constraint options and breaks my code. I get that this is required to support both Annotation and Attribute but this changes the way this constraint is working.

// stupid example
abstract class FormatEnum
{
    public const ALL = [
        'enum.format.example1' => 'f1',
        'enum.format.example2' => 'd6',
        'enum.format.example3' => 'u9',
    ];
}

// Annotation: 👍🏻 
// @Choice(choices=FormatEnum::ALL)

// Attribute: 👎🏻 
// #[Choice(choices: FormatEnum::ALL)]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please open a bug for your issue?

} elseif (null !== $choices) {
$options['choices'] = $choices;
}

parent::__construct($options, $groups, $payload);

$this->callback = $callback ?? $this->callback;
$this->multiple = $multiple ?? $this->multiple;
$this->strict = $strict ?? $this->strict;
$this->min = $min ?? $this->min;
$this->max = $max ?? $this->max;
$this->message = $message ?? $this->message;
$this->multipleMessage = $multipleMessage ?? $this->multipleMessage;
$this->minMessage = $minMessage ?? $this->minMessage;
$this->maxMessage = $maxMessage ?? $this->maxMessage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class GroupSequence
{
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class GroupSequenceProvider
{
}
8 changes: 8 additions & 0 deletions src/Symfony/Component/Validator/Constraints/IsFalse.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IsFalse extends Constraint
{
const NOT_FALSE_ERROR = 'd53a91b0-def3-426a-83d7-269da7ab4200';
Expand All @@ -28,4 +29,11 @@ class IsFalse extends Constraint
];

public $message = 'This value should be false.';

public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);

$this->message = $message ?? $this->message;
}
}
8 changes: 8 additions & 0 deletions src/Symfony/Component/Validator/Constraints/IsNull.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IsNull extends Constraint
{
const NOT_NULL_ERROR = '60d2f30b-8cfa-4372-b155-9656634de120';
Expand All @@ -28,4 +29,11 @@ class IsNull extends Constraint
];

public $message = 'This value should be null.';

public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);

$this->message = $message ?? $this->message;
}
}
8 changes: 8 additions & 0 deletions src/Symfony/Component/Validator/Constraints/IsTrue.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IsTrue extends Constraint
{
const NOT_TRUE_ERROR = '2beabf1c-54c0-4882-a928-05249b26e23b';
Expand All @@ -28,4 +29,11 @@ class IsTrue extends Constraint
];

public $message = 'This value should be true.';

public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);

$this->message = $message ?? $this->message;
}
}
9 changes: 7 additions & 2 deletions src/Symfony/Component/Validator/Constraints/NotBlank.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NotBlank extends Constraint
{
const IS_BLANK_ERROR = 'c1051bb4-d103-4f74-8988-acbcafc7fdc3';
Expand All @@ -33,9 +34,13 @@ class NotBlank extends Constraint
public $allowNull = false;
public $normalizer;

public function __construct($options = null)
public function __construct(array $options = null, string $message = null, bool $allowNull = null, callable $normalizer = null, array $groups = null, $payload = null)
{
parent::__construct($options);
parent::__construct($options ?? [], $groups, $payload);

$this->message = $message ?? $this->message;
$this->allowNull = $allowNull ?? $this->allowNull;
$this->normalizer = $normalizer ?? $this->normalizer;

if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
Expand Down
8 changes: 8 additions & 0 deletions src/Symfony/Component/Validator/Constraints/NotNull.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NotNull extends Constraint
{
const IS_NULL_ERROR = 'ad32d13f-c3d4-423b-909a-857b961eb720';
Expand All @@ -28,4 +29,11 @@ class NotNull extends Constraint
];

public $message = 'This value should not be null.';

public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);

$this->message = $message ?? $this->message;
}
}
Loading