├── phpstan.neon.dist
├── .gitignore
├── src
├── Constraint
│ ├── Constraints
│ │ ├── Url.php
│ │ ├── Email.php
│ │ ├── Integer.php
│ │ ├── Number.php
│ │ ├── Required.php
│ │ ├── Max.php
│ │ ├── Min.php
│ │ ├── MinLength.php
│ │ ├── Pattern.php
│ │ ├── LessThan.php
│ │ ├── GreaterThan.php
│ │ ├── Type.php
│ │ ├── LessThanOrEqual.php
│ │ ├── GreaterThanOrEqual.php
│ │ ├── Length.php
│ │ ├── MaxLength.php
│ │ └── Range.php
│ ├── Factory
│ │ ├── TranslatableFactoryInterface.php
│ │ ├── FactoryInterface.php
│ │ ├── UrlFactory.php
│ │ ├── EmailFactory.php
│ │ ├── RequiredFactory.php
│ │ ├── LessThanFactory.php
│ │ ├── NumberFactory.php
│ │ ├── FactoryRegistry.php
│ │ ├── GreaterThanFactory.php
│ │ ├── LessThanOrEqualFactory.php
│ │ ├── GreaterThanOrEqualFactory.php
│ │ ├── DateFactory.php
│ │ ├── TimeFactory.php
│ │ ├── IntegerFactory.php
│ │ ├── DateTimeFactory.php
│ │ ├── MaxLengthFactory.php
│ │ ├── MinLengthFactory.php
│ │ ├── FactoryTrait.php
│ │ ├── RangeFactory.php
│ │ └── LengthFactory.php
│ ├── Reader
│ │ ├── ReaderInterface.php
│ │ ├── FormTypeReader.php
│ │ ├── ReaderRegistry.php
│ │ └── DataClassReader.php
│ └── Constraint.php
├── Exception
│ └── ConstraintException.php
├── JBen87ParsleyBundle.php
├── DependencyInjection
│ ├── Configuration.php
│ └── JBen87ParsleyExtension.php
└── Form
│ └── Extension
│ └── ParsleyTypeExtension.php
├── tests
├── Constraint
│ ├── Constraints
│ │ ├── ConfiguredConstraintTestCase.php
│ │ ├── UrlTest.php
│ │ ├── EmailTest.php
│ │ ├── NumberTest.php
│ │ ├── IntegerTest.php
│ │ ├── RequiredTest.php
│ │ ├── ConstraintTestCase.php
│ │ ├── TypeTest.php
│ │ ├── MaxTest.php
│ │ ├── MinTest.php
│ │ ├── MinLengthTest.php
│ │ ├── PatternTest.php
│ │ ├── MaxLengthTest.php
│ │ ├── LengthTest.php
│ │ ├── RangeTest.php
│ │ ├── LessThanTest.php
│ │ ├── GreaterThanTest.php
│ │ ├── LessThanOrEqualTest.php
│ │ └── GreaterThanOrEqualTest.php
│ ├── Reader
│ │ ├── ReaderRegistryTest.php
│ │ ├── FormTypeReaderTest.php
│ │ └── DataClassReaderTest.php
│ └── Factory
│ │ ├── FactoryRegistryTest.php
│ │ ├── UrlFactoryTest.php
│ │ ├── EmailFactoryTest.php
│ │ ├── RequiredFactoryTest.php
│ │ ├── NumberFactoryTest.php
│ │ ├── IntegerFactoryTest.php
│ │ ├── DateFactoryTest.php
│ │ ├── TimeFactoryTest.php
│ │ ├── DateTimeFactoryTest.php
│ │ ├── LessThanFactoryTest.php
│ │ ├── GreaterThanFactoryTest.php
│ │ ├── LessThanOrEqualFactoryTest.php
│ │ ├── GreaterThanOrEqualFactoryTest.php
│ │ ├── MaxLengthFactoryTest.php
│ │ ├── MinLengthFactoryTest.php
│ │ ├── RangeFactoryTest.php
│ │ ├── FactoryTestCase.php
│ │ └── LengthFactoryTest.php
└── Form
│ └── Extension
│ └── ParsleyTypeExtensionTest.php
├── phpcs.xml.dist
├── translations
└── validators.en.xliff
├── phpunit.xml.dist
├── LICENSE
├── config
└── services.yaml
├── composer.json
├── .github
└── workflows
│ └── tests.yml
└── README.md
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 1
3 | paths:
4 | - 'src'
5 | - 'tests'
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /docker/
2 | /build/
3 | /vendor/
4 | /.phpcs-cache
5 | /.phpunit.result.cache
6 | /composer.lock
7 | /docker-compose.yml
8 | /Makefile
9 | /phpunit.xml
10 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/Url.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | src/
14 | tests/
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Exception/ConstraintException.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | This value should have {{ min }} to {{ max }} characters.
7 | This value should have {{ min }} to {{ max }} characters.
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/FactoryInterface.php:
--------------------------------------------------------------------------------
1 | assertSame([
16 | 'data-parsley-type' => 'url',
17 | 'data-parsley-type-message' => 'Invalid.',
18 | ], $constraint->normalize($this->normalizer));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/EmailTest.php:
--------------------------------------------------------------------------------
1 | assertSame([
16 | 'data-parsley-type' => 'email',
17 | 'data-parsley-type-message' => 'Invalid.',
18 | ], $constraint->normalize($this->normalizer));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/NumberTest.php:
--------------------------------------------------------------------------------
1 | assertSame([
16 | 'data-parsley-type' => 'number',
17 | 'data-parsley-type-message' => 'Invalid.',
18 | ], $constraint->normalize($this->normalizer));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/IntegerTest.php:
--------------------------------------------------------------------------------
1 | assertSame([
16 | 'data-parsley-type' => 'integer',
17 | 'data-parsley-type-message' => 'Invalid.',
18 | ], $constraint->normalize($this->normalizer));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/JBen87ParsleyBundle.php:
--------------------------------------------------------------------------------
1 | assertSame([
16 | 'data-parsley-required' => 'true',
17 | 'data-parsley-required-message' => 'Invalid.',
18 | ], $constraint->normalize($this->normalizer));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Constraint/Reader/FormTypeReader.php:
--------------------------------------------------------------------------------
1 | getConfig();
14 | if (!$config->hasOption('constraints')) {
15 | return [];
16 | }
17 |
18 | return $config->getOption('constraints');
19 | }
20 |
21 | /**
22 | * @codeCoverageIgnore
23 | */
24 | public function getPriority(): int
25 | {
26 | return 0;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/ConstraintTestCase.php:
--------------------------------------------------------------------------------
1 | normalizer = $this->createMock(NormalizerInterface::class);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/TypeTest.php:
--------------------------------------------------------------------------------
1 | expectException(ConstraintException::class);
23 | $constraint->normalize($this->normalizer);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Constraint/Reader/ReaderRegistry.php:
--------------------------------------------------------------------------------
1 | readers[] = $reader;
21 | }
22 |
23 | usort($this->readers, function (ReaderInterface $left, ReaderInterface $right) {
24 | return $left->getPriority() <=> $right->getPriority();
25 | });
26 | }
27 |
28 | public function all(): array
29 | {
30 | return $this->readers;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | tests
17 |
18 |
19 |
20 |
21 |
22 | src
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/UrlFactory.php:
--------------------------------------------------------------------------------
1 | $this->trans($constraint->message),
22 | ]);
23 | }
24 |
25 | public function supports(SymfonyConstraint $constraint): bool
26 | {
27 | return $constraint instanceof Assert\Url;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/EmailFactory.php:
--------------------------------------------------------------------------------
1 | $this->trans($constraint->message),
22 | ]);
23 | }
24 |
25 | public function supports(SymfonyConstraint $constraint): bool
26 | {
27 | return $constraint instanceof Assert\Email;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/RequiredFactory.php:
--------------------------------------------------------------------------------
1 | $this->trans($constraint->message),
22 | ]);
23 | }
24 |
25 | public function supports(SymfonyConstraint $constraint): bool
26 | {
27 | return $constraint instanceof Assert\NotBlank;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/Max.php:
--------------------------------------------------------------------------------
1 | max = $options['max'];
19 | }
20 |
21 | protected function getAttribute(): string
22 | {
23 | return 'data-parsley-max';
24 | }
25 |
26 | protected function getValue(): string
27 | {
28 | return (string) $this->max;
29 | }
30 |
31 | protected function configureOptions(OptionsResolver $resolver): void
32 | {
33 | $resolver
34 | ->setRequired(['max'])
35 | ->setAllowedTypes('max', ['int'])
36 | ;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/Min.php:
--------------------------------------------------------------------------------
1 | min = $options['min'];
19 | }
20 |
21 | protected function getAttribute(): string
22 | {
23 | return 'data-parsley-min';
24 | }
25 |
26 | protected function getValue(): string
27 | {
28 | return (string) $this->min;
29 | }
30 |
31 | protected function configureOptions(OptionsResolver $resolver): void
32 | {
33 | $resolver
34 | ->setRequired(['min'])
35 | ->setAllowedTypes('min', ['int'])
36 | ;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/MinLength.php:
--------------------------------------------------------------------------------
1 | min = $options['min'];
19 | }
20 |
21 | protected function getAttribute(): string
22 | {
23 | return 'data-parsley-minlength';
24 | }
25 |
26 | protected function getValue(): string
27 | {
28 | return (string) $this->min;
29 | }
30 |
31 | protected function configureOptions(OptionsResolver $resolver): void
32 | {
33 | $resolver
34 | ->setRequired(['min'])
35 | ->setAllowedTypes('min', ['int'])
36 | ;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/Pattern.php:
--------------------------------------------------------------------------------
1 | pattern = $options['pattern'];
19 | }
20 |
21 | protected function getAttribute(): string
22 | {
23 | return 'data-parsley-pattern';
24 | }
25 |
26 | protected function getValue(): string
27 | {
28 | return $this->pattern;
29 | }
30 |
31 | protected function configureOptions(OptionsResolver $resolver): void
32 | {
33 | $resolver
34 | ->setRequired(['pattern'])
35 | ->setAllowedTypes('pattern', ['string'])
36 | ;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/LessThanFactory.php:
--------------------------------------------------------------------------------
1 | $constraint->value,
22 | 'message' => $this->trans($constraint->message, ['{{ compared_value }}' => $constraint->value]),
23 | ]);
24 | }
25 |
26 | public function supports(SymfonyConstraint $constraint): bool
27 | {
28 | return $constraint instanceof Assert\LessThan;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/LessThan.php:
--------------------------------------------------------------------------------
1 | value = $options['value'];
22 | }
23 |
24 | protected function getAttribute(): string
25 | {
26 | return 'data-parsley-lt';
27 | }
28 |
29 | protected function getValue(): string
30 | {
31 | return (string) $this->value;
32 | }
33 |
34 | protected function configureOptions(OptionsResolver $resolver): void
35 | {
36 | $resolver
37 | ->setRequired(['value'])
38 | ->setAllowedTypes('value', ['numeric'])
39 | ;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/NumberFactory.php:
--------------------------------------------------------------------------------
1 | $this->trans($constraint->message, ['{{ type }}' => self::TYPE]),
24 | ]);
25 | }
26 |
27 | public function supports(SymfonyConstraint $constraint): bool
28 | {
29 | return $constraint instanceof Assert\Type && self::TYPE === $constraint->type;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/GreaterThan.php:
--------------------------------------------------------------------------------
1 | value = $options['value'];
22 | }
23 |
24 | protected function getAttribute(): string
25 | {
26 | return 'data-parsley-gt';
27 | }
28 |
29 | protected function getValue(): string
30 | {
31 | return (string) $this->value;
32 | }
33 |
34 | protected function configureOptions(OptionsResolver $resolver): void
35 | {
36 | $resolver
37 | ->setRequired(['value'])
38 | ->setAllowedTypes('value', ['numeric'])
39 | ;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/Type.php:
--------------------------------------------------------------------------------
1 | getType(), self::TYPES, true)) {
27 | throw ConstraintException::createInvalidValueException();
28 | }
29 |
30 | return $this->getType();
31 | }
32 |
33 | abstract protected function getType(): string;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/FactoryRegistry.php:
--------------------------------------------------------------------------------
1 | factories = $factories;
23 | }
24 |
25 | /**
26 | * @throws ConstraintException
27 | */
28 | public function findForConstraint(SymfonyConstraint $constraint): FactoryInterface
29 | {
30 | foreach ($this->factories as $factory) {
31 | if ($factory->supports($constraint)) {
32 | return $factory;
33 | }
34 | }
35 |
36 | throw ConstraintException::createUnsupportedException();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/GreaterThanFactory.php:
--------------------------------------------------------------------------------
1 | $constraint->value,
22 | 'message' => $this->trans($constraint->message, ['{{ compared_value }}' => $constraint->value]),
23 | ]);
24 | }
25 |
26 | public function supports(SymfonyConstraint $constraint): bool
27 | {
28 | return $constraint instanceof Assert\GreaterThan;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/LessThanOrEqual.php:
--------------------------------------------------------------------------------
1 | value = $options['value'];
22 | }
23 |
24 | protected function getAttribute(): string
25 | {
26 | return 'data-parsley-lte';
27 | }
28 |
29 | protected function getValue(): string
30 | {
31 | return (string) $this->value;
32 | }
33 |
34 | protected function configureOptions(OptionsResolver $resolver): void
35 | {
36 | $resolver
37 | ->setRequired(['value'])
38 | ->setAllowedTypes('value', ['numeric'])
39 | ;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/GreaterThanOrEqual.php:
--------------------------------------------------------------------------------
1 | value = $options['value'];
22 | }
23 |
24 | protected function getAttribute(): string
25 | {
26 | return 'data-parsley-gte';
27 | }
28 |
29 | protected function getValue(): string
30 | {
31 | return (string) $this->value;
32 | }
33 |
34 | protected function configureOptions(OptionsResolver $resolver): void
35 | {
36 | $resolver
37 | ->setRequired(['value'])
38 | ->setAllowedTypes('value', ['numeric'])
39 | ;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/LessThanOrEqualFactory.php:
--------------------------------------------------------------------------------
1 | $constraint->value,
22 | 'message' => $this->trans($constraint->message, ['{{ compared_value }}' => $constraint->value]),
23 | ]);
24 | }
25 |
26 | public function supports(SymfonyConstraint $constraint): bool
27 | {
28 | return $constraint instanceof Assert\LessThanOrEqual;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/GreaterThanOrEqualFactory.php:
--------------------------------------------------------------------------------
1 | $constraint->value,
22 | 'message' => $this->trans($constraint->message, ['{{ compared_value }}' => $constraint->value]),
23 | ]);
24 | }
25 |
26 | public function supports(SymfonyConstraint $constraint): bool
27 | {
28 | return $constraint instanceof Assert\GreaterThanOrEqual;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/Length.php:
--------------------------------------------------------------------------------
1 | min = $options['min'];
20 | $this->max = $options['max'];
21 | }
22 |
23 | protected function getAttribute(): string
24 | {
25 | return 'data-parsley-length';
26 | }
27 |
28 | protected function getValue(): string
29 | {
30 | return "[$this->min, $this->max]";
31 | }
32 |
33 | protected function configureOptions(OptionsResolver $resolver): void
34 | {
35 | $resolver
36 | ->setRequired(['min', 'max'])
37 | ->setAllowedTypes('min', ['int'])
38 | ->setAllowedTypes('max', ['int'])
39 | ;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/DateFactory.php:
--------------------------------------------------------------------------------
1 | pattern = $datePattern;
21 | }
22 |
23 | public function create(SymfonyConstraint $constraint): Constraint
24 | {
25 | /** @var Assert\Date $constraint */
26 |
27 | return new ParsleyAssert\Pattern([
28 | 'pattern' => $this->pattern,
29 | 'message' => $this->trans($constraint->message),
30 | ]);
31 | }
32 |
33 | public function supports(SymfonyConstraint $constraint): bool
34 | {
35 | return $constraint instanceof Assert\Date;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/TimeFactory.php:
--------------------------------------------------------------------------------
1 | pattern = $timePattern;
21 | }
22 |
23 | public function create(SymfonyConstraint $constraint): Constraint
24 | {
25 | /** @var Assert\Time $constraint */
26 |
27 | return new ParsleyAssert\Pattern([
28 | 'pattern' => $this->pattern,
29 | 'message' => $this->trans($constraint->message),
30 | ]);
31 | }
32 |
33 | public function supports(SymfonyConstraint $constraint): bool
34 | {
35 | return $constraint instanceof Assert\Time;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Benoit Jouhaud
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/IntegerFactory.php:
--------------------------------------------------------------------------------
1 | $this->trans($constraint->message, ['{{ type }}' => self::TYPE]),
25 | ]);
26 | }
27 |
28 | public function supports(SymfonyConstraint $constraint): bool
29 | {
30 | return $constraint instanceof Assert\Type
31 | && (self::TYPE === $constraint->type || self::TYPE_ALT === $constraint->type)
32 | ;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/DateTimeFactory.php:
--------------------------------------------------------------------------------
1 | pattern = $dateTimePattern;
21 | }
22 |
23 | public function create(SymfonyConstraint $constraint): Constraint
24 | {
25 | /** @var Assert\DateTime $constraint */
26 |
27 | return new ParsleyAssert\Pattern([
28 | 'pattern' => $this->pattern,
29 | 'message' => $this->trans($constraint->message),
30 | ]);
31 | }
32 |
33 | public function supports(SymfonyConstraint $constraint): bool
34 | {
35 | return $constraint instanceof Assert\DateTime;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/MaxLengthFactory.php:
--------------------------------------------------------------------------------
1 | $constraint->max,
22 | 'message' => $this->transChoice(
23 | $constraint->maxMessage,
24 | (int) $constraint->max,
25 | ['{{ limit }}' => $constraint->max]
26 | ),
27 | ]);
28 | }
29 |
30 | public function supports(SymfonyConstraint $constraint): bool
31 | {
32 | return $constraint instanceof Assert\Length && null === $constraint->min && null !== $constraint->max;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/MinLengthFactory.php:
--------------------------------------------------------------------------------
1 | $constraint->min,
22 | 'message' => $this->transChoice(
23 | $constraint->minMessage,
24 | (int) $constraint->min,
25 | ['{{ limit }}' => $constraint->min]
26 | ),
27 | ]);
28 | }
29 |
30 | public function supports(SymfonyConstraint $constraint): bool
31 | {
32 | return $constraint instanceof Assert\Length && null !== $constraint->min && null === $constraint->max;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/FactoryTrait.php:
--------------------------------------------------------------------------------
1 | translator = $translator;
21 | }
22 |
23 | private function trans(
24 | string $id,
25 | array $parameters = [],
26 | string $domain = 'validators',
27 | string $locale = null
28 | ): string {
29 | return $this->translator->trans($id, $parameters, $domain, $locale);
30 | }
31 |
32 | private function transChoice(
33 | string $id,
34 | int $number,
35 | array $parameters = [],
36 | string $domain = 'validators',
37 | string $locale = null
38 | ): string {
39 | $parameters['%count%'] = $number;
40 |
41 | return $this->translator->trans($id, $parameters, $domain, $locale);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/MaxTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\Max();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\Max([
25 | 'max' => '10',
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | $constraint = new ParsleyAssert\Max([
32 | 'max' => 10,
33 | ]);
34 |
35 | $this->assertSame([
36 | 'data-parsley-max' => '10',
37 | 'data-parsley-max-message' => 'Invalid.',
38 | ], $constraint->normalize($this->normalizer));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/MinTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\Min();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\Min([
25 | 'min' => '5',
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | $constraint = new ParsleyAssert\Min([
32 | 'min' => 5,
33 | ]);
34 |
35 | $this->assertSame([
36 | 'data-parsley-min' => '5',
37 | 'data-parsley-min-message' => 'Invalid.',
38 | ], $constraint->normalize($this->normalizer));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/MinLengthTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\MinLength();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\MinLength([
25 | 'min' => '5',
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | $constraint = new ParsleyAssert\MinLength([
32 | 'min' => 5,
33 | ]);
34 |
35 | $this->assertSame([
36 | 'data-parsley-minlength' => '5',
37 | 'data-parsley-minlength-message' => 'Invalid.',
38 | ], $constraint->normalize($this->normalizer));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/PatternTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\Pattern();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\Pattern([
25 | 'pattern' => false,
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | $constraint = new ParsleyAssert\Pattern([
32 | 'pattern' => '\w',
33 | ]);
34 |
35 | $this->assertSame([
36 | 'data-parsley-pattern' => '\w',
37 | 'data-parsley-pattern-message' => 'Invalid.',
38 | ], $constraint->normalize($this->normalizer));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/MaxLength.php:
--------------------------------------------------------------------------------
1 | max = $options['max'];
20 | }
21 |
22 | public function normalize(NormalizerInterface $normalizer, $format = null, array $context = []): array
23 | {
24 | return parent::normalize($normalizer, $format, $context) + ['maxlength' => $this->getValue()];
25 | }
26 |
27 | protected function getAttribute(): string
28 | {
29 | return 'data-parsley-maxlength';
30 | }
31 |
32 | protected function getValue(): string
33 | {
34 | return (string) $this->max;
35 | }
36 |
37 | protected function configureOptions(OptionsResolver $resolver): void
38 | {
39 | $resolver
40 | ->setRequired(['max'])
41 | ->setAllowedTypes('max', ['int'])
42 | ;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/MaxLengthTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\MaxLength();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\MaxLength([
25 | 'max' => '10',
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | $constraint = new ParsleyAssert\MaxLength([
32 | 'max' => 10,
33 | ]);
34 |
35 | $this->assertSame([
36 | 'data-parsley-maxlength' => '10',
37 | 'data-parsley-maxlength-message' => 'Invalid.',
38 | 'maxlength' => '10',
39 | ], $constraint->normalize($this->normalizer));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Constraint/Reader/ReaderRegistryTest.php:
--------------------------------------------------------------------------------
1 | assertSame([$reader2, $reader1], $registry->all());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/LengthTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\Length();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\Length([
25 | 'min' => '5',
26 | 'max' => '10',
27 | ]);
28 | }
29 |
30 | public function testNormalization(): void
31 | {
32 | $constraint = new ParsleyAssert\Length([
33 | 'min' => 5,
34 | 'max' => 10,
35 | ]);
36 |
37 | $this->assertSame([
38 | 'data-parsley-length' => '[5, 10]',
39 | 'data-parsley-length-message' => 'Invalid.',
40 | ], $constraint->normalize($this->normalizer));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/FactoryRegistryTest.php:
--------------------------------------------------------------------------------
1 | createRegistry([
19 | new RequiredFactory(),
20 | ]);
21 |
22 | $factory = $registry->findForConstraint(new Assert\NotBlank());
23 | $this->assertInstanceOf(RequiredFactory::class, $factory);
24 | }
25 |
26 | public function testFindForConstraintUnsupported(): void
27 | {
28 | $this->expectException(ConstraintException::class);
29 | $this->createRegistry([])->findForConstraint(new Assert\NotBlank());
30 | }
31 |
32 | /**
33 | * @param FactoryInterface[] $factories
34 | */
35 | private function createRegistry(array $factories): FactoryRegistry
36 | {
37 | return new FactoryRegistry($factories);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/RangeTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\Range();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\Range([
25 | 'min' => '5',
26 | 'max' => '10',
27 | ]);
28 | }
29 |
30 | public function testNormalization(): void
31 | {
32 | $constraint = new ParsleyAssert\Range([
33 | 'min' => 5,
34 | 'max' => 10,
35 | ]);
36 |
37 | $this->assertSame([
38 | 'data-parsley-min' => '5',
39 | 'data-parsley-min-message' => 'Invalid.',
40 | 'data-parsley-max' => '10',
41 | 'data-parsley-max-message' => 'Invalid.',
42 | ], $constraint->normalize($this->normalizer));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/RangeFactory.php:
--------------------------------------------------------------------------------
1 | min) {
23 | $options += [
24 | 'min' => $constraint->min,
25 | 'minMessage' => $this->trans($constraint->minMessage, ['{{ limit }}' => $constraint->min]),
26 | ];
27 | }
28 |
29 | if (null !== $constraint->max) {
30 | $options += [
31 | 'max' => $constraint->max,
32 | 'maxMessage' => $this->trans($constraint->maxMessage, ['{{ limit }}' => $constraint->max]),
33 | ];
34 | }
35 |
36 | return new ParsleyAssert\Range($options);
37 | }
38 |
39 | public function supports(SymfonyConstraint $constraint): bool
40 | {
41 | return $constraint instanceof Assert\Range;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/config/services.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | _defaults:
3 | autowire: true
4 | autoconfigure: true
5 | public: false
6 |
7 | _instanceof:
8 | JBen87\ParsleyBundle\Constraint\Factory\FactoryInterface:
9 | tags: ['jben87_parsley.factory']
10 | JBen87\ParsleyBundle\Constraint\Reader\ReaderInterface:
11 | tags: ['jben87_parsley.reader']
12 |
13 | JBen87\ParsleyBundle\:
14 | resource: '../src/'
15 | exclude:
16 | - '../src/Constraint/Constraints'
17 | - '../src/DependencyInjection'
18 | - '../src/Exception'
19 |
20 | JBen87\ParsleyBundle\Constraint\Factory\FactoryRegistry:
21 | arguments:
22 | $factories: !tagged_iterator 'jben87_parsley.factory'
23 |
24 | JBen87\ParsleyBundle\Constraint\Factory\DateFactory:
25 | arguments:
26 | $datePattern: '%jben87_parsley.date_pattern%'
27 |
28 | JBen87\ParsleyBundle\Constraint\Factory\DateTimeFactory:
29 | arguments:
30 | $dateTimePattern: '%jben87_parsley.datetime_pattern%'
31 |
32 | JBen87\ParsleyBundle\Constraint\Factory\TimeFactory:
33 | arguments:
34 | $timePattern: '%jben87_parsley.time_pattern%'
35 |
36 | JBen87\ParsleyBundle\Constraint\Reader\ReaderRegistry:
37 | arguments:
38 | $readers: !tagged_iterator 'jben87_parsley.reader'
39 |
40 | JBen87\ParsleyBundle\Form\Extension\ParsleyTypeExtension:
41 | arguments:
42 | $enabled: '%jben87_parsley.enabled%'
43 | $triggerEvent: '%jben87_parsley.trigger_event%'
44 |
--------------------------------------------------------------------------------
/src/Constraint/Factory/LengthFactory.php:
--------------------------------------------------------------------------------
1 | $constraint->min,
22 | 'max' => $constraint->max,
23 | 'message' => $this->trans(
24 | 'This value should have {{ min }} to {{ max }} characters.',
25 | ['{{ min }}' => $constraint->min, '{{ max }}' => $constraint->max]
26 | ),
27 | ];
28 |
29 | if ($constraint->min === $constraint->max) {
30 | $options['message'] = $this->transChoice(
31 | $constraint->exactMessage,
32 | (int) $constraint->min,
33 | ['{{ limit }}' => $constraint->min]
34 | );
35 | }
36 |
37 | return new ParsleyAssert\Length($options);
38 | }
39 |
40 | public function supports(SymfonyConstraint $constraint): bool
41 | {
42 | return $constraint instanceof Assert\Length && null !== $constraint->min && null !== $constraint->max;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/UrlFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
22 | ->expects($this->once())
23 | ->method('trans')
24 | ->with(self::ORIGINAL_MESSAGE)
25 | ->willReturn(self::TRANSLATED_MESSAGE)
26 | ;
27 | }
28 |
29 | protected function getExpectedConstraint(): Constraint
30 | {
31 | return new ParsleyAssert\Url(['message' => self::TRANSLATED_MESSAGE]);
32 | }
33 |
34 | protected function getOriginalConstraint(): SymfonyConstraint
35 | {
36 | return new Assert\Url();
37 | }
38 |
39 | protected function getUnsupportedConstraint(): SymfonyConstraint
40 | {
41 | return new Assert\Valid();
42 | }
43 |
44 | protected function createFactory(): FactoryInterface
45 | {
46 | return new UrlFactory();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | alias = $alias;
17 | }
18 |
19 | public function getConfigTreeBuilder(): TreeBuilder
20 | {
21 | $treeBuilder = new TreeBuilder($this->alias);
22 |
23 | $rootNode = $treeBuilder->getRootNode();
24 | $rootNode
25 | ->children()
26 | ->booleanNode('enabled')
27 | ->defaultTrue()
28 | ->end()
29 | ->scalarNode('trigger_event')
30 | ->defaultValue('blur')
31 | ->end()
32 | ->arrayNode('date_pattern')
33 | ->useAttributeAsKey('locale')
34 | ->scalarPrototype()->end()
35 | ->end()
36 | ->arrayNode('time_pattern')
37 | ->useAttributeAsKey('locale')
38 | ->scalarPrototype()->end()
39 | ->end()
40 | ->arrayNode('datetime_pattern')
41 | ->useAttributeAsKey('locale')
42 | ->scalarPrototype()->end()
43 | ->end()
44 | ->end()
45 | ;
46 |
47 | return $treeBuilder;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/EmailFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
22 | ->expects($this->once())
23 | ->method('trans')
24 | ->with(self::ORIGINAL_MESSAGE)
25 | ->willReturn(self::TRANSLATED_MESSAGE)
26 | ;
27 | }
28 |
29 | protected function getExpectedConstraint(): Constraint
30 | {
31 | return new ParsleyAssert\Email(['message' => self::TRANSLATED_MESSAGE]);
32 | }
33 |
34 | protected function getOriginalConstraint(): SymfonyConstraint
35 | {
36 | return new Assert\Email();
37 | }
38 |
39 | protected function getUnsupportedConstraint(): SymfonyConstraint
40 | {
41 | return new Assert\Valid();
42 | }
43 |
44 | protected function createFactory(): FactoryInterface
45 | {
46 | return new EmailFactory();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/RequiredFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
22 | ->expects($this->once())
23 | ->method('trans')
24 | ->with(self::ORIGINAL_MESSAGE)
25 | ->willReturn(self::TRANSLATED_MESSAGE)
26 | ;
27 | }
28 |
29 | protected function getExpectedConstraint(): Constraint
30 | {
31 | return new ParsleyAssert\Required(['message' => self::TRANSLATED_MESSAGE]);
32 | }
33 |
34 | protected function getOriginalConstraint(): SymfonyConstraint
35 | {
36 | return new Assert\NotBlank();
37 | }
38 |
39 | protected function getUnsupportedConstraint(): SymfonyConstraint
40 | {
41 | return new Assert\Valid();
42 | }
43 |
44 | protected function createFactory(): FactoryInterface
45 | {
46 | return new RequiredFactory();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "j-ben87/parsley-bundle",
3 | "description": "Convert Symfony constraints into data-attributes for client-side validation with Parsley.",
4 | "type": "symfony-bundle",
5 | "keywords": ["symfony", "bundle", "parsley", "validation", "client-side"],
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Benoit Jouhaud",
10 | "email": "bjouhaud@gmail.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=7.4",
15 | "psr/log": "^1.1|^2.0|^3.0",
16 | "symfony/form": "^5.4|^6.0",
17 | "symfony/framework-bundle": "^5.4|^6.0",
18 | "symfony/serializer": "^5.4|^6.0",
19 | "symfony/validator": "^5.4|^6.0"
20 | },
21 | "require-dev": {
22 | "squizlabs/php_codesniffer": "^3.6",
23 | "phpunit/phpunit": "^9.5",
24 | "phpstan/phpstan": "^1.4",
25 | "phpstan/phpstan-phpunit": "^1.0",
26 | "phpstan/phpstan-symfony": "^1.1",
27 | "phpstan/extension-installer": "^1.1"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "JBen87\\ParsleyBundle\\": "src"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "JBen87\\ParsleyBundle\\Tests\\": "tests"
37 | }
38 | },
39 | "minimum-stability": "stable",
40 | "extra": {
41 | "branch-alias": {
42 | "dev-master": "3.x-dev"
43 | }
44 | },
45 | "config": {
46 | "allow-plugins": {
47 | "composer/package-versions-deprecated": true,
48 | "phpstan/extension-installer": true
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Constraint/Constraint.php:
--------------------------------------------------------------------------------
1 | configure($options);
19 |
20 | if (array_key_exists('message', $options)) {
21 | $this->message = $options['message'];
22 | }
23 | }
24 |
25 | public function normalize(NormalizerInterface $normalizer, $format = null, array $context = []): array
26 | {
27 | return [
28 | $this->getAttribute() => $this->getValue(),
29 | sprintf('%s-message', $this->getAttribute()) => $this->message,
30 | ];
31 | }
32 |
33 | abstract protected function getAttribute(): string;
34 |
35 | /**
36 | * @throws ConstraintException
37 | */
38 | abstract protected function getValue(): string;
39 |
40 | protected function configureOptions(OptionsResolver $resolver): void
41 | {
42 | }
43 |
44 | private function configure(array $options): array
45 | {
46 | $resolver = new OptionsResolver();
47 | $resolver->setDefined('message');
48 |
49 | $this->configureOptions($resolver);
50 |
51 | return $resolver->resolve($options);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/NumberFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
22 | ->expects($this->once())
23 | ->method('trans')
24 | ->with(self::ORIGINAL_MESSAGE, ['{{ type }}' => 'numeric'])
25 | ->willReturn(self::TRANSLATED_MESSAGE)
26 | ;
27 | }
28 |
29 | protected function getExpectedConstraint(): Constraint
30 | {
31 | return new ParsleyAssert\Number(['message' => self::TRANSLATED_MESSAGE]);
32 | }
33 |
34 | protected function getOriginalConstraint(): SymfonyConstraint
35 | {
36 | return new Assert\Type(['type' => 'numeric']);
37 | }
38 |
39 | protected function getUnsupportedConstraint(): SymfonyConstraint
40 | {
41 | return new Assert\Valid();
42 | }
43 |
44 | protected function createFactory(): FactoryInterface
45 | {
46 | return new NumberFactory();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/IntegerFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
22 | ->expects($this->once())
23 | ->method('trans')
24 | ->with(self::ORIGINAL_MESSAGE, ['{{ type }}' => 'integer'])
25 | ->willReturn(self::TRANSLATED_MESSAGE)
26 | ;
27 | }
28 |
29 | protected function getExpectedConstraint(): Constraint
30 | {
31 | return new ParsleyAssert\Integer(['message' => self::TRANSLATED_MESSAGE]);
32 | }
33 |
34 | protected function getOriginalConstraint(): SymfonyConstraint
35 | {
36 | return new Assert\Type(['type' => 'integer']);
37 | }
38 |
39 | protected function getUnsupportedConstraint(): SymfonyConstraint
40 | {
41 | return new Assert\Valid();
42 | }
43 |
44 | protected function createFactory(): FactoryInterface
45 | {
46 | return new IntegerFactory();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/DateFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
23 | ->expects($this->once())
24 | ->method('trans')
25 | ->with(self::ORIGINAL_MESSAGE)
26 | ->willReturn(self::TRANSLATED_MESSAGE)
27 | ;
28 | }
29 |
30 | protected function getExpectedConstraint(): Constraint
31 | {
32 | return new ParsleyAssert\Pattern(['pattern' => self::PATTERN, 'message' => self::TRANSLATED_MESSAGE]);
33 | }
34 |
35 | protected function getOriginalConstraint(): SymfonyConstraint
36 | {
37 | return new Assert\Date();
38 | }
39 |
40 | protected function getUnsupportedConstraint(): SymfonyConstraint
41 | {
42 | return new Assert\Valid();
43 | }
44 |
45 | protected function createFactory(): FactoryInterface
46 | {
47 | return new DateFactory(self::PATTERN);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/TimeFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
23 | ->expects($this->once())
24 | ->method('trans')
25 | ->with(self::ORIGINAL_MESSAGE)
26 | ->willReturn(self::TRANSLATED_MESSAGE)
27 | ;
28 | }
29 |
30 | protected function getExpectedConstraint(): Constraint
31 | {
32 | return new ParsleyAssert\Pattern(['pattern' => self::PATTERN, 'message' => self::TRANSLATED_MESSAGE]);
33 | }
34 |
35 | protected function getOriginalConstraint(): SymfonyConstraint
36 | {
37 | return new Assert\Time();
38 | }
39 |
40 | protected function getUnsupportedConstraint(): SymfonyConstraint
41 | {
42 | return new Assert\Valid();
43 | }
44 |
45 | protected function createFactory(): FactoryInterface
46 | {
47 | return new TimeFactory(self::PATTERN);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/DateTimeFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
23 | ->expects($this->once())
24 | ->method('trans')
25 | ->with(self::ORIGINAL_MESSAGE)
26 | ->willReturn(self::TRANSLATED_MESSAGE)
27 | ;
28 | }
29 |
30 | protected function getExpectedConstraint(): Constraint
31 | {
32 | return new ParsleyAssert\Pattern(['pattern' => self::PATTERN, 'message' => self::TRANSLATED_MESSAGE]);
33 | }
34 |
35 | protected function getOriginalConstraint(): SymfonyConstraint
36 | {
37 | return new Assert\DateTime();
38 | }
39 |
40 | protected function getUnsupportedConstraint(): SymfonyConstraint
41 | {
42 | return new Assert\Valid();
43 | }
44 |
45 | protected function createFactory(): FactoryInterface
46 | {
47 | return new DateTimeFactory(self::PATTERN);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/LessThanFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
23 | ->expects($this->once())
24 | ->method('trans')
25 | ->with(self::ORIGINAL_MESSAGE, ['{{ compared_value }}' => self::VALUE])
26 | ->willReturn(self::TRANSLATED_MESSAGE)
27 | ;
28 | }
29 |
30 | protected function getExpectedConstraint(): Constraint
31 | {
32 | return new ParsleyAssert\LessThan(['value' => self::VALUE, 'message' => self::TRANSLATED_MESSAGE]);
33 | }
34 |
35 | protected function getOriginalConstraint(): SymfonyConstraint
36 | {
37 | return new Assert\LessThan(self::VALUE);
38 | }
39 |
40 | protected function getUnsupportedConstraint(): SymfonyConstraint
41 | {
42 | return new Assert\Valid();
43 | }
44 |
45 | protected function createFactory(): FactoryInterface
46 | {
47 | return new LessThanFactory();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Constraint/Reader/DataClassReader.php:
--------------------------------------------------------------------------------
1 | validator = $validator;
19 | }
20 |
21 | public function read(FormInterface $form): array
22 | {
23 | $config = $form->getRoot()->getConfig();
24 | if (null === $config->getDataClass()) {
25 | return [];
26 | }
27 |
28 | try {
29 | $metadata = $this->validator->getMetadataFor($config->getDataClass());
30 | } catch (NoSuchMetadataException $exception) {
31 | return [];
32 | }
33 |
34 | if (!$metadata instanceof ClassMetadata) {
35 | return [];
36 | }
37 |
38 | $constraints = [];
39 | foreach ($metadata->getPropertyMetadata($form->getName()) as $propertyMetadatum) {
40 | $constraints = array_merge($constraints, $propertyMetadatum->findConstraints($metadata->getDefaultGroup()));
41 | }
42 |
43 | return $constraints;
44 | }
45 |
46 | /**
47 | * DataClassReader priority should be greater than FormTypeReader priority
48 | * so that FormType constraints override entity constraints.
49 | *
50 | * @codeCoverageIgnore
51 | */
52 | public function getPriority(): int
53 | {
54 | return 10;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/GreaterThanFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
23 | ->expects($this->once())
24 | ->method('trans')
25 | ->with(self::ORIGINAL_MESSAGE, ['{{ compared_value }}' => self::VALUE])
26 | ->willReturn(self::TRANSLATED_MESSAGE)
27 | ;
28 | }
29 |
30 | protected function getExpectedConstraint(): Constraint
31 | {
32 | return new ParsleyAssert\GreaterThan(['value' => self::VALUE, 'message' => self::TRANSLATED_MESSAGE]);
33 | }
34 |
35 | protected function getOriginalConstraint(): SymfonyConstraint
36 | {
37 | return new Assert\GreaterThan(self::VALUE);
38 | }
39 |
40 | protected function getUnsupportedConstraint(): SymfonyConstraint
41 | {
42 | return new Assert\Valid();
43 | }
44 |
45 | protected function createFactory(): FactoryInterface
46 | {
47 | return new GreaterThanFactory();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/LessThanOrEqualFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
23 | ->expects($this->once())
24 | ->method('trans')
25 | ->with(self::ORIGINAL_MESSAGE, ['{{ compared_value }}' => self::VALUE])
26 | ->willReturn(self::TRANSLATED_MESSAGE)
27 | ;
28 | }
29 |
30 | protected function getExpectedConstraint(): Constraint
31 | {
32 | return new ParsleyAssert\LessThanOrEqual([
33 | 'value' => self::VALUE,
34 | 'message' => self::TRANSLATED_MESSAGE,
35 | ]);
36 | }
37 |
38 | protected function getOriginalConstraint(): SymfonyConstraint
39 | {
40 | return new Assert\LessThanOrEqual(self::VALUE);
41 | }
42 |
43 | protected function getUnsupportedConstraint(): SymfonyConstraint
44 | {
45 | return new Assert\Valid();
46 | }
47 |
48 | protected function createFactory(): FactoryInterface
49 | {
50 | return new LessThanOrEqualFactory();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/GreaterThanOrEqualFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
23 | ->expects($this->once())
24 | ->method('trans')
25 | ->with(self::ORIGINAL_MESSAGE, ['{{ compared_value }}' => self::VALUE])
26 | ->willReturn(self::TRANSLATED_MESSAGE)
27 | ;
28 | }
29 |
30 | protected function getExpectedConstraint(): Constraint
31 | {
32 | return new ParsleyAssert\GreaterThanOrEqual([
33 | 'value' => self::VALUE,
34 | 'message' => self::TRANSLATED_MESSAGE,
35 | ]);
36 | }
37 |
38 | protected function getOriginalConstraint(): SymfonyConstraint
39 | {
40 | return new Assert\GreaterThanOrEqual(self::VALUE);
41 | }
42 |
43 | protected function getUnsupportedConstraint(): SymfonyConstraint
44 | {
45 | return new Assert\Valid();
46 | }
47 |
48 | protected function createFactory(): FactoryInterface
49 | {
50 | return new GreaterThanOrEqualFactory();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/MaxLengthFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
25 | ->expects($this->once())
26 | ->method('trans')
27 | ->with(
28 | self::ORIGINAL_MESSAGE,
29 | ['{{ limit }}' => self::LIMIT, '%count%' => self::LIMIT]
30 | )
31 | ->willReturn(self::TRANSLATED_MESSAGE)
32 | ;
33 | }
34 |
35 | protected function getExpectedConstraint(): Constraint
36 | {
37 | return new ParsleyAssert\MaxLength(['max' => self::LIMIT, 'message' => self::TRANSLATED_MESSAGE]);
38 | }
39 |
40 | protected function getOriginalConstraint(): SymfonyConstraint
41 | {
42 | return new Assert\Length(['max' => self::LIMIT]);
43 | }
44 |
45 | protected function getUnsupportedConstraint(): SymfonyConstraint
46 | {
47 | return new Assert\Valid();
48 | }
49 |
50 | protected function createFactory(): FactoryInterface
51 | {
52 | return new MaxLengthFactory();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/MinLengthFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
27 | ->expects($this->once())
28 | ->method('trans')
29 | ->with(
30 | self::ORIGINAL_MESSAGE,
31 | ['{{ limit }}' => self::LIMIT, '%count%' => self::LIMIT]
32 | )
33 | ->willReturn(self::TRANSLATED_MESSAGE)
34 | ;
35 | }
36 |
37 | protected function getExpectedConstraint(): Constraint
38 | {
39 | return new ParsleyAssert\MinLength(['min' => self::LIMIT, 'message' => self::TRANSLATED_MESSAGE]);
40 | }
41 |
42 | protected function getOriginalConstraint(): SymfonyConstraint
43 | {
44 | return new Assert\Length(['min' => self::LIMIT]);
45 | }
46 |
47 | protected function getUnsupportedConstraint(): SymfonyConstraint
48 | {
49 | return new Assert\Valid();
50 | }
51 |
52 | protected function createFactory(): FactoryInterface
53 | {
54 | return new MinLengthFactory();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/RangeFactoryTest.php:
--------------------------------------------------------------------------------
1 | translator
26 | ->expects($this->exactly(2))
27 | ->method('trans')
28 | ->withConsecutive([self::ORGIGINAL_MIN_MESSAGE], [self::ORGIGINAL_MAX_MESSAGE])
29 | ->willReturnOnConsecutiveCalls(self::TRANSLATED_MIN_MESSAGE, self::TRANSLATED_MAX_MESSAGE)
30 | ;
31 | }
32 |
33 | protected function getExpectedConstraint(): Constraint
34 | {
35 | return new ParsleyAssert\Range([
36 | 'min' => self::MIN,
37 | 'max' => self::MAX,
38 | 'minMessage' => self::TRANSLATED_MIN_MESSAGE,
39 | 'maxMessage' => self::TRANSLATED_MAX_MESSAGE,
40 | ]);
41 | }
42 |
43 | protected function getOriginalConstraint(): SymfonyConstraint
44 | {
45 | return new Assert\Range(['min' => self::MIN, 'max' => self::MAX]);
46 | }
47 |
48 | protected function getUnsupportedConstraint(): SymfonyConstraint
49 | {
50 | return new Assert\Valid();
51 | }
52 |
53 | protected function createFactory(): FactoryInterface
54 | {
55 | return new RangeFactory();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Constraint/Reader/FormTypeReaderTest.php:
--------------------------------------------------------------------------------
1 | createMock(FormConfigInterface::class);
19 | $config
20 | ->expects($this->once())
21 | ->method('hasOption')
22 | ->with('constraints')
23 | ->willReturn(false)
24 | ;
25 |
26 | $form = $this->createMock(FormInterface::class);
27 | $this->setUpForm($form, $config);
28 |
29 | $this->assertEmpty($this->createReader()->read($form));
30 | }
31 |
32 | public function testReadWithConstraints(): void
33 | {
34 | $config = $this->createMock(FormConfigInterface::class);
35 | $config
36 | ->expects($this->once())
37 | ->method('hasOption')
38 | ->with('constraints')
39 | ->willReturn(true)
40 | ;
41 | $config
42 | ->expects($this->once())
43 | ->method('getOption')
44 | ->with('constraints')
45 | ->willReturn([new NotBlank()])
46 | ;
47 |
48 | $form = $this->createMock(FormInterface::class);
49 | $this->setUpForm($form, $config);
50 |
51 | $this->assertEquals([new NotBlank()], $this->createReader()->read($form));
52 | }
53 |
54 | /**
55 | * @param MockObject|FormInterface $form
56 | * @param MockObject|FormConfigInterface $config
57 | */
58 | private function setUpForm(MockObject $form, MockObject $config): void
59 | {
60 | $form
61 | ->expects($this->once())
62 | ->method('getConfig')
63 | ->willReturn($config)
64 | ;
65 | }
66 |
67 | private function createReader(): FormTypeReader
68 | {
69 | return new FormTypeReader();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/LessThanTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\LessThan();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\LessThan([
25 | 'value' => 'foo',
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | // integer
32 | $constraint = new ParsleyAssert\LessThan([
33 | 'value' => 10,
34 | ]);
35 |
36 | $this->assertSame([
37 | 'data-parsley-lt' => '10',
38 | 'data-parsley-lt-message' => 'Invalid.',
39 | ], $constraint->normalize($this->normalizer));
40 |
41 | // float to int
42 | $constraint = new ParsleyAssert\LessThan([
43 | 'value' => 10.0,
44 | ]);
45 |
46 | $this->assertSame([
47 | 'data-parsley-lt' => '10',
48 | 'data-parsley-lt-message' => 'Invalid.',
49 | ], $constraint->normalize($this->normalizer));
50 |
51 | // floating
52 | $constraint = new ParsleyAssert\LessThan([
53 | 'value' => 10.5,
54 | ]);
55 |
56 | $this->assertSame([
57 | 'data-parsley-lt' => '10.5',
58 | 'data-parsley-lt-message' => 'Invalid.',
59 | ], $constraint->normalize($this->normalizer));
60 |
61 | // string
62 | $constraint = new ParsleyAssert\LessThan([
63 | 'value' => '10',
64 | ]);
65 |
66 | $this->assertSame([
67 | 'data-parsley-lt' => '10',
68 | 'data-parsley-lt-message' => 'Invalid.',
69 | ], $constraint->normalize($this->normalizer));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/GreaterThanTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\GreaterThan();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\GreaterThan([
25 | 'value' => 'foo',
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | // integer
32 | $constraint = new ParsleyAssert\GreaterThan([
33 | 'value' => 5,
34 | ]);
35 |
36 | $this->assertSame([
37 | 'data-parsley-gt' => '5',
38 | 'data-parsley-gt-message' => 'Invalid.',
39 | ], $constraint->normalize($this->normalizer));
40 |
41 | // float to int
42 | $constraint = new ParsleyAssert\GreaterThan([
43 | 'value' => 5.0,
44 | ]);
45 |
46 | $this->assertSame([
47 | 'data-parsley-gt' => '5',
48 | 'data-parsley-gt-message' => 'Invalid.',
49 | ], $constraint->normalize($this->normalizer));
50 |
51 | // floating
52 | $constraint = new ParsleyAssert\GreaterThan([
53 | 'value' => 5.2,
54 | ]);
55 |
56 | $this->assertSame([
57 | 'data-parsley-gt' => '5.2',
58 | 'data-parsley-gt-message' => 'Invalid.',
59 | ], $constraint->normalize($this->normalizer));
60 |
61 | // string
62 | $constraint = new ParsleyAssert\GreaterThan([
63 | 'value' => '5',
64 | ]);
65 |
66 | $this->assertSame([
67 | 'data-parsley-gt' => '5',
68 | 'data-parsley-gt-message' => 'Invalid.',
69 | ], $constraint->normalize($this->normalizer));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/LessThanOrEqualTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\LessThanOrEqual();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\LessThanOrEqual([
25 | 'value' => 'foo',
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | // integer
32 | $constraint = new ParsleyAssert\LessThanOrEqual([
33 | 'value' => 10,
34 | ]);
35 |
36 | $this->assertSame([
37 | 'data-parsley-lte' => '10',
38 | 'data-parsley-lte-message' => 'Invalid.',
39 | ], $constraint->normalize($this->normalizer));
40 |
41 | // float to int
42 | $constraint = new ParsleyAssert\LessThanOrEqual([
43 | 'value' => 10.0,
44 | ]);
45 |
46 | $this->assertSame([
47 | 'data-parsley-lte' => '10',
48 | 'data-parsley-lte-message' => 'Invalid.',
49 | ], $constraint->normalize($this->normalizer));
50 |
51 | // floating
52 | $constraint = new ParsleyAssert\LessThanOrEqual([
53 | 'value' => 10.3,
54 | ]);
55 |
56 | $this->assertSame([
57 | 'data-parsley-lte' => '10.3',
58 | 'data-parsley-lte-message' => 'Invalid.',
59 | ], $constraint->normalize($this->normalizer));
60 |
61 | // string
62 | $constraint = new ParsleyAssert\LessThanOrEqual([
63 | 'value' => '10',
64 | ]);
65 |
66 | $this->assertSame([
67 | 'data-parsley-lte' => '10',
68 | 'data-parsley-lte-message' => 'Invalid.',
69 | ], $constraint->normalize($this->normalizer));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Constraint/Constraints/GreaterThanOrEqualTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
16 |
17 | new ParsleyAssert\GreaterThanOrEqual();
18 | }
19 |
20 | public function testInvalidConfiguration(): void
21 | {
22 | $this->expectException(InvalidOptionsException::class);
23 |
24 | new ParsleyAssert\GreaterThanOrEqual([
25 | 'value' => 'foo',
26 | ]);
27 | }
28 |
29 | public function testNormalization(): void
30 | {
31 | // integer
32 | $constraint = new ParsleyAssert\GreaterThanOrEqual([
33 | 'value' => 5,
34 | ]);
35 |
36 | $this->assertSame([
37 | 'data-parsley-gte' => '5',
38 | 'data-parsley-gte-message' => 'Invalid.',
39 | ], $constraint->normalize($this->normalizer));
40 |
41 | // float to int
42 | $constraint = new ParsleyAssert\GreaterThanOrEqual([
43 | 'value' => 5.0,
44 | ]);
45 |
46 | $this->assertSame([
47 | 'data-parsley-gte' => '5',
48 | 'data-parsley-gte-message' => 'Invalid.',
49 | ], $constraint->normalize($this->normalizer));
50 |
51 | // floating
52 | $constraint = new ParsleyAssert\GreaterThanOrEqual([
53 | 'value' => 5.2,
54 | ]);
55 |
56 | $this->assertSame([
57 | 'data-parsley-gte' => '5.2',
58 | 'data-parsley-gte-message' => 'Invalid.',
59 | ], $constraint->normalize($this->normalizer));
60 |
61 | // string
62 | $constraint = new ParsleyAssert\GreaterThanOrEqual([
63 | 'value' => '5',
64 | ]);
65 |
66 | $this->assertSame([
67 | 'data-parsley-gte' => '5',
68 | 'data-parsley-gte-message' => 'Invalid.',
69 | ], $constraint->normalize($this->normalizer));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/DependencyInjection/JBen87ParsleyExtension.php:
--------------------------------------------------------------------------------
1 | alias = $alias;
19 | }
20 |
21 | public function load(array $configs, ContainerBuilder $container): void
22 | {
23 | $config = $this->processConfiguration(new Configuration($this->alias), $configs);
24 |
25 | $container->setParameter('jben87_parsley.enabled', $config['enabled']);
26 | $container->setParameter('jben87_parsley.trigger_event', $config['trigger_event']);
27 |
28 | $this->setDateTimePatternParameters($config, $container);
29 |
30 | $loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__) . '/../config'));
31 | $loader->load('services.yaml');
32 | }
33 |
34 | public function getAlias(): string
35 | {
36 | return $this->alias;
37 | }
38 |
39 | private function setDateTimePatternParameters(array $config, ContainerBuilder $container): void
40 | {
41 | $locale = $container->getParameter('locale');
42 |
43 | $datePattern = '\d{4}-\d{2}-\d{2}';
44 | if (true === array_key_exists($locale, $config['date_pattern'])) {
45 | $datePattern = $config['date_pattern'][$locale];
46 | }
47 |
48 | $timePattern = '\d{2}:\d{2}';
49 | if (true === array_key_exists($locale, $config['time_pattern'])) {
50 | $timePattern = $config['time_pattern'][$locale];
51 | }
52 |
53 | $dateTimePattern = sprintf('%s %s', $datePattern, $timePattern);
54 | if (true === array_key_exists($locale, $config['datetime_pattern'])) {
55 | $dateTimePattern = $config['datetime_pattern'][$locale];
56 | }
57 |
58 | $container->setParameter('jben87_parsley.date_pattern', $datePattern);
59 | $container->setParameter('jben87_parsley.time_pattern', $timePattern);
60 | $container->setParameter('jben87_parsley.datetime_pattern', $dateTimePattern);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Constraint/Constraints/Range.php:
--------------------------------------------------------------------------------
1 | constraints = [
23 | 'min' => $this->createMin($options),
24 | 'max' => $this->createMax($options),
25 | ];
26 | }
27 |
28 | public function normalize(NormalizerInterface $normalizer, $format = null, array $context = []): array
29 | {
30 | return array_merge(
31 | $this->constraints['min']->normalize($normalizer, 'array'),
32 | $this->constraints['max']->normalize($normalizer, 'array')
33 | );
34 | }
35 |
36 | /**
37 | * @codeCoverageIgnore
38 | */
39 | protected function getAttribute(): string
40 | {
41 | throw new \RuntimeException('Should not be called.');
42 | }
43 |
44 | /**
45 | * @codeCoverageIgnore
46 | */
47 | protected function getValue(): string
48 | {
49 | throw new \RuntimeException('Should not be called.');
50 | }
51 |
52 | protected function configureOptions(OptionsResolver $resolver): void
53 | {
54 | $resolver
55 | ->setRequired(['min', 'max'])
56 | ->setAllowedTypes('min', ['numeric'])
57 | ->setAllowedTypes('max', ['numeric'])
58 | ->setDefined(['minMessage', 'maxMessage'])
59 | ;
60 | }
61 |
62 | private function createMin(array $defaults): Min
63 | {
64 | $options = ['min' => $defaults['min']];
65 |
66 | if (array_key_exists('minMessage', $defaults)) {
67 | $options['message'] = $defaults['minMessage'];
68 | }
69 |
70 | return new Min($options);
71 | }
72 |
73 | private function createMax(array $defaults): Max
74 | {
75 | $options = ['max' => $defaults['max']];
76 |
77 | if (array_key_exists('maxMessage', $defaults)) {
78 | $options['message'] = $defaults['maxMessage'];
79 | }
80 |
81 | return new Max($options);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/FactoryTestCase.php:
--------------------------------------------------------------------------------
1 | setUpCreate();
33 | }
34 |
35 | $this->assertEquals($expected, $this->factory->create($constraint));
36 | }
37 |
38 | public function provideCreate(): array
39 | {
40 | return [
41 | [
42 | $this->getExpectedConstraint(),
43 | $this->getOriginalConstraint(),
44 | ],
45 | ];
46 | }
47 |
48 | /**
49 | * @dataProvider provideSupports
50 | */
51 | public function testSupports(SymfonyConstraint $supported, SymfonyConstraint $unsupported): void
52 | {
53 | $this->assertFalse($this->factory->supports($unsupported));
54 | $this->assertTrue($this->factory->supports($supported));
55 | }
56 |
57 | public function provideSupports(): array
58 | {
59 | return [
60 | [
61 | $this->getOriginalConstraint(),
62 | $this->getUnsupportedConstraint(),
63 | ],
64 | ];
65 | }
66 |
67 | protected function setUp(): void
68 | {
69 | $this->factory = $this->createFactory();
70 | $this->translator = $this->createMock(TranslatorInterface::class);
71 |
72 | if ($this->factory instanceof TranslatableFactoryInterface) {
73 | $this->factory->setTranslator($this->translator);
74 | }
75 | }
76 |
77 | protected function setUpCreate(): void
78 | {
79 | }
80 |
81 | protected function getExpectedConstraint(): Constraint
82 | {
83 | throw new \RuntimeException('Not implemented yet.');
84 | }
85 |
86 | protected function getUnsupportedConstraint(): SymfonyConstraint
87 | {
88 | throw new \RuntimeException('Not implemented yet.');
89 | }
90 |
91 | protected function getOriginalConstraint(): SymfonyConstraint
92 | {
93 | throw new \RuntimeException('Not implemented yet.');
94 | }
95 |
96 | abstract protected function createFactory(): FactoryInterface;
97 | }
98 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: 'Tests'
2 |
3 | on: 'push'
4 |
5 | jobs:
6 | phpunit:
7 | name: 'PHPUnit'
8 | runs-on: 'ubuntu-latest'
9 |
10 | strategy:
11 | matrix:
12 | include:
13 | - php-version: '7.4'
14 | symfony-version: '5.4.*'
15 | - php-version: '7.4'
16 | symfony-version: '6.0.*'
17 | - php-version: '8.1'
18 | symfony-version: '5.4.*'
19 | - php-version: '8.1'
20 | symfony-version: '6.0.*'
21 |
22 | steps:
23 | - name: 'Checkout'
24 | uses: 'actions/checkout@v2'
25 |
26 | - name: 'Setup PHP'
27 | uses: 'shivammathur/setup-php@v2'
28 | with:
29 | coverage: 'none'
30 | php-version: '${{ matrix.php-version }}'
31 |
32 | - name: 'Install dependencies with composer'
33 | run: 'composer update --no-interaction --no-progress'
34 |
35 | - name: 'Run tests with phpunit/phpunit'
36 | run: 'vendor/bin/phpunit'
37 |
38 | codecov:
39 | name: 'Code coverage'
40 | runs-on: 'ubuntu-latest'
41 |
42 | strategy:
43 | matrix:
44 | include:
45 | - php-version: '8.1'
46 |
47 | steps:
48 | - name: 'Checkout'
49 | uses: 'actions/checkout@v2'
50 |
51 | - name: 'Setup PHP'
52 | uses: 'shivammathur/setup-php@v2'
53 | with:
54 | coverage: 'xdebug'
55 | php-version: '${{ matrix.php-version }}'
56 |
57 | - name: 'Install dependencies with composer'
58 | run: 'composer update --no-interaction --no-progress'
59 |
60 | - name: 'Run tests with phpunit/phpunit'
61 | env:
62 | CODECOV_TOKEN: '${{ secrets.CODECOV_TOKEN }}'
63 | run: 'vendor/bin/phpunit --coverage-clover coverage.xml'
64 |
65 | - name: 'Upload coverage to Codecov'
66 | uses: 'codecov/codecov-action@v1'
67 |
68 | checkstyke:
69 | name: 'Checkstyle'
70 | runs-on: 'ubuntu-latest'
71 |
72 | strategy:
73 | matrix:
74 | include:
75 | - php-version: '8.1'
76 |
77 | steps:
78 | - name: 'Checkout'
79 | uses: 'actions/checkout@v2'
80 |
81 | - name: 'Setup PHP'
82 | uses: 'shivammathur/setup-php@v2'
83 | with:
84 | coverage: 'xdebug'
85 | php-version: '${{ matrix.php-version }}'
86 |
87 | - name: 'Install dependencies with composer'
88 | run: 'composer update --no-interaction --no-progress'
89 |
90 | - name: 'Run checkstyle with squizlabs/php_codesniffer'
91 | run: 'vendor/bin/phpcs'
92 |
93 | phpstan:
94 | name: 'PHPStan'
95 | runs-on: 'ubuntu-latest'
96 |
97 | strategy:
98 | matrix:
99 | include:
100 | - php-version: '8.1'
101 |
102 | steps:
103 | - name: 'Checkout'
104 | uses: 'actions/checkout@v2'
105 |
106 | - name: 'Setup PHP'
107 | uses: 'shivammathur/setup-php@v2'
108 | with:
109 | coverage: 'xdebug'
110 | php-version: '${{ matrix.php-version }}'
111 |
112 | - name: 'Install dependencies with composer'
113 | run: 'composer update --no-interaction --no-progress'
114 |
115 | - name: 'Run static analysis with phpstan/phpstan'
116 | run: 'vendor/bin/phpcs'
117 |
--------------------------------------------------------------------------------
/src/Form/Extension/ParsleyTypeExtension.php:
--------------------------------------------------------------------------------
1 | factoryRegistry = $factoryRegistry;
37 | $this->logger = $logger;
38 | $this->normalizer = $normalizer;
39 | $this->readerRegistry = $readerRegistry;
40 | $this->enabled = $enabled;
41 | $this->triggerEvent = $triggerEvent;
42 | }
43 |
44 | public function finishView(FormView $view, FormInterface $form, array $options): void
45 | {
46 | if (false === $options['parsley_enabled']) {
47 | return;
48 | }
49 |
50 | // enable parsley validation on root form
51 | if (true === $form->isRoot()) {
52 | $view->vars['attr'] += [
53 | 'novalidate' => true,
54 | 'data-parsley-validate' => true,
55 | ];
56 |
57 | return;
58 | }
59 |
60 | // set trigger event attribute on form children
61 | $view->vars['attr']['data-parsley-trigger'] = $options['parsley_trigger_event'];
62 |
63 | // build constraints and map them as data attributes
64 | foreach ($this->getConstraints($form) as $symfonyConstraint) {
65 | try {
66 | $factory = $this->factoryRegistry->findForConstraint($symfonyConstraint);
67 | $constraint = $factory->create($symfonyConstraint);
68 |
69 | $view->vars['attr'] = array_merge($view->vars['attr'], $constraint->normalize($this->normalizer));
70 | } catch (ConstraintException $exception) {
71 | $this->logger->warning($exception->getMessage(), ['constraint' => $symfonyConstraint]);
72 | }
73 | }
74 | }
75 |
76 | public function configureOptions(OptionsResolver $resolver): void
77 | {
78 | $resolver->setDefaults([
79 | 'parsley_enabled' => $this->enabled,
80 | 'parsley_trigger_event' => $this->triggerEvent,
81 | ]);
82 | }
83 |
84 | public static function getExtendedTypes(): iterable
85 | {
86 | yield FormType::class;
87 | }
88 |
89 | /**
90 | * @return SymfonyConstraint[]
91 | */
92 | private function getConstraints(FormInterface $form): array
93 | {
94 | $constraints = [];
95 | foreach ($this->readerRegistry->all() as $reader) {
96 | $constraints = array_merge($constraints, $reader->read($form));
97 | }
98 |
99 | return $constraints;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/tests/Constraint/Factory/LengthFactoryTest.php:
--------------------------------------------------------------------------------
1 | self::MIN,
31 | 'max' => self::MAX,
32 | 'message' => self::TRANSLATED_MESSAGE,
33 | ]),
34 | new Assert\Length([
35 | 'min' => self::MIN,
36 | 'max' => self::MAX,
37 | ]),
38 | function (LengthFactoryTest $self): void {
39 | $self->translator
40 | ->expects($this->once())
41 | ->method('trans')
42 | ->with(
43 | self::ORIGINAL_MESSAGE,
44 | ['{{ min }}' => self::MIN, '{{ max }}' => self::MAX]
45 | )
46 | ->willReturn(self::TRANSLATED_MESSAGE)
47 | ;
48 | },
49 | ],
50 | [
51 | new ParsleyAssert\Length([
52 | 'min' => self::LIMIT,
53 | 'max' => self::LIMIT,
54 | 'message' => self::TRANSLATED_EXACT_MESSAGE,
55 | ]),
56 | new Assert\Length([
57 | 'min' => self::LIMIT,
58 | 'max' => self::LIMIT,
59 | ]),
60 | function (LengthFactoryTest $self): void {
61 | $self->translator
62 | ->expects($this->exactly(2))
63 | ->method('trans')
64 | ->withConsecutive(
65 | [
66 | self::ORIGINAL_MESSAGE,
67 | ['{{ min }}' => self::LIMIT, '{{ max }}' => self::LIMIT],
68 | ],
69 | [
70 | self::ORIGINAL_EXACT_MESSAGE,
71 | ['{{ limit }}' => self::LIMIT, '%count%' => self::LIMIT],
72 | ]
73 | )
74 | ->willReturnOnConsecutiveCalls(
75 | sprintf('This value should have %d to %d characters.', self::MAX, self::MIN),
76 | self::TRANSLATED_EXACT_MESSAGE
77 | )
78 | ;
79 | },
80 | ],
81 | ];
82 | }
83 |
84 | protected function setUpCreate(): void
85 | {
86 | $this->translator
87 | ->expects($this->once())
88 | ->method('trans')
89 | ->with(self::ORIGINAL_MESSAGE, ['{{ min }}' => self::MIN, '{{ max }}' => self::MAX])
90 | ->willReturn(self::TRANSLATED_MESSAGE)
91 | ;
92 | }
93 |
94 | protected function getOriginalConstraint(): SymfonyConstraint
95 | {
96 | return new Assert\Length([
97 | 'min' => self::MIN,
98 | 'max' => self::MAX,
99 | ]);
100 | }
101 |
102 | protected function getUnsupportedConstraint(): SymfonyConstraint
103 | {
104 | return new Assert\Valid();
105 | }
106 |
107 | protected function createFactory(): FactoryInterface
108 | {
109 | return new LengthFactory();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/tests/Constraint/Reader/DataClassReaderTest.php:
--------------------------------------------------------------------------------
1 | createMock(FormInterface::class);
31 | $this->setUpForm($form, $this->once(), null);
32 |
33 | $this->assertEmpty($this->createReader()->read($form));
34 | }
35 |
36 | /**
37 | * @dataProvider provideReadNoMetadata
38 | */
39 | public function testReadNoMetadata(callable $setUpValidator): void
40 | {
41 | $form = $this->createMock(FormInterface::class);
42 | $this->setUpForm($form);
43 |
44 | $setUpValidator($this->validator);
45 |
46 | $this->assertEmpty($this->createReader()->read($form));
47 | }
48 |
49 | public function provideReadNoMetadata(): array
50 | {
51 | return [
52 | [
53 | function (MockObject $validator): void {
54 | $validator
55 | ->expects($this->once())
56 | ->method('getMetadataFor')
57 | ->with($this->isType('string'))
58 | ->willThrowException(new NoSuchMetadataException())
59 | ;
60 | },
61 | ],
62 | [
63 | function (MockObject $validator): void {
64 | $validator
65 | ->expects($this->once())
66 | ->method('getMetadataFor')
67 | ->with($this->isType('string'))
68 | ->willReturn(new GenericMetadata())
69 | ;
70 | },
71 | ],
72 | ];
73 | }
74 |
75 | public function testReadNoPropertyMetadata(): void
76 | {
77 | $form = $this->createMock(FormInterface::class);
78 | $this->setUpForm($form);
79 |
80 | $this->setUpValidator($this->validator, $this->never(), []);
81 |
82 | $this->assertEmpty($this->createReader()->read($form));
83 | }
84 |
85 | public function testReadNoConstraints(): void
86 | {
87 | $form = $this->createMock(FormInterface::class);
88 | $this->setUpForm($form);
89 |
90 | $propertyMetadatum = $this->createMock(PropertyMetadataInterface::class);
91 | $propertyMetadatum
92 | ->expects($this->once())
93 | ->method('findConstraints')
94 | ->with('Default')
95 | ->willReturn([])
96 | ;
97 |
98 | $this->setUpValidator($this->validator, $this->once(), [$propertyMetadatum]);
99 |
100 | $this->assertEmpty($this->createReader()->read($form));
101 | }
102 |
103 | public function testReadWithConstraints(): void
104 | {
105 | $form = $this->createMock(FormInterface::class);
106 | $this->setUpForm($form);
107 |
108 | $propertyMetadatum1 = $this->createMock(PropertyMetadataInterface::class);
109 | $propertyMetadatum1
110 | ->expects($this->once())
111 | ->method('findConstraints')
112 | ->with('Default')
113 | ->willReturn([new NotBlank()])
114 | ;
115 |
116 | $propertyMetadatum2 = $this->createMock(PropertyMetadataInterface::class);
117 | $propertyMetadatum2
118 | ->expects($this->once())
119 | ->method('findConstraints')
120 | ->with('Default')
121 | ->willReturn([new NotNull()])
122 | ;
123 |
124 | $this->setUpValidator($this->validator, $this->exactly(2), [$propertyMetadatum1, $propertyMetadatum2]);
125 |
126 | $this->assertEquals([new NotBlank(), new NotNull()], $this->createReader()->read($form));
127 | }
128 |
129 | protected function setUp(): void
130 | {
131 | $this->validator = $this->createMock(ValidatorInterface::class);
132 | }
133 |
134 | /**
135 | * @param MockObject|FormInterface $form
136 | */
137 | private function setUpForm(
138 | MockObject $form,
139 | Rule\InvokedCount $configMatcher = null,
140 | ?string $dataClass = '\\stdClass'
141 | ): void {
142 | if (null === $configMatcher) {
143 | $configMatcher = $this->exactly(2);
144 | }
145 |
146 | $config = $this->createMock(FormConfigInterface::class);
147 | $config
148 | ->expects($configMatcher)
149 | ->method('getDataClass')
150 | ->willReturn($dataClass)
151 | ;
152 |
153 | $rootForm = $this->createMock(FormInterface::class);
154 | $rootForm
155 | ->expects($this->once())
156 | ->method('getConfig')
157 | ->willReturn($config)
158 | ;
159 |
160 | $form
161 | ->expects($this->once())
162 | ->method('getRoot')
163 | ->willReturn($rootForm)
164 | ;
165 |
166 | $form
167 | ->method('getName')
168 | ->willReturn('Foo')
169 | ;
170 | }
171 |
172 | /**
173 | * @param MockObject|ValidatorInterface $validator
174 | * @param PropertyMetadataInterface[] $propertyMetadata
175 | */
176 | private function setUpValidator(
177 | MockObject $validator,
178 | Rule\InvokedCount $groupMatcher,
179 | array $propertyMetadata
180 | ): void {
181 | $metadata = $this->createMock(ClassMetadata::class);
182 | $metadata
183 | ->expects($this->once())
184 | ->method('getPropertyMetadata')
185 | ->with($this->isType('string'))
186 | ->willReturn($propertyMetadata)
187 | ;
188 | $metadata
189 | ->expects($groupMatcher)
190 | ->method('getDefaultGroup')
191 | ->willReturn('Default')
192 | ;
193 |
194 | $validator
195 | ->expects($this->once())
196 | ->method('getMetadataFor')
197 | ->with($this->isType('string'))
198 | ->willReturn($metadata)
199 | ;
200 | }
201 |
202 | private function createReader(): DataClassReader
203 | {
204 | return new DataClassReader($this->validator);
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ParsleyBundle
2 |
3 | [](https://travis-ci.com/bjd-php/parsley-bundle)
4 | [](https://scrutinizer-ci.com/g/bjd-php/parsley-bundle/?branch=master)
5 | [](https://scrutinizer-ci.com/g/bjd-php/parsley-bundle/?branch=master)
6 |
7 | Convert Symfony constraints into data-attributes for client-side validation with [Parsley](http://parsleyjs.org/).
8 |
9 | ## Installation
10 |
11 | Install the bundle with composer:
12 |
13 | ```bash
14 | composer require j-ben87/parsley-bundle
15 | ```
16 |
17 | Install Parsley library: http://parsleyjs.org/doc/index.html#installation
18 |
19 | ## Configuration
20 |
21 | The bundle exposes a basic configuration:
22 |
23 | ```yml
24 | jben87_parsley:
25 | enabled: true # enable/disable Parsley validation globally (can be enabled on FormType or Constraint level)
26 | trigger_event: 'blur' # the JavaScript event for which the validation is to be triggered (relative to the selected input)
27 | ```
28 |
29 | ## Usage
30 |
31 | ### Form constraints
32 |
33 | Create a `FormType`.
34 |
35 | Any supported constraints you have defined on your form will automatically be turned into Parsley data-attributes.
36 |
37 | Yes, it's that simple!
38 |
39 | ```php
40 | add('title', TextType::class, [
58 | 'constraints' => [
59 | new Assert\NotBlank(),
60 | new Assert\Length(30),
61 | ],
62 | ])
63 | ->add('content', TextareaType::class, [
64 | 'constraints' => [
65 | new Assert\NotBlank(),
66 | ],
67 | ])
68 | ;
69 | }
70 | }
71 | ```
72 |
73 | Results in:
74 |
75 | ```html
76 |
77 |
78 |
79 |
80 |
81 | ```
82 |
83 | ### `data-class` constraints
84 |
85 | Create a `FormType` and configure its `data-class` option.
86 |
87 | Any supported constraint you have defined on your class will automatically be turned into Parsley data-attributes.
88 |
89 | Here again, it's incredibly simple!
90 |
91 | ```php
92 | add('username')
137 | ->add('email')
138 | ;
139 | }
140 |
141 | public function configureOptions(OptionsResolver $resolver): void
142 | {
143 | $resolver->setDefault('data_class', User::class);
144 | }
145 | }
146 | ```
147 |
148 | Results in:
149 |
150 | ```html
151 | {% {{ form_widget(form.username) }} %}
152 |
153 |
154 | {% {{ form_widget(form.email }} %}
155 |
156 | ```
157 |
158 | **Notice:** if you define the same constraint on both the `FormType` and the configured `data-class`, the `FormType` constraint will override the one configured on the `data-class`.
159 |
160 | ## Internals
161 |
162 | The `ParsleyTypeExtension` is where all the magic happens.
163 |
164 | It gathers all Symfony constraints thanks to registered readers and turn them into Parsley constraints through factories.
165 |
166 | It uses the special `ChainFactory` to automatically find the first factory that supports the given Symfony constraint.
167 |
168 | Finally it normalizes the Parsley constraint into data-attributes and merge them with the `FormView` attributes.
169 |
170 | ## Extending the bundle
171 |
172 | You can easily add more constraints by:
173 |
174 | - creating a constraint that extends the abstract class `JBen87\ParsleyBundle\Constraint\Constraint`
175 | - creating a factory that implements the interface `JBen87\ParsleyBundle\Constraint\Factory\FactoryInterface`
176 |
177 | Your factory will be automatically registered to be used by the `ChainFactory` service.
178 |
179 | ```php
180 | $this->trans($constraint->message),
226 | ]);
227 | }
228 |
229 | public function supports(SymfonyConstraint $constraint): bool
230 | {
231 | return $constraint instanceof Assert\Valid;
232 | }
233 | }
234 | ```
235 |
236 | ## Supported constraints
237 |
238 | The following Symfony constraints are currently supported:
239 |
240 | - Symfony\Component\Validator\Constraints\Date
241 | - Symfony\Component\Validator\Constraints\DateTime
242 | - Symfony\Component\Validator\Constraints\Email
243 | - Symfony\Component\Validator\Constraints\GreaterThan
244 | - Symfony\Component\Validator\Constraints\GreaterThanOrEqual
245 | - Symfony\Component\Validator\Constraints\Length
246 | - Symfony\Component\Validator\Constraints\LessThan
247 | - Symfony\Component\Validator\Constraints\LessThanOrEqual
248 | - Symfony\Component\Validator\Constraints\NotBlank
249 | - Symfony\Component\Validator\Constraints\Range
250 | - Symfony\Component\Validator\Constraints\Time
251 | - Symfony\Component\Validator\Constraints\Type
252 | - Symfony\Component\Validator\Constraints\Url
253 |
254 | ## What's next
255 |
256 | - Support more constraints
257 | - Support group validation
258 |
--------------------------------------------------------------------------------
/tests/Form/Extension/ParsleyTypeExtensionTest.php:
--------------------------------------------------------------------------------
1 | createExtension(new FactoryRegistry([]), new ReaderRegistry([]));
40 | $options = $this->resolveExtensionOptions($extension, []);
41 |
42 | $this->assertCount(1, ParsleyTypeExtension::getExtendedTypes());
43 | $this->assertContains(FormType::class, ParsleyTypeExtension::getExtendedTypes());
44 | $this->assertTrue($options['parsley_enabled']);
45 | $this->assertSame('blur', $options['parsley_trigger_event']);
46 | }
47 |
48 | /**
49 | * @dataProvider provideFinishViewDisabled
50 | */
51 | public function testFinishViewDisabled(bool $enabled, bool $parsleyEnabled): void
52 | {
53 | $form = $this->createMock(FormInterface::class);
54 | $view = $this->createMock(FormView::class);
55 |
56 | $extension = $this->createExtension(new FactoryRegistry([]), new ReaderRegistry([]), $enabled);
57 | $options = $this->resolveExtensionOptions($extension, ['parsley_enabled' => $parsleyEnabled]);
58 | $extension->finishView($view, $form, $options);
59 |
60 | $this->assertEmpty($view->vars['attr']);
61 | }
62 |
63 | public function provideFinishViewDisabled(): array
64 | {
65 | return [
66 | [false, false],
67 | [true, false],
68 | ];
69 | }
70 |
71 | public function testFinishViewRootForm(): void
72 | {
73 | $form = $this->createMock(FormInterface::class);
74 | $view = $this->createMock(FormView::class);
75 |
76 | $this->setUpForm($form, true);
77 |
78 | $extension = $this->createExtension(new FactoryRegistry([]), new ReaderRegistry([]));
79 | $options = $this->resolveExtensionOptions($extension);
80 | $extension->finishView($view, $form, $options);
81 |
82 | $this->assertSame(
83 | [
84 | 'novalidate' => true,
85 | 'data-parsley-validate' => true,
86 | ],
87 | $view->vars['attr']
88 | );
89 | }
90 |
91 | public function testFinishViewWithoutReaders(): void
92 | {
93 | $form = $this->createMock(FormInterface::class);
94 | $view = $this->createMock(FormView::class);
95 |
96 | $this->setUpForm($form);
97 |
98 | $extension = $this->createExtension(new FactoryRegistry([]), new ReaderRegistry([]));
99 | $options = $this->resolveExtensionOptions($extension);
100 | $extension->finishView($view, $form, $options);
101 |
102 | $this->assertSame(['data-parsley-trigger' => 'blur'], $view->vars['attr']);
103 | }
104 |
105 | public function testFinishViewWithoutConstraints(): void
106 | {
107 | $form = $this->createMock(FormInterface::class);
108 | $view = $this->createMock(FormView::class);
109 |
110 | $this->setUpForm($form);
111 |
112 | $extension = $this->createExtension(
113 | new FactoryRegistry([]),
114 | new ReaderRegistry([
115 | new class implements ReaderInterface
116 | {
117 | public function read(FormInterface $form): array
118 | {
119 | return [];
120 | }
121 |
122 | public function getPriority(): int
123 | {
124 | return 0;
125 | }
126 | },
127 | ])
128 | );
129 |
130 | $options = $this->resolveExtensionOptions($extension);
131 | $extension->finishView($view, $form, $options);
132 |
133 | $this->assertSame(['data-parsley-trigger' => 'blur'], $view->vars['attr']);
134 | }
135 |
136 | public function testFinishViewWithUnsupportedConstraints(): void
137 | {
138 | $unsupportedConstraint = new Assert\Valid();
139 |
140 | $form = $this->createMock(FormInterface::class);
141 | $view = $this->createMock(FormView::class);
142 |
143 | $this->setUpForm($form);
144 |
145 | $this->logger
146 | ->expects($this->once())
147 | ->method('warning')
148 | ->with($this->isType('string'), ['constraint' => $unsupportedConstraint])
149 | ;
150 |
151 | $extension = $this->createExtension(
152 | new FactoryRegistry([]),
153 | new ReaderRegistry([
154 | new class ([$unsupportedConstraint]) implements ReaderInterface
155 | {
156 | private array $data;
157 |
158 | public function __construct(array $data)
159 | {
160 | $this->data = $data;
161 | }
162 |
163 | public function read(FormInterface $form): array
164 | {
165 | return $this->data;
166 | }
167 |
168 | public function getPriority(): int
169 | {
170 | return 0;
171 | }
172 | },
173 | ])
174 | );
175 |
176 | $options = $this->resolveExtensionOptions($extension);
177 | $extension->finishView($view, $form, $options);
178 |
179 | $this->assertSame(['data-parsley-trigger' => 'blur'], $view->vars['attr']);
180 | }
181 |
182 | public function testFinishViewWithConstraints(): void
183 | {
184 | $form = $this->createMock(FormInterface::class);
185 | $view = $this->createMock(FormView::class);
186 |
187 | $this->setUpForm($form);
188 |
189 | $translator = $this->createMock(TranslatorInterface::class);
190 | $translator->method('trans')->willReturn('Invalid.');
191 |
192 | $factory1 = new RequiredFactory();
193 | $factory1->setTranslator($translator);
194 |
195 | $translator = $this->createMock(TranslatorInterface::class);
196 | $translator->method('trans')->willReturn('Invalid.');
197 |
198 | $factory2 = new DateFactory('foo');
199 | $factory2->setTranslator($translator);
200 |
201 | $extension = $this->createExtension(
202 | new FactoryRegistry([$factory1, $factory2]),
203 | new ReaderRegistry([
204 | new class ([new Assert\NotBlank(), new Assert\Date()]) implements ReaderInterface
205 | {
206 | private array $data;
207 |
208 | public function __construct(array $data)
209 | {
210 | $this->data = $data;
211 | }
212 |
213 | public function read(FormInterface $form): array
214 | {
215 | return $this->data;
216 | }
217 |
218 | public function getPriority(): int
219 | {
220 | return 0;
221 | }
222 | },
223 | ])
224 | );
225 |
226 | $options = $this->resolveExtensionOptions($extension);
227 | $extension->finishView($view, $form, $options);
228 |
229 | $this->assertSame(
230 | [
231 | 'data-parsley-trigger' => 'blur',
232 | 'data-parsley-required' => 'true',
233 | 'data-parsley-required-message' => 'Invalid.',
234 | 'data-parsley-pattern' => 'foo',
235 | 'data-parsley-pattern-message' => 'Invalid.',
236 | ],
237 | $view->vars['attr']
238 | );
239 | }
240 |
241 | protected function setUp(): void
242 | {
243 | $this->logger = $this->createMock(LoggerInterface::class);
244 | $this->normalizer = $this->createMock(NormalizerInterface::class);
245 | }
246 |
247 | /**
248 | * @param MockObject|FormInterface $form
249 | */
250 | private function setUpForm(MockObject $form, bool $root = false): void
251 | {
252 | $form
253 | ->expects($this->once())
254 | ->method('isRoot')
255 | ->willReturn($root)
256 | ;
257 | }
258 |
259 | private function resolveExtensionOptions(AbstractTypeExtension $extension, array $options = []): array
260 | {
261 | $resolver = new OptionsResolver();
262 | $extension->configureOptions($resolver);
263 |
264 | return $resolver->resolve($options);
265 | }
266 |
267 | private function createExtension(
268 | FactoryRegistry $factoryRegistry,
269 | ReaderRegistry $readerRegistry,
270 | bool $enabled = true
271 | ): ParsleyTypeExtension {
272 | return new ParsleyTypeExtension(
273 | $factoryRegistry,
274 | $this->logger,
275 | $this->normalizer,
276 | $readerRegistry,
277 | $enabled,
278 | 'blur'
279 | );
280 | }
281 | }
282 |
--------------------------------------------------------------------------------