Skip to content
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

[Validator] Use comparison constraints as attributes #38382

Merged
merged 1 commit into from
Oct 4, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions UPGRADE-5.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ Validator
*/
```

* Deprecated the `NumberConstraintTrait` trait.

Security
--------

Expand Down
2 changes: 2 additions & 0 deletions UPGRADE-6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ Validator
*/
```

* Removed the `NumberConstraintTrait` trait.

Yaml
----

Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Validator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ CHANGELOG
* added the `ULID` constraint and validator
* added support for UUIDv6 in `Uuid` constraint
* enabled the validator to load constraints from PHP attributes
* deprecated the `NumberConstraintTrait` trait

5.1.0
-----
Expand Down
33 changes: 19 additions & 14 deletions src/Symfony/Component/Validator/Constraints/AbstractComparison.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,33 @@ abstract class AbstractComparison extends Constraint

/**
* {@inheritdoc}
*
* @param mixed $value the value to compare or a set of options
*/
public function __construct($options = null)
public function __construct($value = null, $propertyPath = null, string $message = null, array $groups = null, $payload = null, array $options = [])
{
if (null === $options) {
$options = [];
if (\is_array($value)) {
$options = array_merge($value, $options);
} elseif (null !== $value) {
$options['value'] = $value;
}

if (\is_array($options)) {
if (!isset($options['value']) && !isset($options['propertyPath'])) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires either the "value" or "propertyPath" option to be set.', static::class));
}
parent::__construct($options, $groups, $payload);

if (isset($options['value']) && isset($options['propertyPath'])) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "value" or "propertyPath" options to be set, not both.', static::class));
}
$this->message = $message ?? $this->message;
$this->propertyPath = $propertyPath ?? $this->propertyPath;

if (isset($options['propertyPath']) && !class_exists(PropertyAccess::class)) {
throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', static::class));
}
if (null === $this->value && null === $this->propertyPath) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires either the "value" or "propertyPath" option to be set.', static::class));
}

parent::__construct($options);
if (null !== $this->value && null !== $this->propertyPath) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "value" or "propertyPath" options to be set, not both.', static::class));
}

