Skip to content

[RFC][Form] Using form data to set constraints options #7444

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
marcospassos opened this issue Mar 20, 2013 · 8 comments
Closed

[RFC][Form] Using form data to set constraints options #7444

marcospassos opened this issue Mar 20, 2013 · 8 comments
Labels

Comments

@marcospassos
Copy link

I've a model with the fields defaultValue, minLength and maxLength. The property defaultValue must be validated by Constraints\Length, but the min and max options depends of minLength and maxLength values. Furthermore, I would like to validate if minLength is less than maxLength using Constraints\Range. Once there is specialized validators to do this job, I wouldn't like to use the callback constraint.

I believe that is no way to do that currently, but is a very usefull feature.

@stof
Copy link
Member

stof commented Mar 20, 2013

Configuring the validation with the values looks wrong, as the validation is about checking the value.

What you probably need is the constraint comparing 2 properties of an object, which are implemented in #790 and may be part of Symfony 2.3. And these cannot be implemented with Length or Range as the logic is totally diffeent (it is not the same rules)

@marcospassos
Copy link
Author

I agree that this solution solves the second example, but how about the Length case? How can I validate the defaultValue respecting the values entered in minLengthand maxLength without replicate the Constraints\Length code?

@marcospassos
Copy link
Author

Currently, I need create a class validator, inject Validatorand perform the validations.

    public function validate($value, Constraint $constraint)
    {
        if (!$value instanceof OptionTextInterface) {
            throw new UnexpectedTypeException($value, 'Acme\ExampleBundle\Model\OptionTextInterface');
        }

        $option = $value;
        $maxLength = $option->getMaxLength();
        $minLength = $option->getMinLength();

        // minLength cannot be great than maxLength
        if ($minLength > 0 && $maxLength > 0) {
            $rangeConstraint = new Range(array(
                'max' => $maxLength,
                'maxMessage' => $constraint->minLengthLogicalMessage,
            ));

            $this->validateValue($minLength, 'minLength', $rangeConstraint);
        }

        // The default value must respect the minLength and maxLength constraints
        if (($minLength > 0 || $maxLength > 0) && ($defaultValue = $option->getDefaultValue())) {
            $lengthConstraint = new Length(array(
                'min' => $minLength,
                'max' => $maxLength,
                'minMessage' => $constraint->defaultValueMinLengthMessage,
                'maxMessage' => $constraint->defaultValueMaxLengthMessage,
            ));

            $this->validateValue($defaultValue, 'defaultValue', $lengthConstraint);
        }
    }

    private function validateValue($value, $property, $constraint)
    {
        $errors = $this->validator->validateValue($value, $constraint);
        if (count($errors) > 0) {
            $this->context->addViolationAt($property, $errors[0]->getMessage());

            return false;
        }

        return true;
    }

It could be addressed with something like:

<class name="Acme\ExampleBundle\Entity\OptionText">
    <property name="minLength">
        <constraint name="Range">
            <option name="max">
                <!-- PropertyPath style -->
                <propertyValue>maxLength</propertyValue>
            </option>
        </constraint>
    </property>
    <property name="defaultValue">
        <constraint name="Length">
            <option name="min">
                <propertyValue>minLength</propertyValue>
            </option>
            <option name="max">
                <propertyValue>maxLength</propertyValue>
            </option>
        </constraint>
    </property>
</class>

Rules:

  • When a validation depends of a value of other field, then this field should be validated before the performing the current validation. If not valid, then the current validation is not performed.
  • When circular references are detected the CircularReferenceException is thrown

@webmozart
Copy link
Contributor

What about adding a @Reference annotation? Using this annotation, we could refer to values in the validated object graph in any other constraint without implement so much specialized logic as in #790.

use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Reference;

/**
 * @Range(min = @Reference("min"), max = @Reference("max"))
 */
private $value;

private $min;

private $max

@marcospassos
Copy link
Author

You have got! It is exactly what I was talking about, and the most important: it solves two problems with one simple and efficient solution.

@stof
Copy link
Member

stof commented Apr 18, 2013

@bschussek but any valdiator would have to support the case of @Reference by itself. We would still need specialize logic. the only difference is that we would put it in existing constraints, not in new ones.

And this would require changing the XML and YAML loaders too as they are currently unable to handle this (see the issue with Optional and Required in the Collection constraint)

@webmozart
Copy link
Contributor

@stof Yes, but the biggest part of that implementation would be outsourced to ReferenceResolver. The XML and YAML loader can create Reference objects too. The question is just which syntax to use in their source files. Let's continue this discussion in #790.

@webmozart
Copy link
Contributor

Closing in favor of #7726.

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

No branches or pull requests

3 participants