doctrine2额外的懒惰提取关联

And*_*708 4 php doctrine-orm

我有一个与Thread实体有OneToMany关联的Message实体.我正在使用DQL查询获取一个线程,我想将其消息量限制为10.因此,我将获取模式设置EXTRA_LAZY为如下所示.

class Thread
{
    // ...

    /**
     * @var ArrayCollection
     * @ORM\OneToMany(targetEntity="Profile\Entity\Message", mappedBy="thread", fetch="EXTRA_LAZY")
     * @ORM\OrderBy({"timeSent" = "ASC"})
     */
    protected $messages;
}
Run Code Online (Sandbox Code Playgroud)

这允许我使用该slice方法向LIMIT数据库发出SQL查询.到目前为止都很好.因为我的消息是加密的,所以在将线程对象处理到控制器(并最终查看)之前,我需要在服务层中对它们进行解密.为实现这一目标,我在服务中执行以下操作:

foreach ($thread->getMessages()->slice(0, 10) as $message) {
    // Decrypt message
}
Run Code Online (Sandbox Code Playgroud)

调用slice触发一个获取10条消息的SQL查询.在我看来,我正在执行以下操作来呈现线程的消息:

$this->partialLoop()->setObjectKey('message');
echo $this->partialLoop('partial/thread/message.phtml', $thread->getMessages());
Run Code Online (Sandbox Code Playgroud)

问题是这会从数据库中获取整个消息集合.如果我slice在我的服务中调用,LIMIT 10则向数据库发出相同的SQL查询,这是不可取的.

如何在我的服务层中处理有限的消息集合而不在我的视图中发出另一个SQL查询?也就是说,要让doctrine创建单个SQL查询,而不是两个.我可以简单地在我的视图中解密我的消息,但在这种情况下,这种方式会破坏服务层的目的.我当然可以"手动"获取消息并将它们添加到线程对象中,但如果我可以通过关联自动完成,那么这将是更受欢迎的.

提前致谢!

Jas*_*wer 5

与大多数建议略有不同的方法怎么样:

切片

Thread实体中,有一个返回消息片段的专用方法:

class Thread
{
    // ...

    /**
     * @param  int      $offset
     * @param  int|null $length
     * @return array
     */
    public function getSliceOfMessages($offset, $length = null)
    {
        return $this->messages->slice($offset, $length);
    }
}
Run Code Online (Sandbox Code Playgroud)

这样可以轻松地在视图中检索切片,而不会有获取整个集合的风险.

解密邮件内容

接下来,您需要解密的消息内容.

我建议你创建一个可以处理加密/解密的服务,让Message实体依赖它.

class Message
{
    // ...

    /**
     * @var CryptService
     */
    protected $crypt;

    /**
     * @param CryptService $crypt
     */
    public function __construct(CryptService $crypt)
    {
        $this->crypt = $crypt;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你必须Message通过传递CryptService给它来创建实体.您可以在创建Message实体的服务中对其进行管理.

但这只会照顾实例化的Message实体,而不是Doctrine实例化的实体.为此,您可以使用该事件.PostLoad

创建一个事件监听器:

class SetCryptServiceOnMessageListener
{
    /**
     * @var CryptService
     */
    protected $crypt;

    /**
     * @param CryptService $crypt
     */
    public function __construct(CryptService $crypt)
    {
        $this->crypt = $crypt;
    }

    /**
     * @param LifecycleEventArgs $event
     */
    public function postLoad(LifecycleEventArgs $event)
    {
        $entity = $args->getObject();

        if ($entity instanceof Message) {
            $message->setCryptService($this->crypt);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

只要Doctrine加载一个事件监听器,它就会CryptServiceMessage实体注入一个.

在应用程序的bootstrap/configuration阶段注册事件监听器:

$eventListener = new SetCryptServiceOnMessageListener($crypt);
$eventManager  = $entityManager->getEventManager();
$eventManager->addEventListener(array(Events::postLoad), $eventListener);
Run Code Online (Sandbox Code Playgroud)

将setter添加到Message实体:

class Message
{
    // ...

    /**
     * @param CryptService $crypt
     */
    public function setCryptService(CryptService $crypt)
    {
        if ($this->crypt !== null) {
            throw new \RuntimeException('We already have a crypt service, you cannot swap it.');
        }

        $this->crypt = $crypt;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,setter可以防止交换CryptService(只需要在没有时设置它).

现在,无论您或Doctrine是否实例化,Message实体都将始终具有CryptService依赖关系!

最后我们可以使用CryptService加密和解密内容:

class Message
{
    // ...

    /**
     * @param string $content
     */
    public function setContent($content)
    {
        $this->content = $this->crypt->encrypt($content);
    }     

    /**
     * @return string
     */
    public function getContent()
    {
        return $this->crypt->decrypt($this->content);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

在视图中,您可以执行以下操作:

foreach ($thread->getSliceOfMessages(0, 10) as $message) {
    echo $message->getContent();
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,这很简单!

另一个专家是内容只能以加密形式存在于Message实体中.您永远不会意外地将未加密的内容存储在数据库中.