Skip to content

Commit c85be5c

Browse files
authored
feat: Add Symfony 8 compatibility (#66)
* First working version * Factorize code a bit * Attempt at passing CI by fixing symfony/validator to non problematic versions * Prevent iso from being set by both legacy string and positional argument
1 parent b75556d commit c85be5c

File tree

4 files changed

+139
-16
lines changed

4 files changed

+139
-16
lines changed

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ For validating a zip code you need to instantiate a new ZipCode class provided b
2727
$form = $this->createFormBuilder($address)
2828
->add('zipcode', TextType::class, [
2929
'constraints' => [
30-
new ZipCodeValidator\Constraints\ZipCode([
31-
'iso' => 'DE'
32-
])
30+
new ZipCodeValidator\Constraints\ZipCode(iso: 'DE')
3331
]
3432
])
3533
->add('save', SubmitType::class, ['label' => 'Create Task'])
@@ -51,19 +49,24 @@ class Address
5149
}
5250
```
5351

54-
You can also use it as a PHP8 Attribute, with parameters passed as an array of options, for example:
52+
You can also use it as a PHP8 Attribute with named parameters:
5553
```php
5654
<?php
5755

5856
use ZipCodeValidator\Constraints\ZipCode;
5957

6058
class Address
6159
{
62-
#[ZipCode(['iso'=>'DE'])
60+
#[ZipCode(iso: 'DE')]
6361
protected $zipCode;
6462
}
6563
```
6664

65+
Legacy array options are still supported for backward compatibility:
66+
```php
67+
#[ZipCode(['iso' => 'DE'])]
68+
```
69+
6770
> Please consider to inject a valid ISO 3166 2-letter country code (e.g. DE, US, FR)!
6871
6972
> NOTE: This library validates against known zip code regex patterns and does not validate the existence of a zipcode.
@@ -117,10 +120,7 @@ protected $zipCode;
117120
### Case insensitive zip code matching
118121
In case you want to match the zip code in a case insensitive way you have to pass a `caseSensitiveCheck` parameter with `false` value via the constructor:
119122
```php
120-
$constraint = new ZipCode([
121-
'iso' => 'GB',
122-
'caseSensitiveCheck' => false
123-
]);
123+
$constraint = new ZipCode(iso: 'GB', caseSensitiveCheck: false);
124124

