Added new validator for UUIDs

This commit is contained in:
Colin O'Dell 2014-02-18 15:53:41 -05:00 committed by Fabien Potencier
parent 790ba4cb3d
commit 19931c984c
3 changed files with 344 additions and 0 deletions

View 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
);
}

View File

@ -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));
}
}
}
}

View File

@ -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)
);
}
}