Added new validator for UUIDs
This commit is contained in:
parent
790ba4cb3d
commit
19931c984c
60
src/Symfony/Component/Validator/Constraints/Uuid.php
Normal file
60
src/Symfony/Component/Validator/Constraints/Uuid.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
*
|
||||
* @author Colin O'Dell <colinodell@gmail.com>
|
||||
*/
|
||||
class Uuid extends Constraint
|
||||
{
|
||||
// Possible versions defined by RFC 4122
|
||||
const V1_MAC = 1;
|
||||
const V2_DCE = 2;
|
||||
const V3_MD5 = 3;
|
||||
const V4_RANDOM = 4;
|
||||
const V5_SHA1 = 5;
|
||||
|
||||
/**
|
||||
* Message to display when validation fails
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = 'This is not a valid UUID.';
|
||||
|
||||
/**
|
||||
* Strict mode only allows UUIDs that meet the formal definition and formatting per RFC 4122
|
||||
*
|
||||
* Set this to `false` to allow legacy formats with different dash positioning or wrapping characters
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $strict = true;
|
||||
|
||||
/**
|
||||
* Array of allowed versions (see version constants above)
|
||||
*
|
||||
* All UUID versions are allowed by default
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
public $versions = array(
|
||||
self::V1_MAC,
|
||||
self::V2_DCE,
|
||||
self::V3_MD5,
|
||||
self::V4_RANDOM,
|
||||
self::V5_SHA1
|
||||
);
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Validates whether the value is a valid UUID per RFC 4122.
|
||||
*
|
||||
* @author Colin O'Dell <colinodell@gmail.com>
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4122
|
||||
* @see https://en.wikipedia.org/wiki/Universally_unique_identifier
|
||||
*/
|
||||
class UuidValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* Regular expression which verifies allowed characters and the proper format.
|
||||
*
|
||||
* The strict pattern matches UUIDs like this: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
|
||||
* Roughly speaking: x = any hexadecimal character, M = any allowed version, N = any allowed variant.
|
||||
*/
|
||||
const STRICT_PATTERN = '/^[a-f0-9]{8}-[a-f0-9]{4}-[%s][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i';
|
||||
|
||||
/**
|
||||
* The loose pattern validates similar yet non-compliant UUIDs.
|
||||
*
|
||||
* Dashes are completely optional. If present, they should only appear between every fourth character.
|
||||
* The value can also be wrapped with characters like []{} for backwards-compatibility with other systems.
|
||||
* Neither the version nor the variant is validated by this pattern.
|
||||
*/
|
||||
const LOOSE_PATTERN = '/^[a-f0-9]{4}(?:-?[a-f0-9]{4}){7}$/i';
|
||||
|
||||
/**
|
||||
* Properly-formatted UUIDs contain 32 hex digits, separated by 4 dashes.
|
||||
* We can use this fact to avoid performing a preg_match on strings of other sizes.
|
||||
*/
|
||||
const STRICT_UUID_LENGTH = 36;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (null === $value || '' === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
|
||||
throw new UnexpectedTypeException($value, 'string');
|
||||
}
|
||||
|
||||
$value = (string) $value;
|
||||
|
||||
if ($constraint->strict) {
|
||||
// Insert the allowed versions into the regular expression
|
||||
$pattern = sprintf(static::STRICT_PATTERN, implode('', $constraint->versions));
|
||||
|
||||
if (strlen($value) !== static::STRICT_UUID_LENGTH || !preg_match($pattern, $value)) {
|
||||
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
|
||||
}
|
||||
} else {
|
||||
// Trim any wrapping characters like [] or {} used by some legacy systems
|
||||
$value = trim($value, '[]{}');
|
||||
|
||||
if (!preg_match(static::LOOSE_PATTERN, $value)) {
|
||||
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Tests\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraints\Uuid;
|
||||
use Symfony\Component\Validator\Constraints\UuidValidator;
|
||||
|
||||
/**
|
||||
* @author Colin O'Dell <colinodell@gmail.com>
|
||||
*/
|
||||
class UuidValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $context;
|
||||
protected $validator;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
|
||||
$this->validator = new UuidValidator();
|
||||
$this->validator->initialize($this->context);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->context = null;
|
||||
$this->validator = null;
|
||||
}
|
||||
|
||||
public function testNullIsValid()
|
||||
{
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate(null, new Uuid());
|
||||
}
|
||||
|
||||
public function testEmptyStringIsValid()
|
||||
{
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate('', new Uuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testExpectsStringCompatibleType()
|
||||
{
|
||||
$this->validator->validate(new \stdClass(), new Uuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getValidStrictUuids
|
||||
*/
|
||||
public function testValidStrictUuids($uuid)
|
||||
{
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate($uuid, new Uuid());
|
||||
}
|
||||
|
||||
public function getValidStrictUuids()
|
||||
{
|
||||
return array(
|
||||
array('216fff40-98d9-11e3-a5e2-0800200c9a66'), // Version 1 UUID in lowercase
|
||||
array('216FFF40-98D9-11E3-A5E2-0800200C9A66'), // Version 1 UUID in UPPERCASE
|
||||
array('456daefb-5aa6-41b5-8dbc-068b05a8b201'), // Version 4 UUID in lowercase
|
||||
array('456DAEFb-5AA6-41B5-8DBC-068B05A8B201'), // Version 4 UUID in UPPERCASE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getInvalidStrictUuids
|
||||
*/
|
||||
public function testInvalidStrictUuids($uuid)
|
||||
{
|
||||
$constraint = new Uuid(array(
|
||||
'message' => 'testMessage'
|
||||
));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation')
|
||||
->with('testMessage', array(
|
||||
'{{ value }}' => $uuid,
|
||||
));
|
||||
|
||||
$this->validator->validate($uuid, $constraint);
|
||||
}
|
||||
|
||||
public function getInvalidStrictUuids()
|
||||
{
|
||||
return array(
|
||||
array('216fff40-98d9-11e3-a5e2-0800200c9a6'), // Too few characters
|
||||
array('216fff40-98d9-11e3-a5e2-0800200c9a666'), // Too many characters
|
||||
array('V16fff40-98d9-11e3-a5e2-0800200c9a66'), // Invalid character 'V'
|
||||
array('2-16fff-4098d-911e3a5e20-800-200c9-a66'), // Non-standard dash positions (randomly placed)
|
||||
|
||||
// Non-standard UUIDs allowed by some other systems
|
||||
array('216f-ff40-98d9-11e3-a5e2-0800-200c-9a66'), // Non-standard dash positions (every 4 chars)
|
||||
array('216fff40-98d911e3-a5e20800-200c9a66'), // Non-standard dash positions (every 8 chars)
|
||||
array('216fff4098d911e3a5e20800200c9a66'), // No dashes at all
|
||||
array('{216fff40-98d9-11e3-a5e2-0800200c9a66}'), // Wrapped with curly braces
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getValidStrictUuids
|
||||
*/
|
||||
public function testVersionConstraintIsValid($uuid)
|
||||
{
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$constraint = new Uuid(array(
|
||||
'versions' => array(Uuid::V1_MAC, Uuid::V4_RANDOM)
|
||||
));
|
||||
|
||||
$this->validator->validate($uuid, $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getValidStrictUuids
|
||||
*/
|
||||
public function testVersionConstraintIsInvalid($uuid)
|
||||
{
|
||||
$constraint = new Uuid(array(
|
||||
'versions' => array(Uuid::V2_DCE, Uuid::V3_MD5)
|
||||
));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate($uuid, $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getValidNonStrictUuids
|
||||
*/
|
||||
public function testValidNonStrictUuids($uuid)
|
||||
{
|
||||
$constraint = new Uuid(array(
|
||||
'strict' => false
|
||||
));
|
||||
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate($uuid, $constraint);
|
||||
}
|
||||
|
||||
public function getValidNonStrictUuids()
|
||||
{
|
||||
return array(
|
||||
array('216fff40-98d9-11e3-a5e2-0800200c9a66'), // Version 1 UUID in lowercase
|
||||
array('216FFF40-98D9-11E3-A5E2-0800200C9A66'), // Version 1 UUID in UPPERCASE
|
||||
array('456daefb-5aa6-41b5-8dbc-068b05a8b201'), // Version 4 UUID in lowercase
|
||||
array('456DAEFb-5AA6-41B5-8DBC-068B05A8B201'), // Version 4 UUID in UPPERCASE
|
||||
|
||||
// Non-standard UUIDs allowed by some other systems
|
||||
array('216f-ff40-98d9-11e3-a5e2-0800-200c-9a66'), // Non-standard dash positions (every 4 chars)
|
||||
array('216fff40-98d911e3-a5e20800-200c9a66'), // Non-standard dash positions (every 8 chars)
|
||||
array('216fff4098d911e3a5e20800200c9a66'), // No dashes at all
|
||||
array('{216fff40-98d9-11e3-a5e2-0800200c9a66}'), // Wrapped with curly braces
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getInvalidNonStrictUuids
|
||||
*/
|
||||
public function testInvalidNonStrictUuids($uuid)
|
||||
{
|
||||
$constraint = new Uuid(array(
|
||||
'strict' => false
|
||||
));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate($uuid, $constraint);
|
||||
}
|
||||
|
||||
public function getInvalidNonStrictUuids()
|
||||
{
|
||||
return array(
|
||||
array('216fff40-98d9-11e3-a5e2-0800200c9a6'), // Too few characters
|
||||
array('216fff40-98d9-11e3-a5e2-0800200c9a666'), // Too many characters
|
||||
array('V16fff40-98d9-11e3-a5e2-0800200c9a66'), // Invalid character 'V'
|
||||
array('2-16fff-4098d-911e3a5e20-800-200c9-a66'), // Non-standard dash positions (randomly placed)
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user