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

Skip to content

Commit 16b9210

Browse files
Ninosfabpot
authored andcommitted
[Validator] Add support for types (ALL*, LOCAL_*, UNIVERSAL_*, UNICAST_*, MULTICAST_*, BROADCAST) in MacAddress constraint
1 parent 09437dc commit 16b9210

File tree

4 files changed

+538
-4
lines changed

4 files changed

+538
-4
lines changed

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

+41
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

+66-1
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,76 @@ public function validate(mixed $value, Constraint $constraint): void
4343
$value = ($constraint->normalizer)($value);
4444
}
4545

46-
if (!filter_var($value, \FILTER_VALIDATE_MAC)) {
46+
if (!self::checkMac($value, $constraint->type)) {
4747
$this->context->buildViolation($constraint->message)
4848
->setParameter('{{ value }}', $this->formatValue($value))
4949
->setCode(MacAddress::INVALID_MAC_ERROR)
5050
->addViolation();
5151
}
5252
}
53+
54+
/**
55+
* Checks whether a MAC address is valid.
56+
*/
57+
private static function checkMac(string $mac, string $type): bool
58+
{
59+
if (!filter_var($mac, \FILTER_VALIDATE_MAC)) {
60+
return false;
61+
}
62+
63+
return match ($type) {
64+
MacAddress::ALL => true,
65+
MacAddress::ALL_NO_BROADCAST => !self::isBroadcast($mac),
66+
MacAddress::LOCAL_ALL => self::isLocal($mac),
67+
MacAddress::LOCAL_NO_BROADCAST => self::isLocal($mac) && !self::isBroadcast($mac),
68+
MacAddress::LOCAL_UNICAST => self::isLocal($mac) && self::isUnicast($mac),
69+
MacAddress::LOCAL_MULTICAST => self::isLocal($mac) && !self::isUnicast($mac),
70+
MacAddress::LOCAL_MULTICAST_NO_BROADCAST => self::isLocal($mac) && !self::isUnicast($mac) && !self::isBroadcast($mac),
71+
MacAddress::UNIVERSAL_ALL => !self::isLocal($mac),
72+
MacAddress::UNIVERSAL_UNICAST => !self::isLocal($mac) && self::isUnicast($mac),
73+
MacAddress::UNIVERSAL_MULTICAST => !self::isLocal($mac) && !self::isUnicast($mac),
74+
MacAddress::UNICAST_ALL => self::isUnicast($mac),
75+
MacAddress::MULTICAST_ALL => !self::isUnicast($mac),
76+
MacAddress::MULTICAST_NO_BROADCAST => !self::isUnicast($mac) && !self::isBroadcast($mac),
77+
MacAddress::BROADCAST => self::isBroadcast($mac),
78+
};
79+
}
80+
81+
/**
82+
* Checks whether a 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 a 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 a 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 the 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

+11-3
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)