Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a8f450d

Browse files
committed
Add the twig constraint and its validator
1 parent 95d1191 commit a8f450d

File tree

5 files changed

+242
-0
lines changed

5 files changed

+242
-0
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CHANGELOG
1313
* Add the `Week` constraint
1414
* Add `CompoundConstraintTestCase` to ease testing Compound Constraints
1515
* Add context variable to `WhenValidator`
16+
* Add the `twig` constraint for validating Twig content
1617

1718
7.1
1819
---
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Attribute\HasNamedArguments;
15+
use Symfony\Component\Validator\Constraint;
16+
use Symfony\Component\Validator\Exception\LogicException;
17+
use Twig\Environment;
18+
19+
/**
20+
* @author Mokhtar Tlili <[email protected]>
21+
*/
22+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
23+
class Twig extends Constraint
24+
{
25+
public const INVALID_TWIG_ERROR = 'e7fc55d5-e586-4cc1-924e-d27ee7fcd1b5';
26+
27+
protected const ERROR_NAMES = [
28+
self::INVALID_TWIG_ERROR => 'INVALID_TWIG_ERROR',
29+
];
30+
31+
#[HasNamedArguments]
32+
public function __construct(
33+
public string $message = 'This value is not valid Twig.',
34+
?array $groups = null,
35+
mixed $payload = null,
36+
) {
37+
if (!class_exists(Environment::class)) {
38+
throw new LogicException('The twig/twig library is required to use the Twig constraint. Try running "composer require twig/twig".');
39+
}
40+
41+
parent::__construct(null, $groups, $payload);
42+
}
43+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
use Twig\Environment;
19+
use Twig\Error\Error;
20+
use Twig\Loader\ArrayLoader;
21+
use Twig\Source;
22+
23+
/**
24+
* @author Mokhtar Tlili <[email protected]>
25+
*/
26+
class TwigValidator extends ConstraintValidator
27+
{
28+
public function validate(mixed $value, Constraint $constraint): void
29+
{
30+
if (!$constraint instanceof Twig) {
31+
throw new UnexpectedTypeException($constraint, Twig::class);
32+
}
33+
34+
if (null === $value || '' === $value) {
35+
return;
36+
}
37+
38+
if (!\is_scalar($value) && !$value instanceof \Stringable) {
39+
throw new UnexpectedValueException($value, 'string');
40+
}
41+
42+
$value = (string) $value;
43+
44+
$prevErrorHandler = set_error_handler(static function ($level, $message, $file, $line) use (&$prevErrorHandler) {
45+
if (\E_USER_DEPRECATED === $level) {
46+
$templateLine = 0;
47+
if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) {
48+
$templateLine = $matches[1];
49+
}
50+
51+
throw new Error($message, $templateLine);
52+
}
53+
54+
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
55+
});
56+
57+
try {
58+
$twig = new Environment(new ArrayLoader([$value]));
59+
$twig->parse($twig->tokenize(new Source($value, '')));
60+
} catch (Error $e) {
61+
$this->context->buildViolation($constraint->message)
62+
->setParameter('{{ error }}', $e->getMessage())
63+
->setParameter('{{ line }}', $e->getTemplateLine())
64+
->setCode(Twig::INVALID_TWIG_ERROR)
65+
->addViolation();
66+
} finally {
67+
restore_error_handler();
68+
}
69+
}
70+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Twig;
16+
use Symfony\Component\Validator\Mapping\ClassMetadata;
17+
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader;
18+
19+
/**
20+
* @author Mokhtar Tlili <[email protected]>
21+
*/
22+
class TwigTest extends TestCase
23+
{
24+
public function testAttributes()
25+
{
26+
$metadata = new ClassMetadata(TwigDummy::class);
27+
$loader = new AttributeLoader();
28+
self::assertTrue($loader->loadClassMetadata($metadata));
29+
30+
[$bConstraint] = $metadata->properties['b']->getConstraints();
31+
self::assertSame('myMessage', $bConstraint->message);
32+
self::assertSame(['Default', 'TwigDummy'], $bConstraint->groups);
33+
34+
[$cConstraint] = $metadata->properties['c']->getConstraints();
35+
self::assertSame(['my_group'], $cConstraint->groups);
36+
self::assertSame('some attached data', $cConstraint->payload);
37+
}
38+
}
39+
40+
class TwigDummy
41+
{
42+
#[Twig]
43+
private $a;
44+
45+
#[Twig(message: 'myMessage')]
46+
private $b;
47+
48+
#[Twig(groups: ['my_group'], payload: 'some attached data')]
49+
private $c;
50+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Twig;
15+
use Symfony\Component\Validator\Constraints\TwigValidator;
16+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
17+
18+
/**
19+
* @author Mokhtar Tlili <[email protected]>
20+
*/
21+
class TwigValidatorTest extends ConstraintValidatorTestCase
22+
{
23+
protected function createValidator(): TwigValidator
24+
{
25+
return new TwigValidator();
26+
}
27+
28+
/**
29+
* @dataProvider getValidValues
30+
*/
31+
public function testTwigIsValid($value)
32+
{
33+
$this->validator->validate($value, new Twig());
34+
35+
$this->assertNoViolation();
36+
}
37+
38+
/**
39+
* @dataProvider getInvalidValues
40+
*/
41+
public function testInvalidValues($value, $message, $line)
42+
{
43+
$constraint = new Twig('myMessageTest');
44+
45+
$this->validator->validate($value, $constraint);
46+
47+
$this->buildViolation('myMessageTest')
48+
->setParameter('{{ error }}', $message)
49+
->setParameter('{{ line }}', $line)
50+
->setCode(Twig::INVALID_TWIG_ERROR)
51+
->assertRaised();
52+
}
53+
54+
public static function getValidValues()
55+
{
56+
return [
57+
['Hello {{ name }}'],
58+
['{% if condition %}Yes{% else %}No{% endif %}'],
59+
['{# Comment #}'],
60+
['Hello {{ "world" | upper }}'],
61+
['{% for i in 1..3 %}Item {{ i }}{% endfor %}'],
62+
];
63+
}
64+
65+
public static function getInvalidValues()
66+
{
67+
return [
68+
// Invalid syntax example (missing end tag)
69+
['{% if condition %}Oops', 'Unexpected end of template at line 1.', 1],
70+
// Another syntax error example (unclosed variable)
71+
['Hello {{ name', 'Unexpected token "end of template" ("end of print statement" expected) at line 1.', 1],
72+
// Unknown filter error
73+
['Hello {{ name | unknown_filter }}', 'Unknown "unknown_filter" filter at line 1.', 1],
74+
// Invalid variable syntax
75+
['Hello {{ .name }}', 'Unexpected token "punctuation" of value "." at line 1.', 1],
76+
];
77+
}
78+
}

0 commit comments

Comments
 (0)