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

[Validator] Added GroupSequenceProvider #3199

Merged
merged 7 commits into from Feb 9, 2012

Conversation

Projects
None yet
5 participants
@sebhoerl
Contributor

sebhoerl commented Jan 27, 2012

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.

@webmozart

This comment has been minimized.

Contributor

webmozart commented Jan 28, 2012

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
@sebhoerl

This comment has been minimized.

Contributor

sebhoerl commented Jan 28, 2012

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.

@webmozart

This comment has been minimized.

Contributor

webmozart commented Jan 30, 2012

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.

@webmozart

View changes

src/Symfony/Component/Validator/Constraints/GroupSequenceProvider.php Outdated
* True if the group sequence provider should be used
* @var boolean
*/
public $active;

This comment has been minimized.

@webmozart

webmozart Jan 30, 2012

Contributor

We don't need this property.

@webmozart

View changes

src/Symfony/Component/Validator/Mapping/ClassMetadata.php Outdated
*/
public function setGroupSequenceProvider($active)
{
if ($this->hasGroupSequenceProvider()) {

This comment has been minimized.

@webmozart

webmozart Jan 30, 2012

Contributor

should be hasGroupSequence()

This comment has been minimized.

@webmozart

webmozart Jan 30, 2012

Contributor

a test case is missing for this case

@webmozart

View changes

tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php Outdated
$metadata = new ClassMetadata('stdClass');
try {
$metadata->setGroupSequenceProvider(true);

This comment has been minimized.

@webmozart

webmozart Jan 30, 2012

Contributor

this test should be extracted to a separate test method

@webmozart

View changes

src/Symfony/Component/Validator/Mapping/ClassMetadata.php Outdated
@@ -247,6 +249,10 @@ public function getConstrainedProperties()
*/
public function setGroupSequence(array $groups)
{
if ($this->hasGroupSequenceProvider()) {
throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider');

This comment has been minimized.

@webmozart

webmozart Jan 30, 2012

Contributor

a test case is missing for this case

@sebhoerl

This comment has been minimized.

Contributor

sebhoerl commented Jan 30, 2012

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.

@@ -54,6 +54,10 @@ public function loadClassMetadata(ClassMetadata $metadata)
if (isset($this->classes[$metadata->getClassName()])) {
$yaml = $this->classes[$metadata->getClassName()];
if (isset($yaml['group_sequence_provider'])) {

This comment has been minimized.

@sebhoerl

sebhoerl Jan 30, 2012

Contributor

Should I use array_key_exists here? This would allow the user to set:

MyClass:
    dynamic_constraint_provider: ~

This comment has been minimized.

@webmozart

webmozart Jan 31, 2012

Contributor

I think isset is fine.

MyClass:
    dynamic_constraint_provider: true
@webmozart

View changes

tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php Outdated
public function testGroupSequenceFailesIfGroupSequenceProviderIsSet()

This comment has been minimized.

@webmozart

webmozart Feb 1, 2012

Contributor

typo: Fails

@webmozart

View changes

tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php Outdated
try {
$metadata->setGroupSequence(array('GroupSequenceProviderEntity', 'Foo'));
$this->fail();
} catch(GroupDefinitionException $e) {}

This comment has been minimized.

@webmozart

webmozart Feb 1, 2012

Contributor

You should use the test method annotation

/**
 * @expectedException Symfony\Component\Validator\Exception\GroupDefinitionException
 */

instead.

@webmozart

View changes

tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php Outdated
} catch(GroupDefinitionException $e) {}
}
public function testGroupSequenceProviderFailesIfGroupSequenceIsSet()

This comment has been minimized.

@webmozart

webmozart Feb 1, 2012

Contributor

typo: Fails

@webmozart

View changes

tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php Outdated
} catch(GroupDefinitionException $e) {}
}
public function testGroupSequenceProviderFailesIfDomainClassIsInvalid()

This comment has been minimized.

@webmozart

webmozart Feb 1, 2012

Contributor

typo: Fails

@webmozart

View changes

tests/Symfony/Tests/Component/Validator/Fixtures/Entity.php Outdated
@@ -6,6 +6,7 @@
require_once __DIR__.'/EntityInterface.php';
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\GroupSequenceProviderInterface;

This comment has been minimized.

@webmozart

webmozart Feb 1, 2012

Contributor

unused?

@webmozart

View changes

tests/Symfony/Tests/Component/Validator/Fixtures/Entity.php Outdated
@@ -28,6 +29,8 @@ class Entity extends EntityParent implements EntityInterface
protected $lastName;
public $reference;
protected $groups = array();

This comment has been minimized.

@webmozart

webmozart Feb 1, 2012

Contributor

unused?

@webmozart

View changes

src/Symfony/Component/Validator/Mapping/ClassMetadata.php Outdated
*
* @return boolean
*/
public function hasGroupSequenceProvider()

This comment has been minimized.

@webmozart

webmozart Feb 1, 2012

Contributor

rename to isGroupSequenceProvider

@sebhoerl

This comment has been minimized.

Contributor

sebhoerl commented Feb 2, 2012

Okay, typos are fixed now and hasGroupSequenceProvider has been renamed to isGroupSequenceProvider. I also had to adjust some tests after the rebase with master.

@webmozart

View changes

src/Symfony/Component/Validator/GroupSequenceProviderInterface.php Outdated
*
* @return array An array of validation groups
*/
function getValidationGroups();

This comment has been minimized.

@webmozart

webmozart Feb 2, 2012

Contributor

This should be named getGroupSequence to match with the idea behind the provider.

This comment has been minimized.

@sebhoerl

sebhoerl Feb 2, 2012

Contributor

Done.

@webmozart

This comment has been minimized.

Contributor

webmozart commented Feb 3, 2012

Looks good.

@fabpot 👍

@fabpot

This comment has been minimized.

Member

fabpot commented Feb 3, 2012

Can you add a note in the CHANGELOG before I merge? Thanks.

@sebhoerl

This comment has been minimized.

Contributor

sebhoerl commented Feb 9, 2012

@fabpot done

fabpot added a commit that referenced this pull request Feb 9, 2012

merged branch blogsh/dynamic_group_sequence (PR #3199)
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: ![](https://secure.travis-ci.org/blogsh/symfony.png?branch=dynamic_group_sequence)

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

@fabpot fabpot merged commit 411a0cc into symfony:master Feb 9, 2012

@canni

This comment has been minimized.

Contributor

canni commented Feb 9, 2012

@blogsh can you also update docs and submit PR to symfony-docs?

@sebhoerl

This comment has been minimized.

Contributor

sebhoerl commented Feb 9, 2012

Yes, will be updated.

@mvrhov

This comment has been minimized.

Contributor

mvrhov commented Jun 22, 2012

@blogsh:
First the background: I have the same design as FOSUser bundle. The model which defines all the properties and then empty Entity class extending the Model.
I've defined all the property validation metadata for Model class. And that metadata applies just fine for the Entity class.
However if I want to validate with group-sequence-provider, then I have to define it on the Entity class. Now I'm wondering is this by design and why, or this really is a bug?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment