From 538d2b0ce7c92abed3d4559904cc246b95c7ea12 Mon Sep 17 00:00:00 2001 From: Dan Burzynski Date: Fri, 26 Jan 2018 15:35:57 +0000 Subject: [PATCH 1/3] Not all things parsed to Parsley are Directives --- README.md | 4 ++-- src/Contracts/ConstraintInterface.php | 4 ++-- src/Factory/ConstraintFactory.php | 4 ++-- src/Form/Extension/ParsleyTypeExtension.php | 2 +- .../Directive/Field/Constraint/AbstractComparison.php | 4 ++-- .../Directive/Field/Constraint/AbstractConstraint.php | 6 +++--- .../Directive/Field/Constraint/AbstractLength.php | 4 ++-- src/{ => Parsleys}/Directive/Field/Constraint/Email.php | 4 ++-- src/{ => Parsleys}/Directive/Field/Constraint/Length.php | 4 ++-- src/{ => Parsleys}/Directive/Field/Constraint/Max.php | 4 ++-- .../Directive/Field/Constraint/MaxLength.php | 4 ++-- src/{ => Parsleys}/Directive/Field/Constraint/Min.php | 4 ++-- .../Directive/Field/Constraint/MinLength.php | 4 ++-- src/{ => Parsleys}/Directive/Field/Constraint/Pattern.php | 4 ++-- src/{ => Parsleys}/Directive/Field/Constraint/Range.php | 4 ++-- .../Directive/Field/Constraint/Required.php | 4 ++-- .../Directive/Field/ConstraintErrorMessage.php | 6 +++--- src/{ => Parsleys}/Directive/Field/Generic.php | 6 +++--- tests/Factory/ConstraintFactoryTest.php | 2 +- tests/Fixtures/Form/Type/TestType.php | 6 +++--- .../Directive/Field/Constraint/EmailTest.php | 4 ++-- .../Directive/Field/Constraint/LengthTest.php | 4 ++-- .../Directive/Field/Constraint/MaxLengthTest.php | 4 ++-- .../{ => Parsleys}/Directive/Field/Constraint/MaxTest.php | 4 ++-- .../Directive/Field/Constraint/MinLengthTest.php | 4 ++-- .../{ => Parsleys}/Directive/Field/Constraint/MinTest.php | 4 ++-- .../Directive/Field/Constraint/PatternTest.php | 4 ++-- .../Directive/Field/Constraint/RangeTest.php | 4 ++-- .../Directive/Field/Constraint/RequiredTest.php | 4 ++-- .../Directive/Field/ConstraintErrorMessageTest.php | 8 ++++---- 30 files changed, 64 insertions(+), 64 deletions(-) rename src/{ => Parsleys}/Directive/Field/Constraint/AbstractComparison.php (90%) rename src/{ => Parsleys}/Directive/Field/Constraint/AbstractConstraint.php (90%) rename src/{ => Parsleys}/Directive/Field/Constraint/AbstractLength.php (89%) rename src/{ => Parsleys}/Directive/Field/Constraint/Email.php (73%) rename src/{ => Parsleys}/Directive/Field/Constraint/Length.php (84%) rename src/{ => Parsleys}/Directive/Field/Constraint/Max.php (62%) rename src/{ => Parsleys}/Directive/Field/Constraint/MaxLength.php (83%) rename src/{ => Parsleys}/Directive/Field/Constraint/Min.php (62%) rename src/{ => Parsleys}/Directive/Field/Constraint/MinLength.php (83%) rename src/{ => Parsleys}/Directive/Field/Constraint/Pattern.php (88%) rename src/{ => Parsleys}/Directive/Field/Constraint/Range.php (94%) rename src/{ => Parsleys}/Directive/Field/Constraint/Required.php (72%) rename src/{ => Parsleys}/Directive/Field/ConstraintErrorMessage.php (92%) rename src/{ => Parsleys}/Directive/Field/Generic.php (89%) rename tests/{ => Parsleys}/Directive/Field/Constraint/EmailTest.php (79%) rename tests/{ => Parsleys}/Directive/Field/Constraint/LengthTest.php (80%) rename tests/{ => Parsleys}/Directive/Field/Constraint/MaxLengthTest.php (80%) rename tests/{ => Parsleys}/Directive/Field/Constraint/MaxTest.php (84%) rename tests/{ => Parsleys}/Directive/Field/Constraint/MinLengthTest.php (80%) rename tests/{ => Parsleys}/Directive/Field/Constraint/MinTest.php (84%) rename tests/{ => Parsleys}/Directive/Field/Constraint/PatternTest.php (81%) rename tests/{ => Parsleys}/Directive/Field/Constraint/RangeTest.php (88%) rename tests/{ => Parsleys}/Directive/Field/Constraint/RequiredTest.php (80%) rename tests/{ => Parsleys}/Directive/Field/ConstraintErrorMessageTest.php (80%) diff --git a/README.md b/README.md index 6aa39dc..2324bfb 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ For reasons that I can't quite imagine, you might only want to just add client s TextType::class, [ 'parsleys' => [ - new \C0ntax\ParsleyBundle\Directive\Field\Constraint\MinLength(2, 'You need more than %s chars'), + new \C0ntax\ParsleyBundle\Parsleys\Directive\Field\Constraint\MinLength(2, 'You need more than %s chars'), ], ] ) @@ -146,7 +146,7 @@ Let's assume that the Entity in the example above is out of your control. It's c TextType::class, [ 'parsleys' => [ - new \C0ntax\ParsleyBundle\Directive\Field\ConstraintErrorMessage(\C0ntax\ParsleyBundle\Directive\Field\Constraint\MinLength::class, 'You need more than %s chars'), + new \C0ntax\ParsleyBundle\Parsleys\Directive\Field\ConstraintErrorMessage(\C0ntax\ParsleyBundle\Parsleys\Directive\Field\Constraint\MinLength::class, 'You need more than %s chars'), ], ] ) diff --git a/src/Contracts/ConstraintInterface.php b/src/Contracts/ConstraintInterface.php index fdee9c7..a2273ff 100644 --- a/src/Contracts/ConstraintInterface.php +++ b/src/Contracts/ConstraintInterface.php @@ -3,12 +3,12 @@ namespace C0ntax\ParsleyBundle\Contracts; -use C0ntax\ParsleyBundle\Directive\Field\ConstraintErrorMessage; +use C0ntax\ParsleyBundle\Parsleys\Directive\Field\ConstraintErrorMessage; /** * Interface ConstraintInterface * - * @package C0ntax\ParsleyBundle\Directive\Field\Constraint + * @package C0ntax\ParsleyBundle\Parsleys\Directive\Field\Constraint */ interface ConstraintInterface extends DirectiveInterface { diff --git a/src/Factory/ConstraintFactory.php b/src/Factory/ConstraintFactory.php index 0a937fc..fc73669 100644 --- a/src/Factory/ConstraintFactory.php +++ b/src/Factory/ConstraintFactory.php @@ -4,7 +4,7 @@ namespace C0ntax\ParsleyBundle\Factory; use C0ntax\ParsleyBundle\Contracts\ConstraintInterface; -use C0ntax\ParsleyBundle\Directive\Field\Constraint as ParsleyConstraint; +use C0ntax\ParsleyBundle\Parsleys\Directive\Field\Constraint as ParsleyConstraint; use Symfony\Component\Form\Extension\Core\Type\BirthdayType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\DateType; @@ -16,7 +16,7 @@ /** * Class ConstraintFactory * - * @package C0ntax\ParsleyBundle\Directive\Field\Constraint + * @package C0ntax\ParsleyBundle\Parsleys\Directive\Field\Constraint */ class ConstraintFactory { diff --git a/src/Form/Extension/ParsleyTypeExtension.php b/src/Form/Extension/ParsleyTypeExtension.php index 5cd3287..5c72209 100644 --- a/src/Form/Extension/ParsleyTypeExtension.php +++ b/src/Form/Extension/ParsleyTypeExtension.php @@ -5,8 +5,8 @@ use C0ntax\ParsleyBundle\Contracts\ConstraintInterface; use C0ntax\ParsleyBundle\Contracts\DirectiveInterface; -use C0ntax\ParsleyBundle\Directive\Field\Generic; use C0ntax\ParsleyBundle\Factory\ConstraintFactory; +use C0ntax\ParsleyBundle\Parsleys\Directive\Field\Generic; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormInterface; diff --git a/src/Directive/Field/Constraint/AbstractComparison.php b/src/Parsleys/Directive/Field/Constraint/AbstractComparison.php similarity index 90% rename from src/Directive/Field/Constraint/AbstractComparison.php rename to src/Parsleys/Directive/Field/Constraint/AbstractComparison.php index ad769a1..462497e 100644 --- a/src/Directive/Field/Constraint/AbstractComparison.php +++ b/src/Parsleys/Directive/Field/Constraint/AbstractComparison.php @@ -1,12 +1,12 @@ Date: Sat, 27 Jan 2018 10:57:15 +0000 Subject: [PATCH 2/3] Added Removals --- README.md | 17 +++ src/Contracts/DirectiveInterface.php | 3 +- src/Contracts/ParsleyInterface.php | 16 +++ src/Contracts/RemoveInterface.php | 19 +++ src/Form/Extension/ParsleyTypeExtension.php | 108 ++++++++++++++++-- src/Parsleys/AbstractRemove.php | 57 +++++++++ src/Parsleys/RemoveParsleyConstraint.php | 27 +++++ src/Parsleys/RemoveSymfonyConstraint.php | 27 +++++ tests/Fixtures/Entity/TestRemovalEntity.php | 86 ++++++++++++++ tests/Fixtures/Form/Type/TestRemovalType.php | 69 +++++++++++ .../Extension/ParsleyTypeExtensionTest.php | 52 ++++++++- .../Parsleys/RemoveParsleyConstraintTest.php | 86 ++++++++++++++ .../Parsleys/RemoveSymfonyConstraintTest.php | 78 +++++++++++++ 13 files changed, 633 insertions(+), 12 deletions(-) create mode 100644 src/Contracts/ParsleyInterface.php create mode 100644 src/Contracts/RemoveInterface.php create mode 100644 src/Parsleys/AbstractRemove.php create mode 100644 src/Parsleys/RemoveParsleyConstraint.php create mode 100644 src/Parsleys/RemoveSymfonyConstraint.php create mode 100644 tests/Fixtures/Entity/TestRemovalEntity.php create mode 100644 tests/Fixtures/Form/Type/TestRemovalType.php create mode 100644 tests/Parsleys/RemoveParsleyConstraintTest.php create mode 100644 tests/Parsleys/RemoveSymfonyConstraintTest.php diff --git a/README.md b/README.md index 2324bfb..211a77e 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,23 @@ Let's assume that the Entity in the example above is out of your control. It's c *NOTE* The class passed to identify where to attache the error message is the ParsleyBundle one and not the Symfony one! +## Removals + +There may be occassions where you want the bridge between Symfony and Parsley enabled, but specific validations 'removed' from a form element. For example, in the case of [Group Sequences](https://symfony.com/doc/current/validation/sequence_provider.html) where there is no equivalent in Parsley. With removals, you can 'turn off' the Symfony Constraint and manually add your own custom Parsely validation. For example, say we wanted to have a Regex symfony validation on the server, but not on the client side: + +```php + $builder->add( + 'field', + TextType::class, + [ + 'constraints' => [new Regex(['pattern' => '/bla/]), + 'parsleys' => [new RemoveSymfonyConstraint(Regex::class)],] + ] + ); +``` + +There is also the ``RemoveParsleyConstraint()`` class that can be used to remove specific Parsley constrains. This is handy if you want to remove something that was auto-generated from a Symfony Constraint. + ## Rolling your own You can add your own parsley directives by simply implementing the ``DirectiveInterface``. The only requirement is that it passes back an array of attributes that will be injected into your form HTML. diff --git a/src/Contracts/DirectiveInterface.php b/src/Contracts/DirectiveInterface.php index 2698431..652271c 100644 --- a/src/Contracts/DirectiveInterface.php +++ b/src/Contracts/DirectiveInterface.php @@ -1,4 +1,5 @@ getAnnotatedConstraintsFromForm($form), - $this->getConstraintsFromForm($form) + $parsleys = $options[self::OPTION_NAME]; + + $symfonyConstraints = $this->removeFromConstraints( + array_merge( + $this->getAnnotatedConstraintsFromForm($form), + $this->getConstraintsFromForm($form) + ), + $this->getRemoveSymfonyConstraintsFromParsleys($parsleys) ); - $parsleyConstraints = array_merge( - $this->createParsleyConstraintsFromValidationConstraints($constraints, $form), - $options[self::OPTION_NAME] + $parsleyConstraints = $this->removeFromConstraints( + array_merge( + $this->createParsleyConstraintsFromValidationConstraints($symfonyConstraints, $form), + $this->getDirectivesFromParsleys($parsleys) + ), + $this->getRemoveParsleyConstraintsFromParsleys($parsleys) ); $this->addParsleyToView($view, $parsleyConstraints); @@ -76,13 +88,89 @@ public function getExtendedType(): string /** * @param OptionsResolver $resolver * @throws \Symfony\Component\OptionsResolver\Exception\AccessException + * @throws \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException */ public function configureOptions(OptionsResolver $resolver): void { - $resolver->setDefaults( - [ - self::OPTION_NAME => [], - ] + $resolver + ->setDefaults( + [ + self::OPTION_NAME => [], + ] + ) + ->addAllowedTypes(self::OPTION_NAME, 'array'); + } + + /** + * @param ParsleyInterface[] $parsleys + * @return DirectiveInterface[] + */ + private function getDirectivesFromParsleys(array $parsleys): array + { + $dir = array_values( + array_filter( + $parsleys, + function (ParsleyInterface $parsley) { + return $parsley instanceof DirectiveInterface; + } + ) + ); + + return $dir; + } + + /** + * @param ParsleyInterface[] $parsleys + * @return RemoveParsleyConstraint[] + */ + private function getRemoveParsleyConstraintsFromParsleys(array $parsleys): array + { + return array_values( + array_filter( + $parsleys, + function (ParsleyInterface $parsley) { + return $parsley instanceof RemoveParsleyConstraint; + } + ) + ); + } + + /** + * @param ParsleyInterface[] $parsleys + * @return RemoveSymfonyConstraint[] + */ + private function getRemoveSymfonyConstraintsFromParsleys(array $parsleys): array + { + return array_values( + array_filter( + $parsleys, + function (ParsleyInterface $parsley) { + return $parsley instanceof RemoveSymfonyConstraint; + } + ) + ); + } + + /** + * @param \Symfony\Component\Validator\Constraint[]|ConstraintInterface[] $constraints + * @param RemoveInterface[] $removals + * @return \Symfony\Component\Validator\Constraint[]|ConstraintInterface[] + */ + private function removeFromConstraints(array $constraints, array $removals): array + { + return array_values( + array_filter( + $constraints, + function ($constraint) use ($removals) { + foreach ($removals as $removal) { + if ($removal->getClassName() === get_class($constraint)) { + return false; + } + } + + return true; + } + ) ); } diff --git a/src/Parsleys/AbstractRemove.php b/src/Parsleys/AbstractRemove.php new file mode 100644 index 0000000..0363778 --- /dev/null +++ b/src/Parsleys/AbstractRemove.php @@ -0,0 +1,57 @@ +setClassName($className); + } + + /** + * @return string + */ + public function getClassName(): string + { + return $this->className; + } + + /** + * @param string $className + * @return AbstractRemove + */ + private function setClassName(string $className): AbstractRemove + { + $this->className = $className; + + return $this; + } +} diff --git a/src/Parsleys/RemoveParsleyConstraint.php b/src/Parsleys/RemoveParsleyConstraint.php new file mode 100644 index 0000000..79ea8f0 --- /dev/null +++ b/src/Parsleys/RemoveParsleyConstraint.php @@ -0,0 +1,27 @@ +id1; + } + + /** + * @param null|string $id1 + * @return TestRemovalEntity + */ + public function setId1(?string $id1) + { + $this->id1 = $id1; + + return $this; + } + + /** + * @return null|string + */ + public function getId2(): ?string + { + return $this->id2; + } + + /** + * @param null|string $id2 + * @return TestRemovalEntity + */ + public function setId2(?string $id2) + { + $this->id2 = $id2; + + return $this; + } + + /** + * @return null|string + */ + public function getId3(): ?string + { + return $this->id3; + } + + /** + * @param null|string $id3 + * @return TestRemovalEntity + */ + public function setId3(?string $id3) + { + $this->id3 = $id3; + + return $this; + } +} diff --git a/tests/Fixtures/Form/Type/TestRemovalType.php b/tests/Fixtures/Form/Type/TestRemovalType.php new file mode 100644 index 0000000..670031a --- /dev/null +++ b/tests/Fixtures/Form/Type/TestRemovalType.php @@ -0,0 +1,69 @@ +add( + 'id1', + TextType::class, + [ + 'constraints' => new Regex('/[e]+/'), + 'parsleys' => [ + new MinLength(2, 'You need more than %s chars'), + new ConstraintErrorMessage(MaxLength::class, 'You need less than %s chars'), + new RemoveSymfonyConstraint(Regex::class), + new RemoveSymfonyConstraint(Length::class), + ], + ] + ) + ->add( + 'id2', + TextType::class, + [ + 'constraints' => new Regex('/[e]+/'), + 'parsleys' => [ + new MinLength(2, 'You need more than %s chars'), + new ConstraintErrorMessage(MaxLength::class, 'You need less than %s chars'), + new RemoveParsleyConstraint(MinLength::class), + ], + ] + ) + ->add( + 'id3', + TextType::class, + [ + 'constraints' => new Regex('/[e]+/'), + 'parsleys' => [ + new MinLength(2, 'You need more than %s chars'), + new ConstraintErrorMessage(MaxLength::class, 'You need less than %s chars'), + new RemoveSymfonyConstraint(Regex::class), + new RemoveSymfonyConstraint(Length::class), + new RemoveParsleyConstraint(MinLength::class), + ], + ] + ) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(['data_class' => TestRemovalEntity::class]); + } +} diff --git a/tests/Form/Extension/ParsleyTypeExtensionTest.php b/tests/Form/Extension/ParsleyTypeExtensionTest.php index 0455076..d9a5cca 100644 --- a/tests/Form/Extension/ParsleyTypeExtensionTest.php +++ b/tests/Form/Extension/ParsleyTypeExtensionTest.php @@ -2,13 +2,14 @@ namespace C0ntax\ParsleyBundle\Tests\Form\Extension; +use C0ntax\ParsleyBundle\Tests\Fixtures\Form\Type\TestRemovalType; use C0ntax\ParsleyBundle\Tests\Fixtures\Form\Type\TestType; use C0ntax\ParsleyBundle\Tests\Form\AbstractTypeTestCase; use Symfony\Component\Form\FormInterface; class ParsleyTypeExtensionTest extends AbstractTypeTestCase { - public function testSomething() + public function testWithoutRemoval() { $form = $this->createForm(); $form->submit(['id' => 9, 'email' => 'sausage', 'string' => str_repeat('a', 51)]); @@ -29,6 +30,46 @@ public function testSomething() ); } + public function testRemoval() + { + $form = $this->createRemovalForm(); + $form->submit(['id1' => 9, 'id2' => 10, 'id3' => 11]); + + $view = $form->createView(); + + // Symfony constraint removal + self::assertEquals( + [ + 'data-parsley-trigger' => 'focusout', + 'data-parsley-minlength' => '2', + 'data-parsley-minlength-message' => 'You need more than %s chars', + 'data-parsley-maxlength-message' => 'You need less than %s chars', // TODO Make this not be the case! + ], + $view->children['id1']->vars['attr'] + ); + + // Parsley constraint removal + self::assertEquals( + [ + 'data-parsley-trigger' => 'focusout', + 'data-parsley-pattern-message' => 'This value is not valid.', + 'data-parsley-pattern' => '/[e]+/', + 'data-parsley-maxlength-message' => 'You need less than %s chars', + 'data-parsley-maxlength' => '10', + ], + $view->children['id2']->vars['attr'] + ); + + // Both constraint removal + self::assertEquals( + [ + 'data-parsley-maxlength-message' => 'You need less than %s chars', // TODO Make this not be the case! + 'data-parsley-trigger' => 'focusout', // TODO Make this not be the case! + ], + $view->children['id3']->vars['attr'] + ); + } + protected function getParsleyTypeConfig() { return [ @@ -48,4 +89,13 @@ private function createForm(): FormInterface return $this->factory->create(TestType::class); } + /** + * @return FormInterface + * @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + private function createRemovalForm(): FormInterface + { + return $this->factory->create(TestRemovalType::class); + } + } diff --git a/tests/Parsleys/RemoveParsleyConstraintTest.php b/tests/Parsleys/RemoveParsleyConstraintTest.php new file mode 100644 index 0000000..94824f4 --- /dev/null +++ b/tests/Parsleys/RemoveParsleyConstraintTest.php @@ -0,0 +1,86 @@ + ParsleyConstraint\Email::class, + ], + [ + 'className' => ParsleyConstraint\Length::class, + ], + [ + 'className' => ParsleyConstraint\Max::class, + ], + [ + 'className' => ParsleyConstraint\MaxLength::class, + ], + [ + 'className' => ParsleyConstraint\Min::class, + ], + [ + 'className' => ParsleyConstraint\MinLength::class, + ], + [ + 'className' => ParsleyConstraint\Pattern::class, + ], + [ + 'className' => ParsleyConstraint\Range::class, + ], + [ + 'className' => ParsleyConstraint\Required::class, + ], + ]; + } + + public function getInvalidClassNames() + { + return [ + [ + 'className' => ConstraintErrorMessage::class, + ], + [ + 'className' => Generic::class, + ], + [ + 'className' => SymfonyConstraint\Email::class, + ], + [ + 'className' => SymfonyConstraint\Length::class, + ], + [ + 'className' => SymfonyConstraint\Regex::class, + ], + ]; + } +} diff --git a/tests/Parsleys/RemoveSymfonyConstraintTest.php b/tests/Parsleys/RemoveSymfonyConstraintTest.php new file mode 100644 index 0000000..5f34eca --- /dev/null +++ b/tests/Parsleys/RemoveSymfonyConstraintTest.php @@ -0,0 +1,78 @@ + SymfonyConstraint\Email::class, + ], + [ + 'className' => SymfonyConstraint\Length::class, + ], + [ + 'className' => SymfonyConstraint\Regex::class, + ], + ]; + } + + public function getInValidClassNames() + { + return [ + [ + 'className' => ParsleyConstraint\Email::class, + ], + [ + 'className' => ParsleyConstraint\Length::class, + ], + [ + 'className' => ParsleyConstraint\Max::class, + ], + [ + 'className' => ParsleyConstraint\MaxLength::class, + ], + [ + 'className' => ParsleyConstraint\Min::class, + ], + [ + 'className' => ParsleyConstraint\MinLength::class, + ], + [ + 'className' => ParsleyConstraint\Pattern::class, + ], + [ + 'className' => ParsleyConstraint\Range::class, + ], + [ + 'className' => ParsleyConstraint\Required::class, + ], + ]; + } +} From 39ee681f4137a434e89cc6da9bde512ca90c9c11 Mon Sep 17 00:00:00 2001 From: Dan Burzynski Date: Sat, 27 Jan 2018 10:58:11 +0000 Subject: [PATCH 3/3] Feature add (strictly a breaking change, but since I'm pretty sure I'm the only one using this library at the moment...) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b631b63..2806f4c 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "c0ntax/parsley-bundle", - "version": "0.4.0", + "version": "0.5.0", "type": "symfony-bundle", "description": "A bridge between Symfony and Parsley.js", "license": "Apache-2.0",