if (null !== $this->propertyPath && !class_exists(PropertyAccess::class)) {
throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', static::class));
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*
* @author Colin O'Dell <colinodell@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class DivisibleBy extends AbstractComparison
{
const NOT_DIVISIBLE_BY = '6d99d6c3-1464-4ccf-bdc7-14d083cf455c';
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Validator/Constraints/EqualTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class EqualTo extends AbstractComparison
{
const NOT_EQUAL_ERROR = '478618a7-95ba-473d-9101-cabd45e49115';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class GreaterThan extends AbstractComparison
{
const TOO_LOW_ERROR = '778b7ae0-84d3-481a-9dec-35fdb64b1d78';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class GreaterThanOrEqual extends AbstractComparison
{
const TOO_LOW_ERROR = 'ea4e51d1-3342-48bd-87f1-9e672cd90cad';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IdenticalTo extends AbstractComparison
{
const NOT_IDENTICAL_ERROR = '2a8cc50f-58a2-4536-875e-060a2ce69ed5';
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Validator/Constraints/LessThan.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class LessThan extends AbstractComparison
{
const TOO_HIGH_ERROR = '079d7420-2d13-460c-8756-de810eeb37d2';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class LessThanOrEqual extends AbstractComparison
{
const TOO_HIGH_ERROR = '30fbb013-d015-4232-8b3b-8f3be97a7e14';
Expand Down
13 changes: 2 additions & 11 deletions src/Symfony/Component/Validator/Constraints/Negative.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,10 @@
*
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Negative extends LessThan
{
use NumberConstraintTrait;
use ZeroComparisonConstraintTrait;

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

public function __construct($options = null)
{
parent::__construct($this->configureNumberConstraintOptions($options));
}

public function validatedBy(): string
{
return LessThanValidator::class;
}
}
13 changes: 2 additions & 11 deletions src/Symfony/Component/Validator/Constraints/NegativeOrZero.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,10 @@
*
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NegativeOrZero extends LessThanOrEqual
{
use NumberConstraintTrait;
use ZeroComparisonConstraintTrait;

public $message = 'This value should be either negative or zero.';

public function __construct($options = null)
{
parent::__construct($this->configureNumberConstraintOptions($options));
}

public function validatedBy(): string
{
return LessThanOrEqualValidator::class;
}
}
1 change: 1 addition & 0 deletions src/Symfony/Component/Validator/Constraints/NotEqualTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NotEqualTo extends AbstractComparison
{
const IS_EQUAL_ERROR = 'aa2e33da-25c8-4d76-8c6c-812f02ea89dd';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NotIdenticalTo extends AbstractComparison
{
const IS_IDENTICAL_ERROR = '4aaac518-0dda-4129-a6d9-e216b9b454a0';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@

use Symfony\Component\Validator\Exception\ConstraintDefinitionException;

trigger_deprecation('symfony/validator', '5.2', '%s is deprecated.', NumberConstraintTrait::class);

/**
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*
* @deprecated since Symfony 5.2
*/
trait NumberConstraintTrait
{
Expand Down
13 changes: 2 additions & 11 deletions src/Symfony/Component/Validator/Constraints/Positive.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,10 @@
*
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Positive extends GreaterThan
{
use NumberConstraintTrait;
use ZeroComparisonConstraintTrait;

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

public function __construct($options = null)
{
parent::__construct($this->configureNumberConstraintOptions($options));
}

public function validatedBy(): string
{
return GreaterThanValidator::class;
}
}
13 changes: 2 additions & 11 deletions src/Symfony/Component/Validator/Constraints/PositiveOrZero.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,10 @@
*
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class PositiveOrZero extends GreaterThanOrEqual
{
use NumberConstraintTrait;
use ZeroComparisonConstraintTrait;

public $message = 'This value should be either positive or zero.';

public function __construct($options = null)
{
parent::__construct($this->configureNumberConstraintOptions($options));
}

public function validatedBy(): string
{
return GreaterThanOrEqualValidator::class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Validator\Constraints;

use Symfony\Component\Validator\Exception\ConstraintDefinitionException;

/**
* @internal
*
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
* @author Alexander M. Turek <me@derrabus.de>
*/
trait ZeroComparisonConstraintTrait
{
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
if (null === $options) {
$options = [];
}

if (isset($options['propertyPath'])) {
throw new ConstraintDefinitionException(sprintf('The "propertyPath" option of the "%s" constraint cannot be set.', static::class));
}

if (isset($options['value'])) {
throw new ConstraintDefinitionException(sprintf('The "value" option of the "%s" constraint cannot be set.', static::class));
}

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

public function validatedBy(): string
{
return parent::class.'Validator';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Validator\Tests\Constraints;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\DivisibleBy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;

/**
* @requires PHP 8
*/
class DivisibleByTest extends TestCase
{
public function testAttributes()
{
$metadata = new ClassMetadata(DivisibleByDummy::class);
$loader = new AnnotationLoader();
self::assertTrue($loader->loadClassMetadata($metadata));

list($aConstraint) = $metadata->properties['a']->getConstraints();
self::assertSame(2, $aConstraint->value);
self::assertNull($aConstraint->propertyPath);

list($bConstraint) = $metadata->properties['b']->getConstraints();
self::assertSame(4711, $bConstraint->value);
self::assertSame('myMessage', $bConstraint->message);
self::assertSame(['Default', 'DivisibleByDummy'], $bConstraint->groups);

list($cConstraint) = $metadata->properties['c']->getConstraints();
self::assertNull($cConstraint->value);
self::assertSame('b', $cConstraint->propertyPath);
self::assertSame('myMessage', $cConstraint->message);
self::assertSame(['foo'], $cConstraint->groups);
}
}

class DivisibleByDummy
{
#[DivisibleBy(2)]
private $a;

#[DivisibleBy(value: 4711, message: 'myMessage')]
private $b;

#[DivisibleBy(propertyPath: 'b', message: 'myMessage', groups: ['foo'])]
private $c;
}