Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 18d08ff

Browse filesBrowse files
committed
feature #58542 [Validator] Add Slug constraint (raffaelecarelle)
This PR was squashed before being merged into the 7.3 branch. Discussion ---------- [Validator] Add `Slug` constraint | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | | License | MIT Introduce Slug constraint class for validating strings as slugs. Add unit tests to verify correct behavior for valid and invalid slugs. Commits ------- 6233382 [Validator] Add `Slug` constraint
2 parents 8c069cd + 6233382 commit 18d08ff
Copy full SHA for 18d08ff

File tree

5 files changed

+242
-0
lines changed
Filter options

5 files changed

+242
-0
lines changed

‎src/Symfony/Component/Validator/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add support for ratio checks for SVG files to the `Image` constraint
8+
* Add the `Slug` constraint
89

910
7.2
1011
---
+41Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
16+
/**
17+
* Validates that a value is a valid slug.
18+
*
19+
* @author Raffaele Carelle <raffaele.carelle@gmail.com>
20+
*/
21+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
22+
class Slug extends Constraint
23+
{
24+
public const NOT_SLUG_ERROR = '14e6df1e-c8ab-4395-b6ce-04b132a3765e';
25+
26+
public string $message = 'This value is not a valid slug.';
27+
public string $regex = '/^[a-z0-9]+(?:-[a-z0-9]+)*$/';
28+
29+
public function __construct(
30+
?array $options = null,
31+
?string $regex = null,
32+
?string $message = null,
33+
?array $groups = null,
34+
mixed $payload = null,
35+
) {
36+
parent::__construct($options, $groups, $payload);
37+
38+
$this->message = $message ?? $this->message;
39+
$this->regex = $regex ?? $this->regex;
40+
}
41+
}
+47Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
18+
19+
/**
20+
* @author Raffaele Carelle <raffaele.carelle@gmail.com>
21+
*/
22+
class SlugValidator extends ConstraintValidator
23+
{
24+
public function validate(mixed $value, Constraint $constraint): void
25+
{
26+
if (!$constraint instanceof Slug) {
27+
throw new UnexpectedTypeException($constraint, Slug::class);
28+
}
29+
30+
if (null === $value || '' === $value) {
31+
return;
32+
}
33+
34+
if (!\is_scalar($value) && !$value instanceof \Stringable) {
35+
throw new UnexpectedValueException($value, 'string');
36+
}
37+
38+
$value = (string) $value;
39+
40+
if (0 === preg_match($constraint->regex, $value)) {
41+
$this->context->buildViolation($constraint->message)
42+
->setParameter('{{ value }}', $this->formatValue($value))
43+
->setCode(Slug::NOT_SLUG_ERROR)
44+
->addViolation();
45+
}
46+
}
47+
}
+47Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\Constraints\Slug;
16+
use Symfony\Component\Validator\Mapping\ClassMetadata;
17+
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader;
18+
19+
class SlugTest extends TestCase
20+
{
21+
public function testAttributes()
22+
{
23+
$metadata = new ClassMetadata(SlugDummy::class);
24+
$loader = new AttributeLoader();
25+
self::assertTrue($loader->loadClassMetadata($metadata));
26+
27+
[$bConstraint] = $metadata->properties['b']->getConstraints();
28+
self::assertSame('myMessage', $bConstraint->message);
29+
self::assertSame(['Default', 'SlugDummy'], $bConstraint->groups);
30+
31+
[$cConstraint] = $metadata->properties['c']->getConstraints();
32+
self::assertSame(['my_group'], $cConstraint->groups);
33+
self::assertSame('some attached data', $cConstraint->payload);
34+
}
35+
}
36+
37+
class SlugDummy
38+
{
39+
#[Slug]
40+
private $a;
41+
42+
#[Slug(message: 'myMessage')]
43+
private $b;
44+
45+
#[Slug(groups: ['my_group'], payload: 'some attached data')]
46+
private $c;
47+
}
+106Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use Symfony\Component\Validator\Constraints\Slug;
15+
use Symfony\Component\Validator\Constraints\SlugValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
17+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
18+
19+
class SlugValidatorTest extends ConstraintValidatorTestCase
20+
{
21+
protected function createValidator(): SlugValidator
22+
{
23+
return new SlugValidator();
24+
}
25+
26+
public function testNullIsValid()
27+
{
28+
$this->validator->validate(null, new Slug());
29+
30+
$this->assertNoViolation();
31+
}
32+
33+
public function testEmptyStringIsValid()
34+
{
35+
$this->validator->validate('', new Slug());
36+
37+
$this->assertNoViolation();
38+
}
39+
40+
public function testExpectsStringCompatibleType()
41+
{
42+
$this->expectException(UnexpectedValueException::class);
43+
$this->validator->validate(new \stdClass(), new Slug());
44+
}
45+
46+
/**
47+
* @testWith ["test-slug"]
48+
* ["slug-123-test"]
49+
* ["slug"]
50+
*/
51+
public function testValidSlugs($slug)
52+
{
53+
$this->validator->validate($slug, new Slug());
54+
55+
$this->assertNoViolation();
56+
}
57+
58+
/**
59+
* @testWith ["NotASlug"]
60+
* ["Not a slug"]
61+
* ["not-á-slug"]
62+
* ["not-@-slug"]
63+
*/
64+
public function testInvalidSlugs($slug)
65+
{
66+
$constraint = new Slug([
67+
'message' => 'myMessage',
68+
]);
69+
70+
$this->validator->validate($slug, $constraint);
71+
72+
$this->buildViolation('myMessage')
73+
->setParameter('{{ value }}', '"'.$slug.'"')
74+
->setCode(Slug::NOT_SLUG_ERROR)
75+
->assertRaised();
76+
}
77+
78+
/**
79+
* @testWith ["test-slug", true]
80+
* ["slug-123-test", true]
81+
*/
82+
public function testCustomRegexInvalidSlugs($slug)
83+
{
84+
$constraint = new Slug(regex: '/^[a-z0-9]+$/i');
85+
86+
$this->validator->validate($slug, $constraint);
87+
88+
$this->buildViolation($constraint->message)
89+
->setParameter('{{ value }}', '"'.$slug.'"')
90+
->setCode(Slug::NOT_SLUG_ERROR)
91+
->assertRaised();
92+
}
93+
94+
/**
95+
* @testWith ["slug"]
96+
* @testWith ["test1234"]
97+
*/
98+
public function testCustomRegexValidSlugs($slug)
99+
{
100+
$constraint = new Slug(regex: '/^[a-z0-9]+$/i');
101+
102+
$this->validator->validate($slug, $constraint);
103+
104+
$this->assertNoViolation();
105+
}
106+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.