我有一个实体BlogPost
,其属性为status
。此状态属性取决于通过规范postLoad事件处理的外部API调用。所有其他属性都存储在本地数据库中。
public function postLoad(BlogPost $post)
{
$this->postHandler->calculateStatus($post);
}
问题是,在某些情况下,我根本不想计算状态。例如,如果我只想获得所有博客文章中的description
。
使用上面的代码,即使我只是想从本地数据库获取值,所有正在加载的博客实体都将触发postLoad事件。那是非常昂贵的,不能接受的。
例如,在我的存储库类中,我想获取所有具有网站的BlogPost而不调用postLoad事件。
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getResult();
}
有没有办法说“是的,请加载BlogPost集合,但不要触发事件!” ???
还有其他方法吗?自定义事件?
谢谢
答案 0 :(得分:0)
为什么不只是将此逻辑移到发布实体和事件侦听器之外?如果您知道何时需要计算状态,则可以明确进行。 例如
$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 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();
希望这会有所帮助。
PS。如果您只想获取所有博客文章的描述,可以这样做:
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
不会调用生命周期回调。
答案 1 :(得分:0)
由于我没有在互联网上找到真正的类似用例,因此我将寻求以下解决方案,这对我来说似乎是最简单,最可接受的解决方案。也许其他人会发现这很有用。
实现TransientLoadable
接口
interface TransientLoadable
{
public function isLoaded() : bool;
public function setLoaded(bool $loaded) : TransientLoadable;
public function setTransientLoadingFunction(\Closure $loadingFunction) :
TransientLoadable;
}
实施实体
class BlogPost implements TransientLoadable
{
...
}
在PostLoad事件上设置加载功能
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依赖属性,那么也会加载所有其他属性(因为我们不想为每个属性都拥有另一个调用)。这是通过loaded
接口的TransientLoadable
功能来确保的。 setAllDataFromAPI
函数将加载所有数据,该函数作为闭包函数注入。
我认为这不是最干净的解决方案。加载stuf应该在实体类的顶部额外一层进行。由于sonata admin不会处理此类层,因此我认为此解决方案比直接将加载机制直接写入实体类更干净。
我愿意接受其他建议或反馈
谢谢