8000 feature #51862 [Validator] Add `MacAddress` constraint for validating… · symfony/symfony@a935659 · GitHub
[go: up one dir, main page]

Skip to content

Commit a935659

Browse files
feature #51862 [Validator] Add MacAddress constraint for validating MAC address (Ninos)
This PR was merged into the 7.1 branch. Discussion ---------- [Validator] Add `MacAddress` constraint for validating MAC address | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | License | MIT Possibility to validate against mac address. See also past discussion: #51777 Commits ------- 06ccf62 [Validator] Add `MacAddress` constraint for validating MAC address
2 parents 0685c42 + 06ccf62 commit a935659

File tree

7 files changed

+309
-0
lines changed

7 files changed

+309
-0
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
7.1
55
---
66

7+
* Add `MacAddress` constraint
78
* Add `list` and `associative_array` types to `Type` constraint
89
* Add the `Charset` constraint
910

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\InvalidArgumentException;
16+
17+
/**
18+
* Validates that a value is a valid MAC address.
19+
*
20+
* @author Ninos Ego <me@ninosego.de>
21+
*/
22+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
23+
class MacAddress extends Constraint
24+
{
25+
public const INVALID_MAC_ERROR = 'a183fbff-6968-43b4-82a2-cc5cf7150036';
26+
27+
protected const ERROR_NAMES = [
28+
self::INVALID_MAC_ERROR => 'INVALID_MAC_ERROR',
29+
];
30+
31+
public string $message = 'This is not a valid MAC address.';
32+
33+
/** @var callable|null */
34+
public $normalizer;
35+
36+
public function __construct(
37+
array $options = null,
38+
string $message = null,
39+
callable $normalizer = null,
40+
array $groups = null,
41+
mixed $payload = null,
42+
) {
43+
parent::__construct($options, $groups, $payload);
44+
45+
$this->message = $message ?? $this->message;
46+
$this->normalizer = $normalizer ?? $this->normalizer;
47+
48+
if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
49+
throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
50+
}
51+
}
52+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
18+
19+
/**
20+
* Validates whether a value is a valid MAC address.
21+
*
22+
* @author Ninos Ego <me@ninosego.de>
23+
*/
24+
class MacAddressValidator extends ConstraintValidator
25+
{
26+
public function validate(mixed $value, Constraint $constraint): void
27+
{
28+
if (!$constraint instanceof MacAddress) {
29+
throw new UnexpectedTypeException($constraint, MacAddress::class);
30+
}
31+
32+
if (null === $value || '' === $value) {
33+
return;
34+
}
35+
36+
if (!\is_scalar($value) && !$value instanceof \Stringable) {
37+
throw new UnexpectedValueException($value, 'string');
38+
}
39+
40+
$value = (string) $value;
41+
42+
if (null !== $constraint->normalizer) {
43+
$value = ($constraint->normalizer)($value);
44+
}
45+
46+
if (!filter_var($value, \FILTER_VALIDATE_MAC)) {
47+
$this->context->buildViolation($constraint->message)
48+
->setParameter('{{ value }}', $this->formatValue($value))
49+
->setCode(MacAddress::INVALID_MAC_ERROR)
50+
->addViolation();
51+
}
52+
}
53+
}

src/Symfony/Component/Validator/Resources/translations/validators.de.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@
434434
<source>The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}.</source>
435435
<target>Der erkannte Zeichensatz ist nicht gültig ({{ detected }}). Gültige Zeichensätze sind {{ encodings }}.</target>
436436
</trans-unit>
437+
<trans-unit id="112">
438+
<source>This is not a valid MAC address.</source>
439+
<target>Dies ist keine gültige MAC-Adresse.</target>
440+
</trans-unit>
437441
</body>
438442
</file>
439443
</xliff>

