Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 663d801

Browse files
committed
[Validator] Add support for types (ALL*, LOCAL_*, UNIVERSAL_*, UNICAST_*, MULTICAST_*, BROADCAST) in MacAddress constraint
1 parent 09437dc commit 663d801

File tree

4 files changed

+538
-4
lines changed

4 files changed

+538
-4
lines changed

src/Symfony/Component/Validator/Constraints/MacAddress.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Validator\Constraints;
1313

1414
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1516

1617
/**
1718
* Validates that a value is a valid MAC address.
@@ -21,22 +22,62 @@
2122
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
2223
class MacAddress extends Constraint
2324
{
25+
public const ALL = 'all';
26+
public const ALL_NO_BROADCAST = 'all_no_broadcast';
27+
public const LOCAL_ALL = 'local_all';
28+
public const LOCAL_NO_BROADCAST = 'local_no_broadcast';
29+
public const LOCAL_UNICAST = 'local_unicast';
30+
public const LOCAL_MULTICAST = 'local_multicast';
31+
public const LOCAL_MULTICAST_NO_BROADCAST = 'local_multicast_no_broadcast';
32+
public const UNIVERSAL_ALL = 'universal_all';
33+
public const UNIVERSAL_UNICAST = 'universal_unicast';
34+
public const UNIVERSAL_MULTICAST = 'universal_multicast';
35+
public const UNICAST_ALL = 'unicast_all';
36+
public const MULTICAST_ALL = 'multicast_all';
37+
public const MULTICAST_NO_BROADCAST = 'multicast_no_broadcast';
38+
public const BROADCAST = 'broadcast';
39+
2440
public const INVALID_MAC_ERROR = 'a183fbff-6968-43b4-82a2-cc5cf7150036';
2541

42+
private const TYPES = [
43+
self::ALL,
44+
self::ALL_NO_BROADCAST,
45+
self::LOCAL_ALL,
46+
self::LOCAL_NO_BROADCAST,
47+
self::LOCAL_UNICAST,
48+
self::LOCAL_MULTICAST,
49+
self::LOCAL_MULTICAST_NO_BROADCAST,
50+
self::UNIVERSAL_ALL,
51+
self::UNIVERSAL_UNICAST,
52+
self::UNIVERSAL_MULTICAST,
53+
self::UNICAST_ALL,
54+
self::MULTICAST_ALL,
55+
self::MULTICAST_NO_BROADCAST,
56+
self::BROADCAST,
57+
];
58+
2659
protected const ERROR_NAMES = [
2760
self::INVALID_MAC_ERROR => 'INVALID_MAC_ERROR',
2861
];
2962

3063
public ?\Closure $normalizer;
3164

65+
/**
66+
* @param self::ALL*|self::LOCAL_*|self::UNIVERSAL_*|self::UNICAST_*|self::MULTICAST_*|self::BROADCAST $type A mac address type to validate (defaults to {@see self::ALL})
67+
*/
3268
public function __construct(
3369
public string $message = 'This value is not a valid MAC address.',
70+
public string $type = self::ALL,
3471
?callable $normalizer = null,
3572
?array $groups = null,
3673
mixed $payload = null,
3774
) {
3875
parent::__construct(null, $groups, $payload);
3976

77+
if (!\in_array($this->type, self::TYPES, true)) {
78+
throw new ConstraintDefinitionException(sprintf('The option "type" must be one of "%s".', implode('", "', self::TYPES)));
79+
}
80+
4081
$this->normalizer = null !== $normalizer ? $normalizer(...) : null;
4182
}
4283
}

