Validator event dispatched before Entity validation starts Validator event dispatched before Entity validation starts symfony symfony

Validator event dispatched before Entity validation starts



Hello Voult,

Edit: first method is deprecated in symfony 3 as the thread op mentioned in a comment. Check the second method made for symfony 3.


Symfony 2.3+,Symfony < 3

What I do in this cases, since symfony and most other bundles are using parameters for service class definition, is to extend that service. Check the example below and for more information on extending services check this link

http://symfony.com/doc/current/bundles/override.html

First you need to add a some marker to your entities that require pre-validation. I usually use interfaces for stuff like this something like

namespace Your\Name\Space;interface PreValidateInterface{   public function preValidate();}

After this you extend the validator service

<?phpnamespace Your\Name\Space;use Symfony\Component\Validator\Validator;class MyValidator extends Validator //feel free to rename this to your own liking{    /**     * @inheritdoc     */    public function validate($value, $groups = null, $traverse = false, $deep = false)    {        if (is_object($value) && $value instanceof PreValidateInterface) {            $value->preValidate();        }        return parent::validate($value, $groups, $traverse, $deep);    }}

And final step, you need to add the class value parameter to your 'parameters' config block in config.yml, something like this:

parameters:    validator.class: Your\Name\Space\MyValidator

This is the basic idea. Now you can mix end match this idea with whatever you want to achieve. For instance instead of calling a method on the entity (I usually like to keep business logic outside of my entities), you can look for the interface and if it is there you can launch a pre.validate event with that entity on it, and use a listener to do the job. After that you can keep the result from parent::validate and also launch a post.validate event. You see where i'm going with this. You basically can do whatever you like now inside that validate method.

PS: The example above is the easy method. If you want to go the event route, the service extension will be harder, since you need to inject the dispatcher into it. Check the link I provided at the beginning to see the other way to extend a service and let me know if you need help with this.


For Symfony 3.0 -> 3.1

In this case they managed to make it hard and dirtier to extend

Step 1:

Create your own validator something like this:

<?phpnamespace Your\Name\Space;use Symfony\Component\Validator\Constraint;use Symfony\Component\Validator\ConstraintViolationListInterface;use Symfony\Component\Validator\Context\ExecutionContextInterface;use Symfony\Component\Validator\Exception;use Symfony\Component\Validator\MetadataInterface;use Symfony\Component\Validator\Validator\ContextualValidatorInterface;use Symfony\Component\Validator\Validator\ValidatorInterface;class myValidator implements ValidatorInterface{    /**     * @var ValidatorInterface     */    protected $validator;    /**     * @param ValidatorInterface $validator     */    public function __construct(ValidatorInterface $validator)    {        $this->validator = $validator;    }    /**     * Returns the metadata for the given value.     *     * @param mixed $value Some value     *     * @return MetadataInterface The metadata for the value     *     * @throws Exception\NoSuchMetadataException If no metadata exists for the given value     */    public function getMetadataFor($value)    {        return $this->validator->getMetadataFor($value);    }    /**     * Returns whether the class is able to return metadata for the given value.     *     * @param mixed $value Some value     *     * @return bool Whether metadata can be returned for that value     */    public function hasMetadataFor($value)    {        return $this->validator->hasMetadataFor($value);    }    /**     * Validates a value against a constraint or a list of constraints.     *     * If no constraint is passed, the constraint     * {@link \Symfony\Component\Validator\Constraints\Valid} is assumed.     *     * @param mixed $value The value to validate     * @param Constraint|Constraint[] $constraints The constraint(s) to validate     *                                             against     * @param array|null $groups The validation groups to     *                                             validate. If none is given,     *                                             "Default" is assumed     *     * @return ConstraintViolationListInterface A list of constraint violations.     *                                          If the list is empty, validation     *                                          succeeded     */    public function validate($value, $constraints = null, $groups = null)    {        //the code you are doing all of this for        if (is_object($value) && $value instanceof PreValidateInterface) {            $value->preValidate();        }        //End of code        return $this->validator->validate($value, $constraints, $groups);    }    /**     * Validates a property of an object against the constraints specified     * for this property.     *     * @param object $object The object     * @param string $propertyName The name of the validated property     * @param array|null $groups The validation groups to validate. If     *                                 none is given, "Default" is assumed     *     * @return ConstraintViolationListInterface A list of constraint violations.     *                                          If the list is empty, validation     *                                          succeeded     */    public function validateProperty($object, $propertyName, $groups = null)    {        $this->validator->validateProperty($object, $propertyName, $groups);    }    /**     * Validates a value against the constraints specified for an object's     * property.     *     * @param object|string $objectOrClass The object or its class name     * @param string $propertyName The name of the property     * @param mixed $value The value to validate against the     *                                     property's constraints     * @param array|null $groups The validation groups to validate. If     *                                     none is given, "Default" is assumed     *     * @return ConstraintViolationListInterface A list of constraint violations.     *                                          If the list is empty, validation     *                                          succeeded     */    public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)    {        $this->validator->validatePropertyValue($objectOrClass, $propertyName, $value, $groups);    }    /**     * Starts a new validation context and returns a validator for that context.     *     * The returned validator collects all violations generated within its     * context. You can access these violations with the     * {@link ContextualValidatorInterface::getViolations()} method.     *     * @return ContextualValidatorInterface The validator for the new context     */    public function startContext()    {        $this->validator->startContext();    }    /**     * Returns a validator in the given execution context.     *     * The returned validator adds all generated violations to the given     * context.     *     * @param ExecutionContextInterface $context The execution context     *     * @return ContextualValidatorInterface The validator for that context     */    public function inContext(ExecutionContextInterface $context)    {        $this->validator->inContext($context);    }}

Step 2:

Extend Symfony\Component\Validator\ValidatorBuilder something like this:

namespace Your\Name\Space;use Symfony\Component\Validator\ValidatorBuilder;class myValidatorBuilder extends ValidatorBuilder{    public function getValidator()    {        $validator =  parent::getValidator();        return new  MyValidator($validator);    }}

You need to override Symfony\Component\Validator\Validation. This is the ugly/dirty part, because this class is final so you can't extend it, and has no interface to implement, so you will have to pay attention to in on future versions of symfony in case backward compatibility is broken. It goes something like this:

namespace Your\Name\Space;final class MyValidation{    /**     * The Validator API provided by Symfony 2.4 and older.     *     * @deprecated use API_VERSION_2_5_BC instead.     */    const API_VERSION_2_4 = 1;    /**     * The Validator API provided by Symfony 2.5 and newer.     */    const API_VERSION_2_5 = 2;    /**     * The Validator API provided by Symfony 2.5 and newer with a backwards     * compatibility layer for 2.4 and older.     */    const API_VERSION_2_5_BC = 3;    /**     * Creates a new validator.     *     * If you want to configure the validator, use     * {@link createValidatorBuilder()} instead.     *     * @return ValidatorInterface The new validator.     */    public static function createValidator()    {        return self::createValidatorBuilder()->getValidator();    }    /**     * Creates a configurable builder for validator objects.     *     * @return ValidatorBuilderInterface The new builder.     */    public static function createValidatorBuilder()    {        return new MyValidatorBuilder();    }    /**     * This class cannot be instantiated.     */    private function __construct()    {    }}

And last step overwrite the parameter validator.builder.factory.class in your config.yml:

parameters: validator.builder.factory.class: Your\Name\Space\MyValidation

This is the least invasive way to do it, that i can find. Is not that clean and it could need some maintaining when you upgrade symfony to future versions.

Hope this helps, and happy coding

Alexandru Cosoi