src/Symfony/Component/Validator/Resources/translations/validators.en.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@
434434
<source>The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}.</source>
435435
<target>The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}.</target>
436436
</trans-unit>
437+
<trans-unit id="112">
438+
<source>This is not a valid MAC address.</source>
439+
<target>This is not a valid MAC address.</target>
440+
</trans-unit>
437441
</body>
438442
</file>
439443
</xliff>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\Constraints\MacAddress;
16+
use Symfony\Component\Validator\Exception\InvalidArgumentException;
17+
use Symfony\Component\Validator\Mapping\ClassMetadata;
18+
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader;
19+
20+
/**
21+
* @author Ninos Ego <me@ninosego.de>
22+
*/
23+
class MacAddressTest extends TestCase
24+
{
25+
public function testNormalizerCanBeSet()
26+
{
27+
$mac = new MacAddress(['normalizer' => 'trim']);
28+
29+
$this->assertEquals('trim', $mac->normalizer);
30+
}
31+
32+
public function testInvalidNormalizerThrowsException()
33+
{
34+
$this->expectException(InvalidArgumentException::class);
35+
$this->expectExceptionMessage('The "normalizer" option must be a valid callable ("string" given).');
36+
new MacAddress(['normalizer' => 'Unknown Callable']);
37+
}
38+
39+
public function testInvalidNormalizerObjectThrowsException()
40+
{
41+
$this->expectException(InvalidArgumentException::class);
42+
$this->expectExceptionMessage('The "normalizer" option must be a valid callable ("stdClass" given).');
43+
new MacAddress(['normalizer' => new \stdClass()]);
44+
}
45+
46+
public function testAttributes()
47+
{
48+
$metadata = new ClassMetadata(MacAddressDummy::class);
49+
$loader = new AttributeLoader();
50+
self::assertTrue($loader->loadClassMetadata($metadata));
51+
52+
[$aConstraint] = $metadata->properties['a']->getConstraints();
53+
self::assertSame('myMessage', $aConstraint->message);
54+
self::assertSame('trim', $aConstraint->normalizer);
55+
self::assertSame(['Default', 'MacAddressDummy'], $aConstraint->groups);
56+
57+
[$bConstraint] = $metadata->properties['b']->getConstraints();
58+
self::assertSame(['my_group'], $bConstraint->groups);
59+
self::assertSame('some attached data', $bConstraint->payload);
60+
}
61+
}
62+
63+
class MacAddressDummy
64+
{
65+
#[MacAddress(message: 'myMessage', normalizer: 'trim')]
66+
private $a;
67+
68+
#[MacAddress(groups: ['my_group'], payload: 'some attached data')]
69+
private $b;
70+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use Symfony\Component\Validator\Constraints\MacAddress;
15+
use Symfony\Component\Validator\Constraints\MacAddressValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
17+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
18+
19+
/**
20+
* @author Ninos Ego <me@ninosego.de>
21+
*/
22+
class MacAddressValidatorTest extends ConstraintValidatorTestCase
23+
{
24+
protected function createValidator(): MacAddressValidator
25+
{
26+
return new MacAddressValidator();
27+
}
28+
29+
public function testNullIsValid()
30+
{
31+
$this->validator->validate(null, new MacAddress());
32+
33+
$this->assertNoViolation();
34+
}
35+
36+
public function testEmptyStringIsValid()
37+
{
38+
$this->validator->validate('', new MacAddress());
39+
40+
$this->assertNoViolation();
41+
}
42+
43+
public function testExpectsStringCompatibleType()
44+
{
45+
$this->expectException(UnexpectedValueException::class);
46+
$this->validator->validate(new \stdClass(), new MacAddress());
47+
}
48+
49+
/**
50+
* @dataProvider getValidMacs
51+
*/
52+
public function testValidMac($mac)
53+
{
54+
$this->validator->validate($mac, new MacAddress());
55+
56+
$this->assertNoViolation();
57+
}
58+
59+
public static function getValidMacs(): array
60+
{
61+
return [
62+
['00:00:00:00:00:00'],
63+
['00-00-00-00-00-00'],
64+
['ff:ff:ff:ff:ff:ff'],
65+
['ff-ff-ff-ff-ff-ff'],
66+
['FF:FF:FF:FF:FF:FF'],
67+
['FF-FF-FF-FF-FF-FF'],
68+
];
69+
}
70+
71+
/**
72+
* @dataProvider getValidMacsWithWhitespaces
73+
*/
74+
public function testValidMacsWithWhitespaces($mac)
75+
{
76+
$this->validator->validate($mac, new MacAddress([
77+
'normalizer' => 'trim',
78+
]));
79+
80+
$this->assertNoViolation();
81+
}
82+
83+
public static function getValidMacsWithWhitespaces(): array
84+
{
85+
return [
86+
["\x2000:00:00:00:00:00"],
87+
["\x09\x0900-00-00-00-00-00"],
88+
["ff:ff:ff:ff:ff:ff\x0A"],
89+
["ff-ff-ff-ff-ff-ff\x0D\x0D"],
90+
["\x00FF:FF:FF:FF:FF:FF\x00"],
91+
["\x0B\x0BFF-FF-FF-FF-FF-FF\x0B\x0B"],
92+
];
93+
}
94+
95+
/**
96+
* @dataProvider getInvalidMacs
97+
*/
98+
public function testInvalidMacs($mac)
99+
{
100+
$constraint = new MacAddress([
101+
'message' => 'myMessage',
102+
]);
103+
104+
$this->validator->validate($mac, $constraint);
105+
106+
$this->buildViolation('myMessage')
107+
->setParameter('{{ value }}', '"'.$mac.'"')
108+
->setCode(MacAddress::INVALID_MAC_ERROR)
109+
->assertRaised();
110+
}
111+
112+
public static function getInvalidMacs(): array
113+
{
114+
return [
115+
['0'],
116+
['00:00'],
117+
['00:00:00'],
118+
['00:00:00:00'],
119+
['00:00:00:00:00'],
120+
['00:00:00:00:00:000'],
121+
['-00:00:00:00:00:00'],
122+
['foobar'],
123+
];
124+
}
125+
}

0 commit comments

Comments
 (0)
0