src/Symfony/Component/Validator/Constraints/MacAddressValidator.php

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,33 @@
2323
*/
2424
class MacAddressValidator extends ConstraintValidator
2525
{
26+
/**
27+
* Checks whether an MAC address is valid.
28+
*/
29+
private static function checkMac(string $mac, string $type): bool
30+
{
31+
if (!filter_var($mac, \FILTER_VALIDATE_MAC)) {
32+
return false;
33+
}
34+
35+
return match ($type) {
36+
MacAddress::LOCAL_ALL => self::isLocal($mac),
37+
MacAddress::LOCAL_NO_BROADCAST => self::isLocal($mac) && !self::isBroadcast($mac),
38+
MacAddress::LOCAL_UNICAST => self::isLocal($mac) && self::isUnicast($mac),
39+
MacAddress::LOCAL_MULTICAST => self::isLocal($mac) && !self::isUnicast($mac),
40+
MacAddress::LOCAL_MULTICAST_NO_BROADCAST => self::isLocal($mac) && !self::isUnicast($mac) && !self::isBroadcast($mac),
41+
MacAddress::UNIVERSAL_ALL => !self::isLocal($mac),
42+
MacAddress::UNIVERSAL_UNICAST => !self::isLocal($mac) && self::isUnicast($mac),
43+
MacAddress::UNIVERSAL_MULTICAST => !self::isLocal($mac) && !self::isUnicast($mac),
44+
MacAddress::UNICAST_ALL => self::isUnicast($mac),
45+
MacAddress::MULTICAST_ALL => !self::isUnicast($mac),
46+
MacAddress::MULTICAST_NO_BROADCAST => !self::isUnicast($mac) && !self::isBroadcast($mac),
47+
MacAddress::BROADCAST => self::isBroadcast($mac),
48+
MacAddress::ALL_NO_BROADCAST => !self::isBroadcast($mac),
49+
default => true,
50+
};
51+
}
52+
2653
public function validate(mixed $value, Constraint $constraint): void
2754
{
2855
if (!$constraint instanceof MacAddress) {
@@ -43,11 +70,49 @@ public function validate(mixed $value, Constraint $constraint): void
4370
$value = ($constraint->normalizer)($value);
4471
}
4572

46-
if (!filter_var($value, \FILTER_VALIDATE_MAC)) {
73+
if (!self::checkMac($value, $constraint->type)) {
4774
$this->context->buildViolation($constraint->message)
4875
->setParameter('{{ value }}', $this->formatValue($value))
4976
->setCode(MacAddress::INVALID_MAC_ERROR)
5077
->addViolation();
5178
}
5279
}
80+
81+
/**
82+
* Checks whether an MAC address is unicast or multicast.
83+
*/
84+
private static function isUnicast(string $mac): bool
85+
{
86+
return match (self::sanitize($mac)[1]) {
87+
'0', '4', '8', 'c', '2', '6', 'a', 'e' => true,
88+
default => false,
89+
};
90+
}
91+
92+
/**
93+
* Checks whether an MAC address is local or universal.
94+
*/
95+
private static function isLocal(string $mac): bool
96+
{
97+
return match (self::sanitize($mac)[1]) {
98+
'2', '6', 'a', 'e', '3', '7', 'b', 'f' => true,
99+
default => false,
100+
};
101+
}
102+
103+
/**
104+
* Checks whether an MAC address is broadcast.
105+
*/
106+
private static function isBroadcast(string $mac): bool
107+
{
108+
return 'ffffffffffff' === self::sanitize($mac);
109+
}
110+
111+
/**
112+
* Returns sanitized mac address.
113+
*/
114+
private static function sanitize(string $mac): string
115+
{
116+
return strtolower(str_replace([':', '-', '.'], '', $mac));
117+
}
53118
}

src/Symfony/Component/Validator/Tests/Constraints/MacAddressTest.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,16 @@ public function testAttributes()
3737
[$aConstraint] = $metadata->properties['a']->getConstraints();
3838
self::assertSame('myMessage', $aConstraint->message);
3939
self::assertEquals(trim(...), $aConstraint->normalizer);
40+
self::assertSame(MacAddress::ALL, $aConstraint->type);
4041
self::assertSame(['Default', 'MacAddressDummy'], $aConstraint->groups);
4142

4243
[$bConstraint] = $metadata->properties['b']->getConstraints();
43-
self::assertSame(['my_group'], $bConstraint->groups);
44-
self::assertSame('some attached data', $bConstraint->payload);
44+
self::assertSame(MacAddress::LOCAL_UNICAST, $bConstraint->type);
45+
self::assertSame(['Default', 'MacAddressDummy'], $bConstraint->groups);
46+
47+
[$cConstraint] = $metadata->properties['c']->getConstraints();
48+
self::assertSame(['my_group'], $cConstraint->groups);
49+
self::assertSame('some attached data', $cConstraint->payload);
4550
}
4651
}
4752

@@ -50,6 +55,9 @@ class MacAddressDummy
5055
#[MacAddress(message: 'myMessage', normalizer: 'trim')]
5156
private $a;
5257

53-
#[MacAddress(groups: ['my_group'], payload: 'some attached data')]
58+
#[MacAddress(type: MacAddress::LOCAL_UNICAST)]
5459
private $b;
60+
61+
#[MacAddress(groups: ['my_group'], payload: 'some attached data')]
62+
private $c;
5563
}

0 commit comments

Comments
 (0)