8000 [2.1] Conditional Validation · Issue #1151 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[2.1] Conditional Validation #1151

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

Closed
schmittjoh opened this issue May 30, 2011 · 15 comments
Closed

[2.1] Conditional Validation #1151

schmittjoh opened this issue May 30, 2011 · 15 comments

Comments

@schmittjoh
Copy link
Contributor

Just making sure, this does not get lost, see also http://groups.google.com/group/symfony-devs/msg/a73a70c4b696b8d9

There are two solutions I can imagine:

a) Having an re-entry point for validation that can be used from a class validator

<?php
class MyClassValidator extends ConstraintValidator
{
    public function isValid($value, Constraint $constraint)
    {
        if ($value->field=== 'value') {
            return $this->context->validate($value->path1);
        } else {
            return $this->context->validate($value->path2);
        }
    }
}

b) Having a way to influence the graph walker as to which members should be visited

<?php
class MyClassValidator extends ConstraintValidator
{
    public function isValid($value, Constraint $constraint)
    {
        if ($value->field === 'somevalue') {
            $this->context->getGraphWalker()->skipSubPath('path1');
        } else {
            $this->context->getGraphWalker()->skipSubPath('path2');
        }

        return true;
    }
}

@bschussek, what do you think?

@rande
Copy link
Contributor
rande commented Jun 25, 2011

I come up with another solution : https://github.com/sonata-project/AdminBundle/commit/4022538f66a7f36e9cb5cd90bcdb5f22c1343221#diff-3

Validation is done at runtime with an external service.

@schmittjoh
Copy link
Contributor Author

I'm not really convinced by your approach, as you seem to re-invent how validation should be done. Maybe I'm wrong, can you explain it a bit more?

@lsmith77
Copy link
Contributor

not sure yet about the best approach, but i agree that its an important thing to get worked out.

@rande
Copy link
Contributor
rande commented Jun 25, 2011

Not sure if my proposal is good or not, however is perfectly match a use case : https://gist.github.com/1046563.

I have many block instances managed by different services, the validation must be done inside a service as each block service might have a different logic. I also want something to quickly code new block services without coding specific validators or create dedicated validation group.

btw, this solution rely on the validator components, so it just about adding a bit more flexibility and save time.

@ghost
Copy link
ghost commented Jul 23, 2011

I'm really new on Symfony. Even I'm not sure if you're talking about this issue, anyway here is an idea about how
can be implemented the conditions… using groups

class ParentObject
{
    /**
     * @Assert\Choice(choices={"choice1", "choice2"})
     * @Assert\Choice(choices={"choice1"}, groups={"isChoice1"})     
     * @Assert\Choice(choices={"choice2"}, groups={"isChoice2"})     
     */
    private $choice;

    /**
     * @Assert\NotNull(conditions={"isChoice1"})
     */
    private $choice1;

    /**
     * @Assert\NotNull(conditions={"isChoice2"})
     */    
    private $choice2;
}

If a constraint has a condition/s, validation will be re-executed using the group , if the (sub-validation) is ok , Constraint will be applied

@rande
Copy link
Contributor
rande commented Jul 23, 2011

Groups are not flexible enough

@Gregwar
Copy link
Contributor
Gregwar commented Sep 26, 2011

What about adding a special method on such entities (yet another idea) :

<?php

class User implements ValidationGroupsInterface
{
    // ...

    // Form would call this method, passing it its validation_groups option
    public function getValidationGroups(array $groups = array())
    {
        if ($this->getType() === 'something') {
            $groups[] = 'foo';
        } else {
            $groups[] = 'bar';
        }
        return $groups;
    }
}

@rande
Copy link
Contributor
rande commented Sep 26, 2011

The conditional validator must be a service, as it might require some externals services to validate the data. So settings such method in a model/entity is not flexible enough.

@luishdez
Copy link

But… "Validator" is also a component service and is set directly in the entity , Validation and conditional validation are such a common practice that should have a quick way of develop. I think the last two examples will mitigate the 99% of needs…

Following the logic of the validator component . First would be nice to have a simple and quick method like annotations, then if you want to get deep you can use PHP inside your public validation method in your "entity", if this is not flexible enough then you can go directly to the service in your: formtypes, services, models or whatever

