4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Validator\Constraints;
14 use Symfony\Component\Validator\Context\ExecutionContextInterface;
15 use Symfony\Component\Validator\Constraint;
16 use Symfony\Component\Validator\ConstraintValidator;
17 use Symfony\Component\Validator\Constraints\Deprecated\UuidValidator as Deprecated;
18 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
21 * Validates whether the value is a valid UUID per RFC 4122.
23 * @author Colin O'Dell <colinodell@gmail.com>
24 * @author Bernhard Schussek <bschussek@gmail.com>
26 * @see http://tools.ietf.org/html/rfc4122
27 * @see https://en.wikipedia.org/wiki/Universally_unique_identifier
29 class UuidValidator extends ConstraintValidator
31 // The strict pattern matches UUIDs like this:
32 // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
35 // x = any hexadecimal character
36 // M = any allowed version {1..5}
37 // N = any allowed variant {8, 9, a, b}
39 const STRICT_LENGTH = 36;
40 const STRICT_FIRST_HYPHEN_POSITION = 8;
41 const STRICT_LAST_HYPHEN_POSITION = 23;
42 const STRICT_VERSION_POSITION = 14;
43 const STRICT_VARIANT_POSITION = 19;
45 // The loose pattern validates similar yet non-compliant UUIDs.
46 // Hyphens are completely optional. If present, they should only appear
47 // between every fourth character:
48 // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
49 // xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx
50 // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
52 // The value can also be wrapped with characters like []{}:
53 // {xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx}
55 // Neither the version nor the variant is validated by this pattern.
57 const LOOSE_MAX_LENGTH = 39;
58 const LOOSE_FIRST_HYPHEN_POSITION = 4;
61 * @deprecated since version 2.6, to be removed in 3.0
63 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';
66 * @deprecated since version 2.6, to be removed in 3.0
68 const LOOSE_PATTERN = '/^[a-f0-9]{4}(?:-?[a-f0-9]{4}){7}$/i';
71 * @deprecated since version 2.6, to be removed in 3.0
73 const STRICT_UUID_LENGTH = 36;
78 public function validate($value, Constraint $constraint)
80 if (null === $value || '' === $value) {
84 if (!$constraint instanceof Uuid) {
85 throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Uuid');
88 if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
89 throw new UnexpectedTypeException($value, 'string');
92 $value = (string) $value;
94 if ($constraint->strict) {
95 $this->validateStrict($value, $constraint);
100 $this->validateLoose($value, $constraint);
103 private function validateLoose($value, Uuid $constraint)
106 // 1. ERROR_INVALID_CHARACTERS
107 // 2. ERROR_INVALID_HYPHEN_PLACEMENT
108 // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG
110 // Trim any wrapping characters like [] or {} used by some legacy systems
111 $trimmed = trim($value, '[]{}');
113 // Position of the next expected hyphen
114 $h = self::LOOSE_FIRST_HYPHEN_POSITION;
117 $l = self::LOOSE_MAX_LENGTH;
119 for ($i = 0; $i < $l; ++$i) {
121 if (!isset($trimmed[$i])) {
122 if ($this->context instanceof ExecutionContextInterface) {
123 $this->context->buildViolation($constraint->message)
124 ->setParameter('{{ value }}', $this->formatValue($value))
125 ->setCode(Uuid::TOO_SHORT_ERROR)
128 $this->buildViolation($constraint->message)
129 ->setParameter('{{ value }}', $this->formatValue($value))
130 ->setCode(Uuid::TOO_SHORT_ERROR)
137 // Hyphens must occur every fifth position
138 // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
140 if ('-' === $trimmed[$i]) {
142 if ($this->context instanceof ExecutionContextInterface) {
143 $this->context->buildViolation($constraint->message)
144 ->setParameter('{{ value }}', $this->formatValue($value))
145 ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
148 $this->buildViolation($constraint->message)
149 ->setParameter('{{ value }}', $this->formatValue($value))
150 ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
162 // Missing hyphens are ignored
169 if (!ctype_xdigit($trimmed[$i])) {
170 if ($this->context instanceof ExecutionContextInterface) {
171 $this->context->buildViolation($constraint->message)
172 ->setParameter('{{ value }}', $this->formatValue($value))
173 ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
176 $this->buildViolation($constraint->message)
177 ->setParameter('{{ value }}', $this->formatValue($value))
178 ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
186 // Check length again
187 if (isset($trimmed[$i])) {
188 if ($this->context instanceof ExecutionContextInterface) {
189 $this->context->buildViolation($constraint->message)
190 ->setParameter('{{ value }}', $this->formatValue($value))
191 ->setCode(Uuid::TOO_LONG_ERROR)
194 $this->buildViolation($constraint->message)
195 ->setParameter('{{ value }}', $this->formatValue($value))
196 ->setCode(Uuid::TOO_LONG_ERROR)
202 private function validateStrict($value, Uuid $constraint)
205 // 1. ERROR_INVALID_CHARACTERS
206 // 2. ERROR_INVALID_HYPHEN_PLACEMENT
207 // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG
208 // 4. ERROR_INVALID_VERSION
209 // 5. ERROR_INVALID_VARIANT
211 // Position of the next expected hyphen
212 $h = self::STRICT_FIRST_HYPHEN_POSITION;
214 for ($i = 0; $i < self::STRICT_LENGTH; ++$i) {
216 if (!isset($value[$i])) {
217 if ($this->context instanceof ExecutionContextInterface) {
218 $this->context->buildViolation($constraint->message)
219 ->setParameter('{{ value }}', $this->formatValue($value))
220 ->setCode(Uuid::TOO_SHORT_ERROR)
223 $this->buildViolation($constraint->message)
224 ->setParameter('{{ value }}', $this->formatValue($value))
225 ->setCode(Uuid::TOO_SHORT_ERROR)
232 // Check hyphen placement
233 // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
235 if ('-' === $value[$i]) {
237 if ($this->context instanceof ExecutionContextInterface) {
238 $this->context->buildViolation($constraint->message)
239 ->setParameter('{{ value }}', $this->formatValue($value))
240 ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
243 $this->buildViolation($constraint->message)
244 ->setParameter('{{ value }}', $this->formatValue($value))
245 ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
252 // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
254 if ($h < self::STRICT_LAST_HYPHEN_POSITION) {
262 if (!ctype_xdigit($value[$i])) {
263 if ($this->context instanceof ExecutionContextInterface) {
264 $this->context->buildViolation($constraint->message)
265 ->setParameter('{{ value }}', $this->formatValue($value))
266 ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
269 $this->buildViolation($constraint->message)
270 ->setParameter('{{ value }}', $this->formatValue($value))
271 ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
280 if ($this->context instanceof ExecutionContextInterface) {
281 $this->context->buildViolation($constraint->message)
282 ->setParameter('{{ value }}', $this->formatValue($value))
283 ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
286 $this->buildViolation($constraint->message)
287 ->setParameter('{{ value }}', $this->formatValue($value))
288 ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
296 // Check length again
297 if (isset($value[$i])) {
298 if ($this->context instanceof ExecutionContextInterface) {
299 $this->context->buildViolation($constraint->message)
300 ->setParameter('{{ value }}', $this->formatValue($value))
301 ->setCode(Uuid::TOO_LONG_ERROR)
304 $this->buildViolation($constraint->message)
305 ->setParameter('{{ value }}', $this->formatValue($value))
306 ->setCode(Uuid::TOO_LONG_ERROR)
312 if (!in_array($value[self::STRICT_VERSION_POSITION], $constraint->versions)) {
313 if ($this->context instanceof ExecutionContextInterface) {
314 $this->context->buildViolation($constraint->message)
315 ->setParameter('{{ value }}', $this->formatValue($value))
316 ->setCode(Uuid::INVALID_VERSION_ERROR)
319 $this->buildViolation($constraint->message)
320 ->setParameter('{{ value }}', $this->formatValue($value))
321 ->setCode(Uuid::INVALID_VERSION_ERROR)
326 // Check variant - first two bits must equal "10"
330 if ((hexdec($value[self::STRICT_VARIANT_POSITION]) & 12) !== 8) {
331 if ($this->context instanceof ExecutionContextInterface) {
332 $this->context->buildViolation($constraint->message)
333 ->setParameter('{{ value }}', $this->formatValue($value))
334 ->setCode(Uuid::INVALID_VARIANT_ERROR)
337 $this->buildViolation($constraint->message)
338 ->setParameter('{{ value }}', $this->formatValue($value))
339 ->setCode(Uuid::INVALID_VARIANT_ERROR)