-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[2.2] [WIP][Validator][Constraints][Collection] walk constraints by groups #4453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* | ||
* @param array $constraints | ||
*/ | ||
public function setConstraints(array $constraints) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The indention is wrong here, you must use 4 spaces not tabs.
And a little lower there is a space missing after the comma.
$this->constraints = $constraints; | ||
$constraintsByGroup = array(); | ||
|
||
array_walk($constraints,function($constraint) use(&$constraintsByGroup){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after the coma, after use
and after the last closing parenthesis.
Missing space after the closing parenthesis below.
You should fix all failing tests. Good luck! |
Thanks, the tests have been fixed and I appended a new test for walking by group. |
ping @bschussek |
@bschussek ping |
I've been just bitten by this one. I was expecting that Valid constraint would pass the current group a onto the child object of a collection when validating, but it's reset to the Default group... |
+1 for the functionality as I also have the problem. But the CS should be fixed first. |
Unfortunately this issue is much more complicated to solve than this PR does. Consider the following cases: (a) Explicit groups on the Collection constraint /**
* @Collection(
* fields = {
* "foo" = @NotNull,
* "bar" = @Optional({
* @NotBlank,
* @Range(min = 3)
* })
* },
* groups = "MyGroup"
* )
*/ (b) Explicit groups on /**
* @Collection({
* "foo" = @NotNull,
* "bar" = {
* @Optional(
* constraints = @NotBlank,
* groups = "MyGroup"
* ),
* @Required(@Range(min = 3))
* }
* })
*/ (c) Explicit groups on inner constraints /**
* @Collection({
* "foo" = @NotNull(groups = "MyGroup"),
* "bar" = @Optional({
* @NotBlank,
* @Range(min = 3, groups = "MyGroup")
* })
* })
*/ We need to find out first which exact constraints to validate in either of the cases (a), (b) and (c) and any possible combination of the three approaches. This is not trivial. |
fix ValueMetadata::setConstraints to set instead of append
The latest commit adjusts for changes that have been made during the past year, along with considering the Optional constraint by group context. A unit test has been appended for the latter. |
ping @bschussek |
1 similar comment
ping @bschussek |
See #9888 for a description about how groups should be handled. |
Thanks for the info, that looks like a promising enhancement. As far as I remember, it's been a while, the purpose of this PR was to add group support for values (arrays) to the existing collection constraint, which does support validation by group for objects. |
…mozart) This PR was merged into the 2.5-dev branch. Discussion ---------- [WIP][Validator] New NodeTraverser implementation | Q | A | ------------- | --- | Bug fix? | yes | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | TODO | License | MIT | Doc PR | TODO This PR is ready for review. #### Todo - [x] Test extensively to avoid regressions - [x] Test more extensively - [x] Finish inline documentation - [x] Provide a layer to choose the desired API through ValidatorBuilder - [x] Provide a layer to choose the desired API through FrameworkBundle - [x] Update UPGRADE file - [x] Update CHANGELOG - [ ] Update user documentation #### Goal The goal of this PR is to be able to fix the following tickets: - [x] #6138 Simplify adding of constraint violations - [x] #7146 Support group sequences in Validator API - [x] #7432 Poorly implemented Visitor Pattern - [x] #8617 Control traversal on class level - [x] #9888 Improve support for collection validation (PR: #9988) The following tickets are probably fixed, but require testing first: - [ ] #8376 Using validation_group causes error message to display multiple times - [ ] #9939 GroupSequences still execute next group if first fail Of course, full backwards compatibility **must** be guaranteed. Other tickets I want to fix in follow-up PRs: * #3622 Constraints\Valid does not respect "groups" option * #4453 walk constraints by groups * #7700 Propagate implicit group names in constraints * #9051 Always ask value event if field isn't in the validating group * #10163 poor collection validation test coverage * #10221 TypeValidator does not enforce desired type when value is NULL * #10495 Class Valid Constraint can't be used on a Form Type #### In a nutshell The implementation removes the Visitor pattern, which was implemented badly. I tried fixing it via a NodeTraverser/NodeVisitor implementation, but performance degraded too much so I decided to remove the pattern altogether. A couple of new features and bug fixes are possible thanks to the new implementation. See below for details. #### PHP Versions PHP 5.3.8 and older does not allow to implement two different interfaces which both contain a method with the same name. This is used in the compatibility layer that supports both the old and the new API. For this reason, the compatibility layer is disabled on PHP < 5.3.9. Older PHP versions need to decide on the old API or the new API (without compatibility layer). #### Choosing the API Version The API version can be specified by one of `Validation::API_VERSION_2_4`, `Validation::API_VERSION_2_5` and `Validation::API_VERSION_2_5_BC` to `setApiVersion()` when building the validator: ```php // Old implementation $validator = Validation::createValidatorBuilder() ->setApiVersion(Validation::API_VERSION_2_4) ->getValidator(); // New implementation with BC API // Does not work on PHP < 5.3.9 $validator = Validation::createValidatorBuilder() ->setApiVersion(Validation::API_VERSION_2_5) ->getValidator(); // New implementation without BC API $validator = Validation::createValidatorBuilder() ->setApiVersion(Validation::API_VERSION_2_5_BC) ->getValidator(); ``` #### Features ##### Constraint validation as first-class citizen The new API merges `validateValue()` and `validate()`. The idea is that the validation of values against constraints should be as simple as possible. Object validation is a special case where an object is tested against the `Valid` constraint. A backwards compatibility layer is provided to use both `validate()` and `validateValue()` with the old signature. ```php // Validate against explicit constraints $violations = $validator->validate($firstName, array( new NotNull(), new Length(array('min' => 3)), )); // Validate against metadata $violations = $validator->validate($object); // Same, more expressive notation $violations = $validator->validate($object, new Valid()); // Validate each entry against its metadata $violations = $validator->validate($array); ``` ##### Aggregate violations It is now possible to call the methods of the validator multiple times and aggregate the violations to a common violation list. To do so, call `startContext()`, execute the calls and call `getViolations()` in the end to retrieve the violations: ```php $violations = $validator->startContext() ->validate($title, new NotNull()) ->validate($text, new NotNull()) ->validate($author->getName(), new NotNull()) ->getViolations() ``` Most of the time, you will want to specify a property path for each validation. Use the method `atPath()` for that: ```php $violations = $validator->startContext() ->atPath('title')->validate($title, new NotNull()) ->atPath('text')->validate($text, new NotNull()) ->atPath('author.name')->validate($author->getName(), new NotNull()) ->getViolations() ``` ##### Control the context of nested validations In Symfony <= 2.4, you can validate objects or constraints from within a constraint validator like this: ```php $this->context->validate($object); $this->context->validateValue($value, new Length(array('min' => 3))); ``` The validations will run and all violations will be added to the current context. This way, it is impossible though to validate something, inspect the result and then decide what kind of violations to add to the context. This is needed, for example, for the `Some` constraint (proposed in #9888), which should succeed if any of the validated values did *not* generate violations. For this reason, the new context API features a method `getValidator()`. This method returns the validator instance, you can use it to validate anything in a new context (as the validator always does): ```php $validator = $this->context->getValidator(); $violations = $validator->validate($object); if (count($violations) > 0) { $this->context->addViolation('The validation did not pass'); } ``` You can also explicitly start a new context: ```php $validator = $this->context->getValidator(); $violations = $validator->startContext() ->atPath('title')->validate($title, new NotNull()) ->atPath('text')->validate($text, new NotNull()) ->getViolations() ``` If you want to execute the validation in the current context, use the `inContext()` method of the validator instead: ```php // violations are added to $this->context $validator->inContext($this->context) ->atPath('title')->validate($title, new NotNull()) ->atPath('text')->validate($text, new NotNull()) ; ``` With this feature, #9888 (especially the PR for it: #9988) can be finished. ##### Custom group sequences (#7146) It is now possible to pass `GroupSequence` instances whenever you can pass a group to the validator. For example: ```php $violations = $validator->validate($object, new Valid(), new GroupSequence('Basic', 'Strict')); ``` Or in the context of the Form component: ```php $form = $this->createForm(new BlogType(), new Blog(), array( 'validation_groups' => new GroupSequence('Basic', 'Strict'), )); ``` ##### Constraint violation builders (#6138) The API for adding constraint violations was simplified: ```php $this->context->addViolation('message', array('param' => 'value')); // or $this->context->buildViolation('message') ->atPath('property') ->setParameter('param', 'value') ->setTranslationDomain('validation_strict') ->addViolation(); ``` ##### Control traversal at class level (#8617) Currently, it is possible whether to traverse a `Traversable` object or not in the `Valid` constraint: ```php /** * @Assert\Valid(traverse=true) */ private $tags = new TagList(); ``` (actually, `true` is the default) In this way, the validator will iterate the `TagList` instance and validate each of the contained objects. You can also set "traverse" to `false` to disable iteration. What if you want to specify, that `TagList` instances should always (or never) be traversed? That's currently not possible. With this PR, you can do the following: ```php /** * @Assert\Traverse(false) */ class TagList implements \IteratorAggregate { // ... } ``` #### Follow-up features Features of the follow-up PRs will be described directly there. #### Backwards compatibility I implemented a new `AbstractValidatorTest` which tests both the old and the new implementation for compatibility. I still want to extend this test to make sure we don't introduce any regressions. Almost none of the existing classes were modified (or only slightly). If users depend on the current (now "legacy") implementation, they will have the choice to continue using it until 3.0 (of course, without the new features). #### Your task Congrats, you made it till here :) If you have time, please skim over the code and give me feedback on the overall implementation and the class/method names. Again, no feedback on details yet, there are quite a few areas in the code that are still work in progress. Thanks, Bernhard [1] That means that only the nodes from the root of the graph until the currently validated node are held in memory. Commits ------- ca6a722 [Validator] Converted `@deprecate` doc comment into regular doc comment 68d8018 [Validator] Documented changes in the UPGRADE files b1badea [Validator] Fixed failing CsrfFormLoginTest 0bfde4a [Validator] Fixed misnamed method calls in FrameworkExtension 3dc2b4d [Validator] Made "symfony/property-access" an optional dependency c5629bb [Validator] Added getObject() to ExecutionContextInterface 9b204c9 [FrameworkBundle] Implemented configuration to select the desired Validator API 0946dbe [Validator] Adapted CHANGELOG 1b111d0 [Validator] Fixed typos pointed out by @cordoval 7bc952d [Validator] Improved inline documentation of RecursiveContextualValidator 166d71a [Validator] Removed unused property 90c27bb [Validator] Removed traverser implementation 3183aed [Validator] Improved performance of cache key generation 029a716 [Validator] Moved logic of replaceDefaultGroup() to validateNode() 2f23d97 [Validator] Reduced number of method calls on the execution context 73c9cc5 [Validator] Optimized performance by calling spl_object_hash() only once per object 94ef21e [Validator] Optimized use statements 1622eb3 [Validator] Fixed reference to removed class in ValidatorBuilder be508e0 [Validator] Merged DefaultGroupReplacingVisitor and ContextUpdateVisitor into NodeValidationVisitor 50bb84d [Validator] Optimized RecursiveContextualValidator eed29d8 [Validator] Improved performance of *ContextualValidator::validate() 5c479d8 [Validator] Simplified validateNodeForGroup eeed509 [Validator] Improved phpdoc of RecursiveValidator 274d4e6 [Validator] Changed ValidatorBuilder to always use LegacyExecutionContext 38e26fb [Validator] Decoupled RecursiveContextualValidator from Node 23534ca [Validator] Added a recursive clone of the new implementation for speed comparison f61d31e [Validator] Fixed grammar 886e05e [Validator] Removed unused use statement 93fdff7 [Validator] The supported API versions can now be passed to the ValidatorBuilder 987313d [Validator] Improved inline documentation of the violation builder 79387a7 [Validator] Improved inline documentation of the metadata classes 01ceeda [Validator] Improved test coverage of the Traverse constraint 9ca61df [Validator] Improved inline documentation of CascadingStrategy and TraversalStrategy 524a953 [Validator] Improved inline documentation of the validators 9986f03 [Validator] Added inline documentation for the PropertyPath utility class be7f055 [Validator] Visitors may now abort the traversal by returning false from beforeTraversal() 299c2dc [Validator] Improved test coverage and prevented duplicate validation of constraints 186c115 [Validator] Improved test coverage of NonRecursiveNodeTraverser 822fe47 [Validator] Completed inline documentation of the Node classes and the NodeTraverser dbce5a2 [Validator] Updated outdated doc blocks 8558377 [Validator] Added deprecation notes e8fa15b [Validator] Fixed the new validator API under PHP < 5.3.9 2936d10 [Validator] Removed unused use statement 6fc6ecd [Validator] Fixed tests under PHP<5.3.9 778ec24 [Validator] Removed helper class Traversal 76d8c9a [Validator] Fixed typos 4161371 [Validator] Removed unused use statements aeb6822 [Validator] Improved visitor names 08172bf [Validator] Merged validate(), validateObject() and validateObjects() to simplify usage 51197f6 [Validator] Made traversal of Traversables consistent 117b1b9 [Validator] Wrapped collections into CollectionNode instances 94583a9 [Validator] Changed NodeTraverser to traverse nodes iteratively, not recursively cf1281f [Validator] Added "Visitor" suffix to all node visitors 230f2a7 [Validator] Fixed exception message e057b19 [Validator] Decoupled ContextRefresher from ExecutionContext e440690 [Validator] Renamed validateCollection() to validateObjects() df41974 [Validator] Changed context manager to context factory 26eafa4 [Validator] Removed unused use statements bc29591 [Validator] Clearly separated classes supporting the API <2.5/2.5+ a3555fb [Validator] Fixed: Objects are not traversed unless they are instances of Traversable 2c65a28 [Validator] Completed test coverage and documentation of the Node classes 9c9e715 [Validator] Completed documentation of GroupManagerInterface 1e81f3b [Validator] Finished test coverage and documentation of ExecutionContextManager feb3d6f [Validator] Tested the validation in a separate context 718601c [Validator] Changed validateValue() to validate() in the new API ee1adad [Validator] Implemented handling of arrays and Traversables in LegacyExecutionContext::validate() 09f744b [Validator] Implemented BC traversal of traversables through validate() 297ba4f [Validator] Added a note why scalars are passed to cascadeObject() in NodeTraverser 9b07b0c [Validator] Implemented BC validation of arrays through validate() 405a03b [Validator] Updated deprecation notes in GroupSequence 499b2bb [Validator] Completed test coverage of ExecutionContext adc1437 [Validator] Fixed failing tests 4ea3ff6 [Validator] Finished inline documentation of ExecutionContext[Interface] f6b7288 [Validator] Removed unused use statement 8318286 [Validator] Completed GroupSequence implementation 5fbf848 [Validator] Added note about Callback constraint to CHANGELOG c1b1e03 [Validator] Added TODO reminder 8ae68c9 [Validator] Made tests green (yay!) 680f1ee [Validator] Renamed $params to $parameters 321d5bb [Validator] Throw exception if ObjectInitializer is constructed without visitors 1156bde [Validator] Extracted code for group sequence resolving into GroupSequenceResolver b1a9477 [Validator] Added ObjectInitializer visitor 7e3a41d [Validator] Moved visitors to NodeVisitor namespace a40189c [Validator] Decoupled the new classes a bit a6ed4ca [Validator] Prototype of the traverser implementation 25cdc68 [Validator] Refactored ValidatorTest and ValidationVisitorTest into an abstract validator test class
Replaced by #11505. |
…onstraints (webmozart) This PR was merged into the 2.6-dev branch. Discussion ---------- [Validator] Fixed group handling in composite constraints | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#4453 | License | MIT | Doc PR | - With this PR, it finally becomes possible to set the groups of nested constraints in All and Collection: ```php /** * @Assert\Collection({ * "foo" = @Assert\NotNull(groups = "Strict"), * "bar" = @Assert\NotBlank(groups = "Regular"), * }) */ private $attr; ``` The group logic is implemented as described in [this comment](symfony#4453 (comment)). This PR supports the implementation of symfony#9888 which should now become fairly simple. Commits ------- f6c070d [Validator] Fixed group handling in composite constraints
Bug fix: no
Feature addition: yes
Backwards compatibility break: not sure
Symfony2 tests pass: no
Fixes the following tickets: -
Todo: -
License of the code: MIT
Documentation PR: -
This change to the Validator Collection Constraint allows for walking by group. Consider the following use case example for validating postal code lengths:
In the above code, the Collection constraint would walk all of the constraints for the field 'postal'. This has been changed so only the group(s) specified would be applied, which I beleive is how groups work when validating an object's properties. My concern is that some of the current Collection Constraint tests fail, since they use 'MyGroup' for the validation. The constraint within the test collection belongs to the 'Default' group and is not walked as the test intends. I'm not sure if this is the desired behavior, or if the tests can merely be changed to pass the desired group, as illustrated in the test case diff.