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)
    }
    
  • 使用内置的延迟加载机制仅在需要时从API获取属性

    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;
         }
    }
    
  • 发生了什么事?让我们想象一下,我们想要获得状态和访问者计数,这两个都是通过单个外部API调用加载的

    如果需要实体的某个依赖api的属性,那么也会加载所有其他属性(因为我们不希望每个属性都有另一个调用)。通过
    可临时加载
    界面的
    加载
    功能确保这一点。所有数据都由
    setAllDataFromAPI
    函数加载,该函数作为闭包函数注入

    我认为这不是最干净的解决办法。加载stuf应该由实体类顶部的额外层完成。由于sonataadmin不处理这样的层,我认为这个解决方案比直接将加载机制写入实体类更干净

    我愿意接受其他建议或反馈

    谢谢