Symfony 在某些情况下防止原则加载后事件
我有一个实体Symfony 在某些情况下防止原则加载后事件,symfony,events,doctrine,Symfony,Events,Doctrine,我有一个实体BlogPost,具有status属性。此状态属性取决于通过doctrine postLoad事件处理的外部API调用。所有其他属性都存储在本地数据库中 public function postLoad(BlogPost $post) { $this->postHandler->calculateStatus($post); } 问题是,在某些情况下,我根本不想计算状态。例如,如果我只想获得所有博客帖子的描述 使用上面的代码,所有正在加载的blog实体都将触发p
BlogPost
,具有status
属性。此状态属性取决于通过doctrine postLoad事件处理的外部API调用。所有其他属性都存储在本地数据库中
public function postLoad(BlogPost $post)
{
$this->postHandler->calculateStatus($post);
}
问题是,在某些情况下,我根本不想计算状态。例如,如果我只想获得所有博客帖子的描述
使用上面的代码,所有正在加载的blog实体都将触发postLoad事件,即使我只想从本地数据库获取值。这是非常昂贵和不可接受的
例如,在我的repository类中,我希望所有博客帖子都有一个网站,而不调用postLoad事件
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getResult();
}
有没有办法说“是的,加载BlogPost集合,但不要触发事件!”
还有其他方法吗?定制活动
谢谢为什么不将此逻辑移到post实体和事件侦听器之外?如果您知道何时需要计算状态,则可以显式地进行计算。 比如说
$post = $this->entityManager->find(BlogPost::class, $postId);
$status = $this->postHandler->calculateStatus($post);
我可以建议的另一种方法虽然不好,但很有效。您可以使用延迟计算,而不是在postLoad事件侦听器中调用$this->postHandler->calculateStatus($this)
,您可以将postHandler服务注入实体,并在实际需要时执行计算。
例如,如果在调用$blogPost->getStatus()
方法时需要计算,可以通过以下方式进行:
interface PostHandlerAwareInterface
{
public function setPostHandler(PostHandlerInterface $postHandler): void;
}
class EntityServiceInjectorEventSubscriber implements EventSubscriber
{
/** @var PostHandlerInterface */
private $postHandler;
public function postLoad($entity): void
{
$this->injectServices($entity);
}
public function postPersist($entity): void
{
$this->injectServices($entity);
}
private function injectServices($entity): void
{
if ($entity instanceof PostHandlerAwareInterface) {
$entity->setPostHandler($this->postHandler);
}
}
}
class BlogPost extends PostHandlerAwareInterface
{
/** @var PostHandlerInterface */
private $postHandler;
private $status;
public function setPostHandler(PostHandlerInterface $postHandler): void
{
$this->postHandler = $postHandler;
}
public function getStatus()
{
if (null === $this->status) {
$this->postHandler->calculateStatus($this);
}
return $this->status;
}
}
class BlogPostRepository
{
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp.description')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getArrayResult();
}
}
如果您不喜欢这个想法,您仍然可以通过设置实体事件侦听器的标志来管理它(但我强烈建议不要这样做)。
在获取数据之前,可以将实体事件侦听器注入代码并设置标志:
class BlogPostCalculateStatusListener
{
/** @var bool */
private $calculationEnabled = true;
public function suspendCalculation(): void
{
$this->calculationEnabled = false;
}
public function resumeCalculation(): void
{
$this->calculationEnabled = true;
}
public function postLoad(BlogPost $post): void
{
if ($this->calculationEnabled) {
$this->postHandler->calculateStatus($post);
}
}
}
$this->calculateStatusListener->suspendCalculation();
$blogPosts = $blogPostRepository->findBlogPosts();
$this->calculateStatusListener->resumeCalculation();
希望这有帮助
注:如果你只想获得所有博客文章的描述,你可以这样做:
interface PostHandlerAwareInterface
{
public function setPostHandler(PostHandlerInterface $postHandler): void;
}
class EntityServiceInjectorEventSubscriber implements EventSubscriber
{
/** @var PostHandlerInterface */
private $postHandler;
public function postLoad($entity): void
{
$this->injectServices($entity);
}
public function postPersist($entity): void
{
$this->injectServices($entity);
}
private function injectServices($entity): void
{
if ($entity instanceof PostHandlerAwareInterface) {
$entity->setPostHandler($this->postHandler);
}
}
}
class BlogPost extends PostHandlerAwareInterface
{
/** @var PostHandlerInterface */
private $postHandler;
private $status;
public function setPostHandler(PostHandlerInterface $postHandler): void
{
$this->postHandler = $postHandler;
}
public function getStatus()
{
if (null === $this->status) {
$this->postHandler->calculateStatus($this);
}
return $this->status;
}
}
class BlogPostRepository
{
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp.description')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getArrayResult();
}
}
getArrayResult
不调用生命周期回调。由于我在internet上没有找到真正类似的用例,我将选择以下解决方案,它对我来说似乎是最简单和最干净的。也许其他人会觉得这很有用
接口
interface TransientLoadable
{
public function isLoaded() : bool;
public function setLoaded(bool $loaded) : TransientLoadable;
public function setTransientLoadingFunction(\Closure $loadingFunction) :
TransientLoadable;
}
class BlogPost implements TransientLoadable
{
...
}
public function postLoad(BlogPost $post)
{
$func = function() use ($postHandler, $post)
{
//Since there may be another fields being loaded from the same API, catch them also since data is anyway in the same request
$postHandler->setAllDataFromAPI($post)
//Set the loading state to true to prevent calling the API again for the next property which may also be transient
$post->setLoaded(true);
}
$post->setTransientLoadingFunction($func)
}
class BlogPost implements TransientLoadable
{
private function getStatus() : int
{
if (!$this->isLoaded) {
call_user_function($this->loadingFunction)
}
return $this->status;
}
private function getVisitorCount() : int
{
if (!$this->isLoaded) {
call_user_function($this->loadingFunction)
}
return $this->visitorCount;
}
}
可临时加载
界面的加载
功能确保这一点。所有数据都由setAllDataFromAPI
函数加载,该函数作为闭包函数注入
我认为这不是最干净的解决办法。加载stuf应该由实体类顶部的额外层完成。由于sonataadmin不处理这样的层,我认为这个解决方案比直接将加载机制写入实体类更干净
我愿意接受其他建议或反馈
谢谢