125125
```
126126
By the default the library is using case sensitive zip code matching.

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"prefer-stable": true,
1313
"require": {
1414
"php": ">=8.0",
15-
"symfony/validator": ">=4.4.40"
15+
"symfony/validator": "^5.4.43 || ^6.4.11 || ^7.1.4 || ^8.0"
1616
},
1717
"require-dev": {
1818
"phpunit/phpunit": "^9.6 || ^10.5 || ^11.0.3"

src/ZipCodeValidator/Constraints/ZipCode.php

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Attribute;
66
use Symfony\Component\Validator\Constraint;
7+
use Symfony\Component\Validator\Exception\InvalidOptionsException;
78
use Symfony\Component\Validator\Exception\MissingOptionsException;
89

910
/**
@@ -20,19 +21,72 @@ class ZipCode extends Constraint
2021
public bool $strict = true;
2122
public bool $caseSensitiveCheck = true;
2223

23-
public function __construct(mixed $options = null, ?array $groups = null, mixed $payload = null)
24+
public function __construct(
25+
mixed $options = null,
26+
?array $groups = null,
27+
mixed $payload = null,
28+
?string $iso = null,
29+
?string $getter = null,
30+
?bool $strict = null,
31+
?bool $caseSensitiveCheck = null,
32+
?string $message = null
33+
)
2434
{
2535
if (is_string($options)) {
26-
$options = array(
27-
'iso' => $options
36+
if (null !== $iso) {
37+
throw new InvalidOptionsException(
38+
'Cannot pass both positional string $options and named "iso". Use one style.',
39+
['options', 'iso']
40+
);
41+
}
42+
43+
$options = ['iso' => $options];
44+
} elseif (null === $options) {
45+
$options = [];
46+
} elseif (!is_array($options)) {
47+
throw new InvalidOptionsException(sprintf('The options "%s" do not exist in constraint "%s".', 'options', __CLASS__), ['options']);
48+
}
49+
50+
$resolvedOptions = [
51+
'iso' => $iso,
52+
'getter' => $getter,
53+
'strict' => $strict,
54+
'caseSensitiveCheck' => $caseSensitiveCheck,
55+
'message' => $message,
56+
'groups' => $groups,
57+
'payload' => $payload,
58+
];
59+
60+
$invalidOptions = array_values(array_filter(array_keys($options), fn ($option) => !in_array($option, array_keys($resolvedOptions), true)));
61+
if ([] !== $invalidOptions) {
62+
throw new InvalidOptionsException(
63+
sprintf('The options "%s" do not exist in constraint "%s".', implode('", "', $invalidOptions), __CLASS__),
64+
$invalidOptions
2865
);
2966
}
3067

31-
parent::__construct($options, $groups, $payload);
68+
foreach ($resolvedOptions as $option => $resolvedValue) {
69+
if (null !== $resolvedValue || !array_key_exists($option, $options)) {
70+
continue;
71+
}
72+
73+
$resolvedOptions[$option] = 'groups' === $option ? (array) $options[$option] : $options[$option];
74+
}
75+
76+
parent::__construct(null, $resolvedOptions['groups'], $resolvedOptions['payload']);
77+
78+
unset($resolvedOptions['groups'], $resolvedOptions['payload']);
79+
80+
foreach ($resolvedOptions as $option => $resolvedValue) {
81+
if (null === $resolvedValue) {
82+
continue;
83+
}
84+
85+
$this->{$option} = $resolvedValue;
86+
}
3287

3388
if (null === $this->iso && null === $this->getter) {
3489
throw new MissingOptionsException(sprintf('Either the option "iso" or "getter" must be given for constraint %s', __CLASS__), ['iso', 'getter']);
3590
}
3691
}
37-
3892
}

tests/Constraints/ZipCodeTest.php

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace ZipCodeValidator\Tests\Constraints;
44

55
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\Validator\Exception\InvalidOptionsException;
67
use Symfony\Component\Validator\Exception\MissingOptionsException;
78
use ZipCodeValidator\Constraints\ZipCode;
89

@@ -13,4 +14,72 @@ public function testMissingOptionsExceptionWhenIsoAndGetterIsEmpty(): void
1314
$this->expectException(MissingOptionsException::class);
1415
$constraint = new ZipCode(null);
1516
}
16-
}
17+
18+
public function testLegacyStringOptionSetsIso(): void
19+
{
20+
$constraint = new ZipCode('DE');
21+
22+
$this->assertSame('DE', $constraint->iso);
23+
}
24+
25+
public function testLegacyArrayOptionsAreStillSupported(): void
26+
{
27+
$payload = new \stdClass();
28+
$constraint = new ZipCode([
29+
'iso' => 'GB',
30+
'strict' => false,
31+
'caseSensitiveCheck' => false,
32+
'message' => 'Custom message',
33+
'groups' => 'Address',
34+
'payload' => $payload,
35+
]);
36+
37+
$this->assertSame('GB', $constraint->iso);
38+
$this->assertFalse($constraint->strict);
39+
$this->assertFalse($constraint->caseSensitiveCheck);
40+
$this->assertSame('Custom message', $constraint->message);
41+
$this->assertSame(['Address'], $constraint->groups);
42+
$this->assertSame($payload, $constraint->payload);
43+
}
44+
45+
public function testNamedParametersAreSupported(): void
46+
{
47+
$constraint = new ZipCode(
48+
iso: 'FR',
49+
strict: false,
50+
caseSensitiveCheck: false,
51+
message: 'Another message',
52+
groups: ['Checkout']
53+
);
54+
55+
$this->assertSame('FR', $constraint->iso);
56+
$this->assertFalse($constraint->strict);
57+
$this->assertFalse($constraint->caseSensitiveCheck);
58+
$this->assertSame('Another message', $constraint->message);
59+
$this->assertSame(['Checkout'], $constraint->groups);
60+
}
61+
62+
public function testNamedParametersTakePrecedenceOverLegacyOptionsArray(): void
63+
{
64+
$constraint = new ZipCode(
65+
['iso' => 'DE', 'strict' => true],
66+
iso: 'US',
67+
strict: false
68+
);
69+
70+
$this->assertSame('US', $constraint->iso);
71+
$this->assertFalse($constraint->strict);
72+
}
73+
74+
public function testUnknownLegacyOptionThrowsException(): void
75+
{
76+
$this->expectException(InvalidOptionsException::class);
77+
new ZipCode(['foo' => 'bar', 'iso' => 'FR']);
78+
}
79+
80+
public function testLegacyStringOptionCannotBeCombinedWithNamedIso(): void
81+
{
82+
$this->expectException(InvalidOptionsException::class);
83+
new ZipCode('DE', iso: 'FR');
84+
}
85+
}

0 commit comments

Comments
 (0)