Doctrine 实体验证开始前已调度验证程序事件

Doctrine 实体验证开始前已调度验证程序事件,doctrine,symfony,symfony-validator,Doctrine,Symfony,Symfony Validator,问题 在Symfony 2.8+/3.x+中,是否可以在启动实体验证之前调度事件 情况: 假设我们有100个实体,它们有@LifeCycleCallbacks,它们有@postLoad事件来做一些事情,但其结果仅用于实体的有效性,在99%的情况下@postLoad的结果对系统不重要。因此,如果我们从数据库中获取了数百或数千个实体,那么对于不重要的数据,将丢失大量的机器周期 在验证开始之前,最好运行某种类型的事件,运行方法,为特定实体填充数据 而不是: $entity->preValidat

问题

在Symfony 2.8+/3.x+中,是否可以在启动实体验证之前调度事件

情况:

假设我们有100个实体,它们有@LifeCycleCallbacks,它们有@postLoad事件来做一些事情,但其结果仅用于实体的有效性,在99%的情况下@postLoad的结果对系统不重要。因此,如果我们从数据库中获取了数百或数千个实体,那么对于不重要的数据,将丢失大量的机器周期

在验证开始之前,最好运行某种类型的事件,运行方法,为特定实体填充数据

而不是:

$entity->preValidate();
$validator = $this->get('validator');
$errors = $validator->validate($entity);
我们可以:

$validator = $this->get('validator');
$errors = $validator->validate($entity);
在validate()情况下,preValidate()将自动作为事件进行调度(当然要检查实体是否有这样的方法)

案例研究:

  • 我有一个将页面/子页面存储为实体的系统。可以有10页或10000页/子页
  • 页面/子页面可以包含文件
  • 实体只存储文件名(因为我们不能存储SplFileInfo-资源序列化限制)
  • 虽然Entity->file属性是字符串的类型,但当我想使其成为文件的实例(以便我们可以验证类型文件)时,我有如下方法:
  • 好的,但是postLoad()会更改属性,而且条令会注意到这一点。那么下一步呢

    $entityManager->flush()
    
    所有实体都将被刷新,即使pressave()将其更改回原来的字符串

    所以如果我有任何其他实体,比如TextEntity,我想删除它

    $entityManager->remove($textEntity);
    $entityManager->flush();
    
    不管文件属性的值是否与数据库中的值相同(并且更改只是暂时的),以某种方式更改的所有其他实体(更改已被条令注意到)都将被刷新

    它会被冲掉的

    所以我们有成百上千个毫无意义的sql更新

    顺便说一句

    1.->flush($textEntity)将引发异常,因为->删除($textEntity)已“删除”该实体

    二,。实体属性->文件必须是Assert/file的文件类型,因为FileValidator只能接受文件或文件绝对路径的值。 但我不会存储文件的绝对路径,因为它在开发、阶段和生产环境中将完全不同

    这是我试图上传文件时出现的问题,正如Symfony食谱中所描述的那样

    我的解决方案是,在postLoad()中,在属性中创建一个文件实例,该属性不是doctor列,并且被指定具有断言,等等

    这是可行的,但无用的postLoad()s的问题仍然存在,我考虑了一些事件。这可能是一个有弹性的、非常优雅的解决方案——而不是控制器变得“胖”

    有谁有更好的解决办法吗?或者知道如何在->validate()发生时分派事件?


    你好,沃尔特

    编辑:在symfony 3中,第一个方法作为注释中提到的线程op被弃用。检查为symfony 3制作的第二种方法


    Symfony 2.3+,Symfony<3

    在这种情况下,由于symfony和大多数其他bundle都使用参数来定义服务类,所以我要做的是扩展该服务。查看下面的示例,有关扩展服务的更多信息,请查看此链接

    首先,您需要向需要预验证的实体添加一个标记。我通常使用接口来处理像这样的东西

    namespace Your\Name\Space;
    
    interface PreValidateInterface
    {
       public function preValidate();
    }
    
    在此之后,您将扩展验证程序服务

    <?php
    
    namespace 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);
        }
    }
    
    这是基本思想。现在你可以把这个想法和你想达到的任何目标结合起来。例如,您可以查找接口,而不是在实体上调用方法(我通常喜欢将业务逻辑保留在实体之外),如果接口在那里,您可以启动一个带有该实体的pre.validate事件,并使用侦听器来完成这项工作。之后,您可以保留来自parent::validate的结果,还可以启动post.validate事件。你知道我要说什么了。在验证方法中,您现在基本上可以做任何您喜欢的事情

    PS:上面的例子是简单的方法。如果您想使用事件路由,服务扩展将更加困难,因为您需要向其中注入调度程序。查看我在开始时提供的链接,了解扩展服务的其他方法,如果您需要帮助,请告诉我


    适用于Symfony 3.0->3.1

    在这种情况下,他们设法使它很难和肮脏的扩展

    步骤1:

    创建您自己的验证程序,如下所示:

    parameters:
        validator.class: Your\Name\Space\MyValidator
    
    <?php
    
    namespace 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);
        }
    }
    
    namespace Your\Name\Space;
    
    use Symfony\Component\Validator\ValidatorBuilder;
    
    class myValidatorBuilder extends ValidatorBuilder
    {
        public function getValidator()
        {
            $validator =  parent::getValidator();
    
            return new  MyValidator($validator);
        }
    
    }
    
    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()
        {
        }
    }
    
    您需要覆盖Symfony\Component\Validator\Validation。这是一个丑陋/肮脏的部分,因为这个类是最终类,所以您无法扩展它,并且没有接口可实现,所以您必须注意symfony的未来版本中,以防向后兼容性被破坏。事情是这样的:

    parameters:
        validator.class: Your\Name\Space\MyValidator
    
    <?php
    
    namespace 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);
        }
    }
    
    namespace Your\Name\Space;
    
    use Symfony\Component\Validator\ValidatorBuilder;
    
    class myValidatorBuilder extends ValidatorBuilder
    {
        public function getValidator()
        {
            $validator =  parent::getValidator();
    
            return new  MyValidator($validator);
        }
    
    }
    
    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()
        {
        }
    }
    
    最后一步覆盖config.yml中的参数validator.builder.factory.class:

    参数: validator.builder.factory.class:Your\Name\Space\MyValidation

    这是我能找到的侵入性最小的方法。不是很干净,在升级symfony到将来的版本时可能需要一些维护

    希望这对你有所帮助,并祝你编码愉快


    Alexandru Cosoi

    为什么不装饰一下
    验证器
    服务,自己发送
    预验证
    事件?你的意思是扩展验证器类并将其注册为服务?是的,我喜欢你的想法。但我有一个问题-我必须将MyVallidator注册为一个服务,并使用它,还是“Service.class”参数将作为新的且唯一可用的验证器类被框架自动捕获?好的,2个问题:1。Symfony\Component\Validator\Validator被标记为不推荐2。Symfony\Component\Validator\ValidatorBuilder,方法@getValidator()(如果尚未设置验证程序,则运行该方法)使用(返回)Symfony\Compo