gpt4 book ai didi

doctrine - 在实体验证开始之前调度的验证器事件

转载 作者:行者123 更新时间:2023-12-02 03:11:38 25 4
gpt4 key购买 nike

问题

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() 将作为 Event 自动发送(当然还要检查 Entity 是否有这样的方法)。

案例研究:
  • 我有一个将页面/子页面存储为实体的系统。可以有 10 或 10000 个页面/子页面
  • 页面/子页面可以有文件。
  • 实体只存储文件名(因为我们不能存储 SplFileInfo - 资源序列化限制)
  • 虽然 Entity->file 属性是字符串类型,但当我想让它成为 File 的实例时(所以我们可以对 File 类型进行验证),我有类似的东西:

  • /**
    * @postLoad()
    */
    public function postLoad()
    {
    //magicly we get $rootPath
    $this->file = new File($rootPath . '/' . $this->file);
    }
    /**
    * @prePersist()
    * @preUpdate()
    */
    public function preSave()
    {
    if ($this->file instance of File)
    $this->file = $this->file->getFilename();
    }
    }



    好的,但是 postLoad() 会改变属性,而 Doctrine 会注意到这一点。所以在接下来
    $entityManager->flush()

    所有实体都将被刷新——即使 preSave() 将它改回原来的字符串。

    因此,如果我有任何其他实体,比如说 TextEntity,我想删除它
    $entityManager->remove($textEntity);
    $entityManager->flush();

    无论文件属性的值是否与数据库中的值相同(并且更改只是暂时的),所有其他以某种方式更改的实体(更改被 Doctrine 注意到)都会被刷新。

    它会被冲洗掉。

    因此,我们有数百个或数千个毫无意义的 sql 更新。

    顺便提一句。

    1. ->flush($textEntity) 将抛出异常,因为 ->remove($textEntity) 已经“删除”了该实体。

    2. Entity property ->file 对于 Assert/File 必须是 File 类型,因为 FileValidator 只能接受 File 或 absolute-path-to-file 的值。
    但我不会存储文件的绝对路径,因为它在 Dev、Stage 和 Production 环境中完全不同。

    这是我尝试上传文件时出现的问题,如 Symfony 食谱 中所述 http://symfony.com/doc/current/controller/upload_file.html .

    我的解决方案是,在 postLoad() 中,在不是 Doctrine 列的属性中创建 File 实例,并注明有断言等。

    这行得通,但是无用的 postLoad()s 的问题仍然存在,我想到了事件。这可能是弹性且非常优雅的解决方案——而不是 Controller 变得“胖”。

    任何人有更好的解决方案?或者知道如果 ->validate() 发生了如何调度事件?

    最佳答案

    你好沃尔特,

    编辑:第一种方法在 symfony 3 中被弃用,因为评论中提到的线程操作。检查为 symfony 3 制作的第二种方法。

    Symfony 2.3+,Symfony < 3

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

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

    首先,您需要为需要预验证的实体添加一些标记。我通常将接口(interface)用于类似这样的东西

    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);
    }
    }

    最后一步,您需要将类值参数添加到 config.yml 中的“参数”配置 block 中,如下所示:
    parameters:
    validator.class: Your\Name\Space\MyValidator

    这是基本思想。现在,您可以将最终匹配这个想法与您想要实现的任何目标混合在一起。例如,不是在实体上调用方法(我通常喜欢将业务逻辑保留在我的实体之外),您可以查找接口(interface),如果存在,您可以启动带有该实体的 pre.validate 事件,并且使用监听器来完成这项工作。之后,您可以保留 parent::validate 的结果并启动 post.validate 事件。你知道我要去哪里。你现在基本上可以在 validate 方法中做任何你喜欢的事情。

    PS:上面的例子是简单的方法。如果你想走事件路由,服务扩展会更难,因为你需要将调度程序注入(inject)其中。检查我在开始时提供的链接以查看扩展服务的另一种方式,如果您需要帮助,请告诉我。

    对于 Symfony 3.0 -> 3.1

    在这种情况下,他们设法使扩展变得更加困难和肮脏

    第1步:

    创建您自己的验证器,如下所示:
    <?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);
    }
    }

    第2步:

    像这样扩展 Symfony\Component\Validator\ValidatorBuilder :
    namespace Your\Name\Space;

    use Symfony\Component\Validator\ValidatorBuilder;

    class myValidatorBuilder extends ValidatorBuilder
    {
    public function getValidator()
    {
    $validator = parent::getValidator();

    return new MyValidator($validator);
    }

    }

    你需要覆盖 Symfony\Component\Validator\Validation。这是丑陋/肮脏的部分,因为这个类是最终的,所以你不能扩展它,并且没有要实现的接口(interface),所以你必须注意 future 的 symfony 版本,以防向后兼容性被破坏。它是这样的:
    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: 你的名字空间 MyValidation

    这是我能找到的侵入性最小的方法。不是那么干净,当您将 symfony 升级到 future 版本时,它可能需要一些维护。

    希望这会有所帮助,并且编码愉快

    亚历山德鲁·科索伊

    关于doctrine - 在实体验证开始之前调度的验证器事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39615300/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com