-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Validator] Dynamic constraints #3114
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 Mapping\ClassMetadata | ||
*/ | ||
public function getConstraints(ClassMetadata $metadata); |
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.
GETConstraints? This function does not seem to return anything.
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.
Yes, you're right. Maybe something like "buildConstraints" would be better, but I'm not sure if it is the right name. What's your suggestion?
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.
I believe the coding standard would state that you do not need public
for interfaces.
Related to #1151 |
👍 |
I think this is a good addition. I think we still have a naming problem though. When constraints are loaded using a static method, the default name for the loader method is Solution (1): Rename the method in your interface to
Solution (2): Rename the default method name in
@fabpot: Are we allowed to break BC here? If not, we should probably stick to (1). |
I would prefer to not break BC if possible. |
So "loadDynamicValidatorMetadata" would be the best solution? |
Sounds fine for me based on @bschussek's comment. |
Commits ------- 92f820a Renamed registerConstraints to loadDynamicValidatorMetadata dd12ff8 CS fix, getConstraints renamed 09c1911 [Validator] Improved dynamic constraints 54cb6e4 [Validator] Added dynamic constraints Discussion ---------- [Validator] Dynamic constraints Bug fix: no Feature addition: yes Backwards compatibility break: no Symfony2 tests pass: yes By now the Validator component is based on a per-class configuration of constraints, but in some cases it might be neccessary to add new constraints dynamically at runtime. This pull request adds a "ConstraintProviderInterface" to the Validator component. If an object is validated that implements this interface the method "getConstraints" is used to add dynamic constraints: class User implements ConstraintProviderInterface { protected $isPremium; protected $paymentInformation; public function getConstraints(ClassMetadata $metadata) { if ($this->isPremium) { $metadata->addPropertyConstraint('paymentInformation', new NotBlank()); } } } --------------------------------------------------------------------------- by alexandresalome at 2012-01-15T11:20:04Z Related to #1151 --------------------------------------------------------------------------- by canni at 2012-01-16T09:22:28Z :+1: --------------------------------------------------------------------------- by bschussek at 2012-01-16T12:32:44Z I think this is a good addition. I think we still have a naming problem though. When constraints are loaded using a static method, the default name for the loader method is `loadValidatorMetadata`. Since the method for dynamic constraint loading is basically the same, I think the two names should be related. Solution (1): Rename the method in your interface to `loadDynamicValidatorMetadata`. Ugly and long. class MyClass implements ConstraintProviderInterface { public static loadValidatorMetadata(ClassMetadata $metadata) ... public loadDynamicValidatorMetadata(ClassMetadata $metadata) ... } Solution (2): Rename the default method name in `StaticMethodLoader` to `registerConstraints` and adjust the docs. Breaks BC. class MyClass implements ConstraintProviderInterface { public static registerConstraints(ClassMetadata $metadata) ... public registerDynamicConstraints(ClassMetadata $metadata) ... } @fabpot: Are we allowed to break BC here? If not, we should probably stick to (1). --------------------------------------------------------------------------- by fabpot at 2012-01-16T12:36:14Z I would prefer to not break BC if possible. --------------------------------------------------------------------------- by blogsh at 2012-01-16T15:25:46Z So "loadDynamicValidatorMetadata" would be the best solution? --------------------------------------------------------------------------- by althaus at 2012-01-17T13:39:19Z >So "loadDynamicValidatorMetadata" would be the best solution? Sounds fine for me based on @bschussek's comment.
I'm sorry to be late to the party, but I fear I have to put my veto on this PR. First of all, the implementation is flawed. Dynamic constraints should not be loaded in the Validator, but in the GraphWalker. Second, this will circumvent any caching (of course) and I would like to see if a solution like in Hibernate 4.2 wouldn't fit better to solve this use case. |
I could move the code of this PR into the GraphWalker. |
I've reverted the merge. |
@blogsh: Feel free to do so! I'm not sure whether it's good to port it to PHP exactly as it is in Java though. Maybe it makes sense to allow classes to implement the dynamic group sequence provider interface themselves. |
I thought you just wanted to use the "idea" behind that "dynamic group sequence provider". As I see it it's only an utility that allows the user to specify which validation groups should be used and in which order. |
No, you can already define group sequences on entities. The idea is to do so dynamically, depending on the entity's state. Adding constraints during runtime sounds nice, I don't know though if this results in a performance penalty because it totally circumvents any potential caching. |
Commits ------- 411a0cc [Validator] Added GroupSequenceProvider to changelog 815c769 [Validator] Renamed getValidationGroups to getGroupSequence d84a2e4 [Validator] Updated test expectations 9f2310b [Validator] Fixed typos, renamed hasGroupSequenceProvider e0d2828 [Validator] GroupSequenceProvider tests improved, configuration changed c3b04a3 [Validator] Changed GroupSequenceProvider implementation 6c4455f [Validator] Added GroupSequenceProvider Discussion ---------- [Validator] Added GroupSequenceProvider Bug fix: no Feature addition: yes Backwards compatibility break: no Symfony2 tests pass:  As discussed in #3114 I implemented the "GroupSequenceProvider" pattern for the validator component. It allows the user to select certain validation groups based on the current state of an object. Here is an example: /** * @Assert\GroupSequenceProvider("UserGroupSequnceProvider") */ class User { /** * @Assert\NotBlank(groups={"Premium"}) */ public function getAddress(); public function hasPremiumSubscription(); } class UserGroupSequenceProvider implements GroupSequenceProviderInterface { public function getValidationGroups($user) { if ($user->hasPremiumSubscription()) { return array('User', 'Premium'); } else { return array('User'); } } } With this patch there are two mechanisms to define the group sequence now. Either you can use @GroupSequence to define a static order of validation groups or you can use @GroupSequenceProvider to create dynamic validation group arrays. The ClassMetadata therefore has methods now which implement quite similar things. The question is whether it would make sense to interpret the static group sequence as a special case and create something like a DefaultGroupSequenceProvider or StaticGroupSequenceProvider which is assigned by default. This would cause a BC break inside the validator component. --------------------------------------------------------------------------- by bschussek at 2012-01-28T13:39:54Z I like the implementation, but I think we should differ a little bit from Java here. 1. `GroupSequenceProviderInterface` should be implemented by the domain classes themselves (`User`), not by a separate class. 2. As such, the parameter `$object` from `getValidationGroups($object)` can be removed 3. `ClassMetadata::setGroupSequenceProvider()` should accept a boolean to activate/deactivate this functionality. Also the check for the interface (does the underlying class implement it?) should be done here Apart from that, special cases need to be treated: * A definition of a group sequence and a group sequence provider in the same `ClassMetadata` should not be allowed. Either of them must not be set. * Metadata loaders must take care of settings made by parent classes. If `Animal` is extended by `Dog`, `Animal` defines a group sequence (or group sequence provider) and `Dog` a group sequence provider (or group sequence), only the setting of `Dog` should apply --------------------------------------------------------------------------- by blogsh at 2012-01-28T21:25:37Z Changes of the latest commit: - GroupSequenceProviderInterface has to be implemented by the domain class - The annotation/configuration options let the user define whether the provider is activated or not (is this neccessary at all?) - An error is thrown if the user wants to use static group sequences and the provider simultaneously At the moment neither the static group sequence nor the provider is inherited from parent classes or interfaces. I don't know if it would make sense to enable this feature. There could be problems if a user wants to define a static group sequence in the parent class and a sequence provider in the child class. --------------------------------------------------------------------------- by bschussek at 2012-01-30T13:07:04Z > There could be problems if a user wants to define a static group sequence in the parent class and a sequence provider in the child class. In this case, the setting in the child class should override the setting of the parent class. But we can leave this open for now. As it seems, [this issue is unresolved in Hibernate as well](https://hibernate.onjira.com/browse/HV-467). --------------------------------------------------------------------------- by blogsh at 2012-01-30T22:54:41Z Okay, finally I managed to upload the latest commit. If you got a bunch of notifications or so I'm sorry, but I had to revert some accidental changes in the commit :( I've rewritten the tests and have removed the "active" setting in the XML configuration. --------------------------------------------------------------------------- by blogsh at 2012-02-02T15:24:01Z Okay, typos are fixed now and `hasGroupSequenceProvider` has been renamed to `isGroupSequenceProvider`. I also had to adjust some tests after the rebase with master. --------------------------------------------------------------------------- by bschussek at 2012-02-03T09:25:19Z Looks good. @fabpot 👍 --------------------------------------------------------------------------- by fabpot at 2012-02-03T09:46:52Z Can you add a note in the CHANGELOG before I merge? Thanks. --------------------------------------------------------------------------- by blogsh at 2012-02-09T12:31:27Z @fabpot done
For anyone reading this, the |
@jgornick Is there another issue about that? I'd like to know why this was removed as I'm trying to figure out what's the best way for implementing conditional validation for Drupal, see https://drupal.org/node/2105797#comment-7937693 |
@fago The only two options that I know of are the callback validator constraint and the group sequence providers. I started off my using the group sequence validators approach then went to move all of my contextual validations in the callback validator. |
Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
By now the Validator component is based on a per-class configuration of
constraints, but in some cases it might be neccessary to add new constraints
dynamically at runtime.
This pull request adds a "ConstraintProviderInterface" to the Validator component. If an object is validated that implements this interface the method "getConstraints" is used to add dynamic constraints: