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

Skip to content

[Validator] Add support for types (ALL*, LOCAL_*, UNIVERSAL_*, UNICAST_*, MULTICAST_*, BROADCAST) in MacAddress constraint #54473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/Symfony/Component/Validator/Constraints/MacAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;

/**
* Validates that a value is a valid MAC address.
Expand All @@ -21,22 +22,62 @@
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class MacAddress extends Constraint
{
public const ALL = 'all';
public const ALL_NO_BROADCAST = 'all_no_broadcast';
public const LOCAL_ALL = 'local_all';
public const LOCAL_NO_BROADCAST = 'local_no_broadcast';
public const LOCAL_UNICAST = 'local_unicast';
public const LOCAL_MULTICAST = 'local_multicast';
public const LOCAL_MULTICAST_NO_BROADCAST = 'local_multicast_no_broadcast';
public const UNIVERSAL_ALL = 'universal_all';
public const UNIVERSAL_UNICAST = 'universal_unicast';
public const UNIVERSAL_MULTICAST = 'universal_multicast';
public const UNICAST_ALL = 'unicast_all';
public const MULTICAST_ALL = 'multicast_all';
public const MULTICAST_NO_BROADCAST = 'multicast_no_broadcast';
public const BROADCAST = 'broadcast';

public const INVALID_MAC_ERROR = 'a183fbff-6968-43b4-82a2-cc5cf7150036';

private const TYPES = [
self::ALL,
self::ALL_NO_BROADCAST,
self::LOCAL_ALL,
self::LOCAL_NO_BROADCAST,
self::LOCAL_UNICAST,
self::LOCAL_MULTICAST,
self::LOCAL_MULTICAST_NO_BROADCAST,
self::UNIVERSAL_ALL,
self::UNIVERSAL_UNICAST,
self::UNIVERSAL_MULTICAST,
self::UNICAST_ALL,
self::MULTICAST_ALL,
self::MULTICAST_NO_BROADCAST,
self::BROADCAST,
];

protected const ERROR_NAMES = [
self::INVALID_MAC_ERROR => 'INVALID_MAC_ERROR',
];

public ?\Closure $normalizer;

/**
* @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})
*/
public function __construct(
public string $message = 'This value is not a valid MAC address.',
public string $type = self::ALL,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a BC break as the constraint was added in 7.1.

?callable $normalizer = null,
?array $groups = null,
mixed $payload = null,
) {
parent::__construct(null, $groups, $payload);

if (!\in_array($this->type, self::TYPES, true)) {
throw new ConstraintDefinitionException(sprintf('The option "type" must be one of "%s".', implode('", "', self::TYPES)));
}

$this->normalizer = null !== $normalizer ? $normalizer(...) : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,76 @@ public function validate(mixed $value, Constraint $constraint): void
$value = ($constraint->normalizer)($value);
}

if (!filter_var($value, \FILTER_VALIDATE_MAC)) {
if (!self::checkMac($value, $constraint->type)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(MacAddress::INVALID_MAC_ERROR)
->addViolation();
}
}

/**
* Checks whether a MAC address is valid.
*/
private static function checkMac(string $mac, string $type): bool
{
if (!filter_var($mac, \FILTER_VALIDATE_MAC)) {
return false;
}

return match ($type) {
MacAddress::ALL => true,
MacAddress::ALL_NO_BROADCAST => !self::isBroadcast($mac),
MacAddress::LOCAL_ALL => self::isLocal($mac),
MacAddress::LOCAL_NO_BROADCAST => self::isLocal($mac) && !self::isBroadcast($mac),
MacAddress::LOCAL_UNICAST => self::isLocal($mac) && self::isUnicast($mac),
MacAddress::LOCAL_MULTICAST => self::isLocal($mac) && !self::isUnicast($mac),
MacAddress::LOCAL_MULTICAST_NO_BROADCAST => self::isLocal($mac) && !self::isUnicast($mac) && !self::isBroadcast($mac),
MacAddress::UNIVERSAL_ALL => !self::isLocal($mac),
MacAddress::UNIVERSAL_UNICAST => !self::isLocal($mac) && self::isUnicast($mac),
MacAddress::UNIVERSAL_MULTICAST => !self::isLocal($mac) && !self::isUnicast($mac),
MacAddress::UNICAST_ALL => self::isUnicast($mac),
MacAddress::MULTICAST_ALL => !self::isUnicast($mac),
MacAddress::MULTICAST_NO_BROADCAST => !self::isUnicast($mac) && !self::isBroadcast($mac),
MacAddress::BROADCAST => self::isBroadcast($mac),
};
}

/**
* Checks whether a MAC address is unicast or multicast.
*/
private static function isUnicast(string $mac): bool
{
return match (self::sanitize($mac)[1]) {
'0', '4', '8', 'c', '2', '6', 'a', 'e' => true,
default => false,
};
}

/**
* Checks whether a MAC address is local or universal.
*/
private static function isLocal(string $mac): bool
{
return match (self::sanitize($mac)[1]) {
'2', '6', 'a', 'e', '3', '7', 'b', 'f' => true,
default => false,
};
}

/**
* Checks whether a MAC address is broadcast.
*/
private static function isBroadcast(string $mac): bool
{
return 'ffffffffffff' === self::sanitize($mac);
}

/**
* Returns the sanitized MAC address.
*/
private static function sanitize(string $mac): string
{
return strtolower(str_replace([':', '-', '.'], '', $mac));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ public function testAttributes()
[$aConstraint] = $metadata->properties['a']->getConstraints();
self::assertSame('myMessage', $aConstraint->message);
self::assertEquals(trim(...), $aConstraint->normalizer);
self::assertSame(MacAddress::ALL, $aConstraint->type);
self::assertSame(['Default', 'MacAddressDummy'], $aConstraint->groups);

[$bConstraint] = $metadata->properties['b']->getConstraints();
self::assertSame(['my_group'], $bConstraint->groups);
self::assertSame('some attached data', $bConstraint->payload);
self::assertSame(MacAddress::LOCAL_UNICAST, $bConstraint->type);
self::assertSame(['Default', 'MacAddressDummy'], $bConstraint->groups);

[$cConstraint] = $metadata->properties['c']->getConstraints();
self::assertSame(['my_group'], $cConstraint->groups);
self::assertSame('some attached data', $cConstraint->payload);
}
}

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

#[MacAddress(groups: ['my_group'], payload: 'some attached data')]
#[MacAddress(type: MacAddress::LOCAL_UNICAST)]
private $b;

#[MacAddress(groups: ['my_group'], payload: 'some attached data')]
private $c;
}
Loading