问题
Symfony 2.8+ / 3.x +是否可以在开始实体验证之前调度事件?
情况:
假设我们有100个实体,他们有@LifeCycleCallbacks,他们有@postLoad事件做某事,但其结果仅用于实体的valiation,在99%的情况下@postLoad的结果对系统不重要。因此,如果我们有从数据库获取的数据库或数千个实体,那么对于不重要的数据,将会有很多机器周期丢失。
运行某种类型的事件会很好,它会运行方法,在验证开始之前填充该特定实体的数据。
而不是:
$entity->preValidate();
$validator = $this->get('validator');
$errors = $validator->validate($entity);
我们可以:
$validator = $this->get('validator');
$errors = $validator->validate($entity);
在validate()情况下,preValidate()将作为Event自动调度(当然,检查实体是否有这样的方法)。
案例研究:
/** * @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注意到)都会刷新,无论文件属性的值是否与DB中的相同(并且更改只是暂时的)。
它会被冲洗。
所以我们有无意义的sql更新或者无用的sql更新。
顺便说一下。
1。 - > flush($ textEntity)将抛出异常,因为 - >> remove($ textEntity)已经“删除”了该实体。
2。实体属性 - >文件必须是File for Assert / File类型,因为FileValidator只能接受File或absolute-path-to-file的值。 但是我不会存储绝对路径到文件,因为它在Dev,Stage和Production环境中会完全不同。
这是我尝试进行文件上传时遇到的问题,如Symfony cookbook http://symfony.com/doc/current/controller/upload_file.html中所述。
我的解决方法是,在postLoad()中,在属性中创建不是Doctrine列的File实例,并且用于声明等等。
这有效,但无用的postLoad()问题仍然存在,我想到了事件。这可能是有弹性的,也是非常优雅的解决方案 - 而不是控制器变得“胖”。
任何人都有更好的解决方案吗?或者知道如果 - > validate()发生事件会如何发送事件?
答案 0 :(得分:1)
Hello Voult,
编辑:第一种方法在symfony 3中被弃用,作为评论中提到的线程。检查为symfony 3制作的第二种方法。
Symfony 2.3 +,Symfony< 3 强>
在这种情况下我做了什么,因为symfony和大多数其他bundle使用参数进行服务类定义,就是扩展该服务。请查看下面的示例,有关扩展服务的更多信息,请查看此链接
http://symfony.com/doc/current/bundles/override.html
首先,您需要为需要预验证的实体添加一些标记。我通常使用类似
这样的东西的接口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);
}
}
最后一步,您需要将类值参数添加到&#39;参数&#39; config.yml中的config block,如下所示:
parameters:
validator.class: Your\Name\Space\MyValidator
这是基本的想法。现在你可以将这个想法与你想要达到的目标混合在一起。例如,不是在实体上调用方法(我通常喜欢将业务逻辑保留在我的实体之外),您可以查找接口,如果它在那里,您可以在其上启动pre.validate事件,并且使用听众来完成这项工作。之后,您可以保留parent :: validate的结果,并启动post.validate事件。你知道我要去哪里。你基本上可以在那个验证方法中做任何你喜欢的事情。
PS:上面的例子是简单的方法。如果你想进入事件路线,服务扩展将更难,因为你需要将调度程序注入其中。检查我在开头提供的链接,看看扩展服务的其他方式,如果您需要帮助,请告诉我。
对于Symfony 3.0 - &gt; 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。这是丑陋/肮脏的部分,因为这个类是最终的,所以你不能扩展它,并且没有实现的接口,所以你必须注意未来的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:您的\ Name \ Space \ MyValidation
这是最难侵入的方式,我能找到。当你将symfony升级到未来版本时,它可能需要一些维护。
希望这会有所帮助,并且编码很快
Alexandru Cosoi