Anyway I totally agree that should be a service, but these examples show how implement it, not how to develope it

@canni
Copy link
Contributor
canni commented Oct 27, 2011

See my PR: #2498

fabpot added a commit that referenced this issue Nov 8, 2011
Commits
-------

d08ec5e Add DelegatingValidator tests
e1822e7 Enable dynamic set of validation groups by a callback or Closure

Discussion
----------

[Form][Validator] Enable dynamic set of validation groups based on callback

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
Symfony2 tests written for new feature: yes
closes tickets: #2498 #1151

This will allow developer to pass a Closure or a callback array as a Validation groups option of a form. Eg:

```
class ClientType extends AbstarctType
{
// ...
    public function getDefaultOptions(array $options)
    {
        return array(
            'validation_groups' => function(FormInterface $form){
                // return array of validation groups based on submitted user data (data is after transform)
                $data = $form->getData();
                if($data->getType() == Entity\Client::TYPE_PERSON)
                    return array('Default', 'person');
                else
                    return array('Default', 'company');
            },
        );
    }
// ...
}
```

```
class ClientType extends AbstarctType
{
// ...
    public function getDefaultOptions(array $options)
    {
        return array(
            'validation_groups' => array(
                'Acme\\AcmeBundle\\Entity\\Client',
                'determineValidationGroups'
            ),
        );
    }
// ...
}
```

This will make developers life easier !

---------------------------------------------------------------------------

by schmittjoh at 2011/10/27 06:39:56 -0700

Does that work if your ClientType were added to another form type?

e.g.

```php
<?php

class MyComplexType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('client', new ClientType());
    }

    // ...
}
```

---------------------------------------------------------------------------

by canni at 2011/10/27 06:44:33 -0700

This is doing nothing more than injecting array of validation groups, should work, but I have not tested this use case.

---------------------------------------------------------------------------

by canni at 2011/10/28 01:58:26 -0700

PHPUnit output

```
OK, but incomplete or skipped tests!
Tests: 5011, Assertions: 12356, Incomplete: 36, Skipped: 32.
```

---------------------------------------------------------------------------

by canni at 2011/11/02 11:37:47 -0700

Now functionality is complete, test are written, and implementation is clean. :)

---------------------------------------------------------------------------

by stloyd at 2011/11/02 11:50:44 -0700

Can tou `squash` your commits ? Thanks.

---------------------------------------------------------------------------

by canni at 2011/11/02 11:58:41 -0700

Done

---------------------------------------------------------------------------

by fabpot at 2011/11/07 07:51:18 -0800

Can you add some tests for the `DelegatingValidator` class, which is where we can ensure that the new feature actually works as expected?

---------------------------------------------------------------------------

by canni at 2011/11/07 13:53:16 -0800

OK, I've written proof-of-concept tests, also I've squashed few commits to make things clear.
Personally I think this should go straight into 2.0 series, as it do not beak BC, and a feature is really nice to use.

---------------------------------------------------------------------------

by stof at 2011/11/07 14:17:15 -0800

@canni the 2.0 branch is for bug fixes, not for new features. This is the difference between maintenance releases and minor releases.
@canni
Copy link
Contributor
canni commented Dec 8, 2011

@schmittjoh do dynamic validation groups ( #2498 ) solve yours case? Maybe this issue can be closed?

@stof
Copy link
Member
stof commented Dec 8, 2011

@canni it does not. the dynamic validation groups are a feature of the Form component. It does not change the fact that the Validator does not support conditional inheritance

@webmozart
Copy link
Contributor

@schmittjoh I'm confused. Your case a) is already possible with the GraphWalker, no? You can launch validation of custom fields, fields in different groups or whatever else you like. I agree that the implementation is not straightforward - should we work on improving this?

@fabpot
Copy link
Member
fabpot commented Jan 22, 2012

Does #3114 solve your issues?

fabpot added a commit that referenced this issue Jan 22, 2012
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.
@webmozart
Copy link
Contributor

Solved by #3199

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

No branches or pull requests

9 participants
0