Skip to content

Commit f6e6fbf

Browse files
authored
Merge pull request #23 from chadicus/uuid-filter
Add filter for UUID values
2 parents b666aeb + 140f12a commit f6e6fbf

File tree

4 files changed

+283
-1
lines changed

4 files changed

+283
-1
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,38 @@ $value = '{ "string": "value", "array": [1, 2, 3] }';
152152
assert($value === ['string' => 'value', 'array' => [1, 2, 3]]);
153153
```
154154

155+
#### UuidFilter::filter
156+
157+
This filter verifies a given string is a valid universally unique identifier.
158+
159+
The second parameter can be set to `true` to allow null values through without an error.
160+
161+
The third parameter can be set to `true` to allow [Nil UUIDs](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.7) values through without an error.
162+
163+
The fourth parameter determines which UUID version the value will be validated against. By default, the filter will succeed if the values matches version 1, 2, 3, 4, 5, 6, or 7
164+
165+
```php
166+
// Filtering an UUID string
167+
$value = '1a42403c-a29d-11ef-b864-0242ac120002';
168+
$filtered = \TraderInteractive\Filter\UuidFilter::filter($value);
169+
assert($value === $filtered);
170+
171+
// Filtering null values
172+
$value = null;
173+
$filtered = \TraderInteractive\Filter\UuidFilter::filter($value, true);
174+
assert(null === $filtered);
175+
176+
// Filtering a nil UUID
177+
$value = '00000000-0000-0000-0000-000000000000';
178+
$filtered = \TraderInteractive\Filter\UuidFilter::filter($value, false, true);
179+
assert($value === $filtered);
180+
181+
// Filtering for only UUID v4
182+
$value = '1a42403c-a29d-41ef-b864-0242ac120002';
183+
$filtered = \TraderInteractive\Filter\UuidFilter::filter($value, false, false, [4]);
184+
assert($value === $filtered);
185+
```
186+
155187
#### XmlFilter::filter
156188

157189
This filter ensures the given string is valid XML.

src/Filter/UuidFilter.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
namespace TraderInteractive\Filter;
4+
5+
use InvalidArgumentException;
6+
use TraderInteractive\Exceptions\FilterException;
7+
8+
final class UuidFilter
9+
{
10+
/**
11+
* @var string
12+
*/
13+
const FILTER_ALIAS = 'uuid';
14+
15+
/**
16+
* @var string
17+
*/
18+
const UUID_PATTERN_FORMAT = '^[0-9A-F]{8}-[0-9A-F]{4}-[%d][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$';
19+
20+
/**
21+
* @var string
22+
*/
23+
const FILTER_ERROR_FORMAT = "Value '%s' is not a valid UUID. Versions checked (%s)";
24+
25+
/**
26+
* @var string
27+
*/
28+
const NIL_NOT_ALLOWED_ERROR_FORMAT = "Value '%s' is nil uuid, but nil values are not allowed.";
29+
30+
/**
31+
* @var string
32+
*/
33+
const NULL_NOT_ALLOWED_ERROR = "Value is null, but null values are not allowed.";
34+
35+
/**
36+
* @var string
37+
*/
38+
const UNSUPPORTED_VERSION_ERROR_FORMAT = 'Filter does not support UUID v%d';
39+
40+
/**
41+
* @var string
42+
*/
43+
const NIL_UUID = '00000000-0000-0000-0000-000000000000';
44+
45+
/**
46+
* @var array
47+
* @internal
48+
*/
49+
const VALID_UUID_VERSIONS = [1,2,3,4,5,6,7];
50+
51+
52+
/**
53+
* Filters a given string values to a valid UUID
54+
*
55+
* @param string|null $value The value to be filtered.
56+
* @param bool $allowNull Flag to allow value to be null.
57+
* @param bool $allowNil Flag to allow value to be a NIL UUID.
58+
* @param array $versions List of specific UUID version to validate against.
59+
*
60+
* @return string|null
61+
*
62+
* @throws FilterException Thrown if value cannot be filtered as an UUID.
63+
*/
64+
public static function filter(
65+
string $value = null,
66+
bool $allowNull = false,
67+
bool $allowNil = false,
68+
array $versions = self::VALID_UUID_VERSIONS
69+
) {
70+
if (self::valueIsNullAndValid($allowNull, $value)) {
71+
return null;
72+
}
73+
74+
if (self::valueIsNilAndValid($allowNil, $value)) {
75+
return self::NIL_UUID;
76+
}
77+
78+
self::validateVersions($versions);
79+
foreach ($versions as $version) {
80+
$pattern = sprintf(self::UUID_PATTERN_FORMAT, $version);
81+
if (preg_match("/{$pattern}/i", $value)) {
82+
return $value;
83+
}
84+
}
85+
86+
throw new FilterException(
87+
sprintf(
88+
self::FILTER_ERROR_FORMAT,
89+
$value,
90+
implode(', ', $versions)
91+
)
92+
);
93+
}
94+
95+
private static function valueIsNullAndValid(bool $allowNull, string $value = null): bool
96+
{
97+
if ($allowNull === false && $value === null) {
98+
throw new FilterException(self::NULL_NOT_ALLOWED_ERROR);
99+
}
100+
101+
return $allowNull === true && $value === null;
102+
}
103+
104+
private static function valueIsNilAndValid(bool $allowNil, string $value = null): bool
105+
{
106+
if ($allowNil === false && $value === self::NIL_UUID) {
107+
throw new FilterException(sprintf(self::NIL_NOT_ALLOWED_ERROR_FORMAT, $value));
108+
}
109+
110+
return $allowNil === true && $value === self::NIL_UUID;
111+
}
112+
113+
private static function validateVersions(array $versions)
114+
{
115+
foreach ($versions as $version) {
116+
if (!in_array($version, self::VALID_UUID_VERSIONS)) {
117+
throw new InvalidArgumentException(sprintf(self::UNSUPPORTED_VERSION_ERROR_FORMAT, $version));
118+
}
119+
}
120+
}
121+
}

tests/Filter/PhoneFilterTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public function filterWithAllowNull()
9292
* @param mixed $value The value to filter.
9393
*
9494
* @test
95-
* @covers ::__invoke
95+
* @covers ::filter
9696
* @dataProvider provideFilterThrowsException
9797
*/
9898
public function filterThrowsException($value)

tests/Filter/UuidFilterTest.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
namespace Filter;
4+
5+
use InvalidArgumentException;
6+
use TraderInteractive\Exceptions\FilterException;
7+
use TraderInteractive\Filter\UuidFilter;
8+
use PHPUnit\Framework\TestCase;
9+
10+
/**
11+
* @coversDefaultClass \TraderInteractive\Filter\UuidFilter
12+
* @covers ::<private>
13+
*/
14+
final class UuidFilterTest extends TestCase
15+
{
16+
/**
17+
* @var string
18+
* @internal
19+
*/
20+
const UUID_V1 = '1a42403c-a29d-11ef-b864-0242ac120002';
21+
22+
/**
23+
* @var string
24+
* @internal
25+
*/
26+
const UUID_V4 = 'cc468b36-0b9d-4c93-b8e9-d5e949331ffb';
27+
28+
/**
29+
* @var string
30+
* @internal
31+
*/
32+
const UUID_V7 = '01932b4a-af2b-7093-af59-2fb2044d13d8';
33+
34+
/**
35+
* @test
36+
* @covers ::filter
37+
*/
38+
public function filterUuidV1()
39+
{
40+
$this->assertSame(self::UUID_V1, UuidFilter::filter(self::UUID_V1));
41+
}
42+
43+
/**
44+
* @test
45+
* @covers ::filter
46+
*/
47+
public function filterUuidV4()
48+
{
49+
$this->assertSame(self::UUID_V4, UuidFilter::filter(self::UUID_V4));
50+
}
51+
52+
/**
53+
* @test
54+
* @covers ::filter
55+
*/
56+
public function filterUuidV7()
57+
{
58+
$this->assertSame(self::UUID_V7, UuidFilter::filter(self::UUID_V7));
59+
}
60+
61+
/**
62+
* @test
63+
* @covers ::filter
64+
*/
65+
public function filterNullAllowedNullIsTrue()
66+
{
67+
$this->assertNull(UuidFilter::filter(null, true));
68+
}
69+
70+
/**
71+
* @test
72+
* @covers ::filter
73+
*/
74+
public function filterNullAllowedNullIsFalse()
75+
{
76+
$this->expectException(FilterException::class);
77+
UuidFilter::filter(null, false);
78+
}
79+
80+
/**
81+
* @test
82+
* @covers ::filter
83+
*/
84+
public function filterWithInvalidVersionSpecified()
85+
{
86+
$this->expectException(InvalidArgumentException::class);
87+
$this->expectExceptionMessage(sprintf(UuidFilter::UNSUPPORTED_VERSION_ERROR_FORMAT, 0));
88+
UuidFilter::filter(self::UUID_V7, false, false, [0]);
89+
}
90+
91+
/**
92+
* @test
93+
* @covers ::filter
94+
*/
95+
public function filterValueDoesNotMatchGivenVersions()
96+
{
97+
$this->expectException(FilterException::class);
98+
$this->expectExceptionMessage(
99+
sprintf(
100+
UuidFilter::FILTER_ERROR_FORMAT,
101+
self::UUID_V4,
102+
implode(', ', [1,7])
103+
)
104+
);
105+
UuidFilter::filter(self::UUID_V4, false, false, [1,7]);
106+
}
107+
108+
/**
109+
* @test
110+
* @covers ::filter
111+
*/
112+
public function filterNilUuid()
113+
{
114+
$value = UuidFilter::NIL_UUID;
115+
$this->assertSame($value, UuidFilter::filter($value, false, true));
116+
}
117+
118+
/**
119+
* @test
120+
* @covers ::filter
121+
*/
122+
public function filterNilUuidNilNotAllowed()
123+
{
124+
$value = UuidFilter::NIL_UUID;
125+
$this->expectException(FilterException::class);
126+
$this->expectExceptionMessage(sprintf(UuidFilter::NIL_NOT_ALLOWED_ERROR_FORMAT, $value));
127+
UuidFilter::filter($value, false, false);
128+
}
129+
}

0 commit comments

Comments
 (0)