Skip to content

Commit 10299d2

Browse files
committed
Support Symfony v6 with PHP8 minimum
- Remove all annotations - Improve tests to be more thorough - Update documentation
1 parent 8da0650 commit 10299d2

12 files changed

Lines changed: 103 additions & 210 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ env:
55
- XDEBUG_MODE=coverage
66
language: php
77
php:
8-
- '7.4'
98
- '8.0'
9+
- '8.1'
1010

1111
before_script:
1212
- composer self-update

README.md

Lines changed: 5 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ Squirrel Validator Cascade
33

44
[![Build Status](https://img.shields.io/travis/com/squirrelphp/validator-cascade.svg)](https://travis-ci.com/squirrelphp/validator-cascade) [![Test Coverage](https://api.codeclimate.com/v1/badges/e056be025c6db0eb31f1/test_coverage)](https://codeclimate.com/github/squirrelphp/validator-cascade/test_coverage) ![PHPStan](https://img.shields.io/badge/style-level%208-success.svg?style=flat-round&label=phpstan) [![Packagist Version](https://img.shields.io/packagist/v/squirrelphp/validator-cascade.svg?style=flat-round)](https://packagist.org/packages/squirrelphp/validator-cascade) [![PHP Version](https://img.shields.io/packagist/php-v/squirrelphp/validator-cascade.svg)](https://packagist.org/packages/squirrelphp/validator-cascade) [![Software License](https://img.shields.io/badge/license-MIT-success.svg?style=flat-round)](LICENSE)
55

6-
Reimplements the `Valid` constraint in the Symfony validator component as `Cascade` annotation/attribute which is more straightforward to use than `Valid` and has no surprising behavior.
6+
Reimplements the `Valid` constraint in the Symfony validator component as `Cascade` attribute which is more straightforward to use than `Valid` and has no surprising behavior.
77

8-
This component is compatible with the Symfony validator component in version 5.0+ and will be adapted to support future versions of Symfony (if any changes are necessary for that).
8+
This component is compatible with the Symfony validator component in version 5.x (v2.x with annotation/attribute support) and 6.x (v3.x with only attribute support) and will be adapted to support future versions of Symfony (if any changes are necessary for that).
99

1010
Installation
1111
------------
@@ -45,82 +45,6 @@ Below is a common example in real applications: You might have an order and mult
4545
use Squirrel\ValidatorCascade\Cascade;
4646
use Symfony\Component\Validator\Constraints as Assert;
4747

48-
class Order
49-
{
50-
/**
51-
* Validate $shippingAddress if validation with no validation
52-
* group or the "Default" validation group is triggered
53-
*
54-
* Validates "Default" and "phoneNumberMandatory" validation groups in $shippingAddress
55-
*
56-
* @Cascade(trigger={"Default", "phoneNumberMandatory"})
57-
*/
58-
public Address $shippingAddress;
59-
60-
/**
61-
* Validate $invoiceAddress only if validation group
62-
* "alternateInvoiceAddress" is passed to validator
63-
*
64-
* Validates only "Default" validation group in $invoiceAddress, so phone number is optional
65-
*
66-
* @Assert\NotNull(groups={"alternateInvoiceAddress"})
67-
* @Cascade(groups={"alternateInvoiceAddress"})
68-
*/
69-
public ?Address $invoiceAddress = null;
70-
}
71-
72-
class Address
73-
{
74-
/**
75-
* @Assert\Length(
76-
* min = 1,
77-
* max = 50
78-
* )
79-
*/
80-
public string $street = '';
81-
82-
/**
83-
* @Assert\Length(
84-
* min = 1,
85-
* max = 50
86-
* )
87-
*/
88-
public string $city = '';
89-
90-
/**
91-
* @Assert\Length(
92-
* min = 1,
93-
* max = 50,
94-
* groups = {"phoneNumberMandatory"}
95-
* )
96-
*
97-
* @var string
98-
*/
99-
public string $phoneNumber = '';
100-
}
101-
102-
$order = new Order();
103-
$order->shippingAddress = new Address();
104-
$order->invoiceAddress = new Address();
105-
106-
// This validates with the "Default" validation group,
107-
// so only shippingAddress must be specified
108-
$symfonyValidator->validate($order);
109-
110-
// This also validates the invoice address in addition
111-
// to the shipping address
112-
$symfonyValidator->validate($order, null, [
113-
"Default",
114-
"alternateInvoiceAddress",
115-
]);
116-
```
117-
118-
If you are using PHP8+, you should use attributes instead (although annotations will still work):
119-
120-
```php
121-
use Squirrel\ValidatorCascade\Cascade;
122-
use Symfony\Component\Validator\Constraints as Assert;
123-
12448
class Order
12549
{
12650
/**
@@ -179,9 +103,7 @@ The current implementation of the `Valid` constraint in the Symfony validator co
179103
### `Valid` constraint without validation group
180104

181105
```php
182-
/**
183-
* @Assert\Valid()
184-
*/
106+
#[Assert\Valid]
185107
public $someobject;
186108
```
187109

@@ -195,9 +117,7 @@ This is fine for simple objects or when you don't need any validation groups at
195117
### `Valid` constraint with validation group(s)
196118

197119
```php
198-
/**
199-
* @Assert\Valid(groups={"invoice"})
200-
*/
120+
#[Assert\Valid(groups: ['invoice'])]
201121
public $someobject;
202122
```
203123

@@ -207,6 +127,6 @@ The `Valid` assertion above only triggers when you validate the "invoice" valida
207127
- There is no way to change which validation groups are triggered in $someobject
208128
- The "traverse" option for `Valid` is not used when a validation group is defined. Although the "traverse" option should probably not be used or needed in general
209129

210-
Having validation groups both as a trigger and as a filter severly limits how you can use it, and makes most use cases (like our [example with addresses](#example)) impossible to do with `Valid`. Even if you manage to make it work, your code will not be self explanatory and it is easy to make mistakes or misunderstand the annotations.
130+
Having validation groups both as a trigger and as a filter severly limits how you can use it, and makes most use cases (like our [example with addresses](#example)) impossible to do with `Valid`. Even if you manage to make it work, your code will not be self explanatory and it is easy to make mistakes or misunderstand the attributes.
211131

212132
`Cascade` as defined in this component separates which validation group the constraint belongs to and which validation groups are triggered in the child object(s). What it cannot do is cascade the validation groups of the parent to the child object, as this information is only available in the `RecursiveContextualValidator` class of the validator component and cannot be accessed without changing a lot of the internals of the validator component (unfortunately).

composer.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@
1919
}
2020
],
2121
"require": {
22-
"php": ">=7.4",
23-
"symfony/validator": "^5.0"
22+
"php": ">=8.0",
23+
"symfony/validator": "^6.0"
2424
},
2525
"require-dev": {
26-
"doctrine/annotations": "^1.6",
2726
"bamarni/composer-bin-plugin": "^1.3",
2827
"captainhook/plugin-composer": "^5.0",
2928
"phpunit/phpunit": "^9.0",

examples/Address.php

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,12 @@
66

77
class Address
88
{
9-
/**
10-
* @Assert\NotNull()
11-
* @Assert\NotBlank()
12-
* @Assert\Length(
13-
* min = 1,
14-
* max = 50
15-
* )
16-
*
17-
* @var string
18-
*/
19-
public $street;
20-
21-
/**
22-
* @Assert\NotNull()
23-
* @Assert\NotBlank()
24-
* @Assert\Length(
25-
* min = 1,
26-
* max = 50
27-
* )
28-
*
29-
* @var string
30-
*/
31-
public $city;
9+
#[Assert\Length(min: 1, max: 50)]
10+
public string $street = '';
3211

33-
/**
34-
* @Assert\NotNull(groups={"phoneNumberMandatory"})
35-
* @Assert\NotBlank(groups={"phoneNumberMandatory"})
36-
* @Assert\Length(
37-
* min = 1,
38-
* max = 50
39-
* )
40-
*
41-
* @var string
42-
*/
43-
#[Assert\NotNull(groups: ['phoneNumberMandatory'])]
44-
#[Assert\NotBlank(groups: ['phoneNumberMandatory'])]
4512
#[Assert\Length(min: 1, max: 50)]
46-
public $phoneNumber;
13+
public string $city = '';
14+
15+
#[Assert\Length(min: 1, max: 50, groups: ['phoneNumberMandatory'])]
16+
public string $phoneNumber = '';
4717
}

examples/Order.php

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,42 +13,25 @@ class Order
1313
*
1414
* Validates "Default" and "phoneNumberMandatory" validation groups
1515
* in $shippingAddress
16-
*
17-
* @Assert\NotNull()
18-
* @Assert\Type(type="Squirrel\ValidatorCascade\Examples\Address")
19-
* @Cascade(trigger={"Default", "phoneNumberMandatory"})
20-
*
21-
* @var Address
2216
*/
23-
#[Assert\NotNull()]
24-
#[Assert\Type(type: 'Squirrel\ValidatorCascade\Examples\Address')]
2517
#[Cascade(trigger: ['Default', 'phoneNumberMandatory'])]
26-
public $shippingAddress;
18+
public Address $shippingAddress;
2719

2820
/**
2921
* Validate $invoiceAddress only if validation group
3022
* "alternateInvoiceAddress" is passed to validator
3123
*
3224
* Validates only "Default" validation group in $invoiceAddress,
3325
* so phone number is optional
34-
*
35-
* @Assert\NotNull()
36-
* @Assert\Type(type="Squirrel\ValidatorCascade\Examples\Address")
37-
* @Cascade(groups={"alternateInvoiceAddress"})
38-
*
39-
* @var Address
4026
*/
41-
public $invoiceAddress;
27+
#[Assert\NotNull(groups: ['alternateInvoiceAddress'])]
28+
#[Cascade(groups: ['alternateInvoiceAddress'])]
29+
public ?Address $invoiceAddress = null;
4230

4331
/**
4432
* Validates only the phone number in the address, as the Default group
4533
* is not passed in
46-
*
47-
* @Assert\NotNull()
48-
* @Assert\Type(type="Squirrel\ValidatorCascade\Examples\Address")
49-
* @Cascade(trigger={"phoneNumberMandatory"})
50-
*
51-
* @var Address
5234
*/
53-
public $phoneNumberOnlyAddress;
35+
#[Cascade(trigger: ['phoneNumberMandatory'])]
36+
public Address $phoneNumberOnlyAddress;
5437
}

phpunit.xml.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="vendor/autoload.php">
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="vendor/autoload.php">
33
<coverage>
44
<include>
55
<directory suffix=".php">src</directory>

psalm-baseline.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<files psalm-version="4.3.1@2feba22a005a18bf31d4c7b9bdb9252c73897476"/>
2+
<files psalm-version="v4.15.0@a1b5e489e6fcebe40cb804793d964e99fc347820">
3+
<file src="src/Cascade.php">
4+
<PropertyNotSetInConstructor occurrences="1">
5+
<code>Cascade</code>
6+
</PropertyNotSetInConstructor>
7+
</file>
8+
</files>

src/Cascade.php

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,16 @@
44

55
use Symfony\Component\Validator\Constraint;
66

7-
/**
8-
* @Annotation
9-
* @Target({"PROPERTY", "METHOD"})
10-
*/
117
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)]
128
class Cascade extends Constraint
139
{
14-
/**
15-
* @var array Which validation groups to trigger in any child objects
16-
*/
17-
protected array $trigger = [Constraint::DEFAULT_GROUP];
18-
19-
/**
20-
* @param array|null $options
21-
* @param string[] $groups
22-
* @param string[] $trigger
23-
*/
2410
public function __construct(
25-
$options = null,
11+
/** @var string[] For which groups to trigger this contraint (default Symfony Validator behavior) */
2612
array $groups = [Constraint::DEFAULT_GROUP],
27-
array $trigger = [Constraint::DEFAULT_GROUP]
13+
/** @var string[] Which validation groups to trigger in any child objects */
14+
protected array $trigger = [Constraint::DEFAULT_GROUP]
2815
) {
29-
if (isset($options['groups'])) {
30-
$groups = $options['groups'];
31-
}
32-
33-
if (isset($options['trigger'])) {
34-
$trigger = $options['trigger'];
35-
}
36-
37-
$this->groups = $groups;
38-
$this->trigger = $trigger;
16+
parent::__construct(groups: $groups);
3917
}
4018

4119
public function getTrigger(): array

src/CascadeValidator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
class CascadeValidator extends ConstraintValidator
1010
{
11-
public function validate($value, Constraint $constraint): void
11+
public function validate(mixed $value, Constraint $constraint): void
1212
{
1313
if (!$constraint instanceof Cascade) {
1414
throw new UnexpectedTypeException($constraint, __NAMESPACE__ . '\Cascade');
@@ -22,6 +22,6 @@ public function validate($value, Constraint $constraint): void
2222
$this->context
2323
->getValidator()
2424
->inContext($this->context)
25-
->validate($value, null, $constraint->getTrigger());
25+
->validate($value, groups: $constraint->getTrigger());
2626
}
2727
}

0 commit comments

Comments
 (0)