Doctrine 2中的代理是什么?

116

我刚刚完成了Doctrine 2的所有文档阅读,开始使用自己的沙盒实验,我理解了大部分原则,但仍有一个问题,我在文档中找不到完整的解释。

  1. Proxy类是什么?
  2. 什么时候应该使用它们替代实体?

据我所知,代理类可以增加一层来添加一些其他特性到您的实体中,但为什么要使用代理类而不是在实体类中自己实现这些方法呢?

2个回答

159

更新

这个答案关于代理对象和部分对象之间的差异包含错误信息。请参考 @Kontrollfreak 的回答以获取更多细节:https://dev59.com/fG445IYBdhLWcg3wUYrM#17787070


当你的查询没有返回创建一个实体所需的所有数据时,就会使用代理对象。想象以下情境:

@Entity
class User {
     @Column protected $id;
     @Column protected $username;
     @Column protected $firstname;
     @Column protected $lastname;

     // bunch of setters/getters here
}

DQL query:

SELECT u.id, u.username FROM Entity\User u WHERE u.id = :id
正如您所看到的,此查询不返回firstnamelastname属性,因此无法创建User对象。创建不完整的实体可能会导致意外错误。
这就是为什么Doctrine将创建支持延迟加载的UserProxy对象。当您尝试访问未加载的firstname属性时,它将首先从数据库中加载该值。
我是说为什么我应该使用代理?
你应该总是将代码编写得好像根本没有使用代理对象。它们可以被视为Doctrine使用的内部对象。
为什么惰性加载不能在实体本身中实现?
技术上它是可以的,但看一下某个随机代理对象的类。它充满了糟糕的代码,很烦人。在实体中拥有干净的代码很好。
你能给我提供一个用例吗?
您正在显示最新的25篇文章列表,并且想要显示第一篇文章的详细信息。每篇文章都包含大量文本,因此获取所有这些数据将是浪费内存。这就是为什么您不获取不必要的数据的原因。
SELECT a.title, a.createdAt
FROM Entity\Article a
ORDER BY a.createdAt DESC
LIMIT 25

$isFirst = true;
foreach ($articles as $article) {
    echo $article->getTitle();
    echo $article->getCreatedAt();

    if ($isFirst) {
        echo $article->getContent(); // Article::content is not loaded so it is transparently loaded 
                                     // for this single article.

        $isFirst = false;
    }
}

1
部分对象和代理对象是同一件事情 - 它们可以被视为同义词。至于其他问题,请查看我的更新答案。 - Crozin
1
我不明白为什么Doctrine不能在只有一半属性的情况下创建对象。在PHP中,即使没有设置所有属性,我也能够创建一个对象。 - sanders
1
这是一个非常棒的答案,应该被写入文档中。 - Jimbo
我同意你的看法,@Jimbo。 - S.Thiongane
7
这个答案对代理和部分对象存在一些严重的误解。请查看我的答案了解原因。 - Kontrollfreak
显示剩余3条评论

91

代理

Doctrine 代理是一个包装器,它扩展实体类以为其提供延迟加载。

默认情况下,当您要求实体管理器获取与另一个实体关联的实体时,相关联的实体不会从数据库中加载,而是被包装成代理对象。当您的应用程序请求此代理实体的属性或调用其方法时,Doctrine 将从数据库中加载实体(除非您请求 ID,因为代理始终知道 ID)。

由于这个代理扩展了您的实体类,所以这个过程对于您的应用程序来说是完全透明的。

如果您在查询中没有使用JOIN语句或将提取模式设置为EAGER,Doctrine 默认会将关联作为延迟加载代理进行填充。


现在我必须补充一下,因为我没有足够的声望在任何地方发表评论:

不幸的是,Crozin 的回答包含错误信息。

如果您执行像下面这样的 DQL 查询:

SELECT u.id, u.username FROM Entity\User u WHERE u.id = :id

你将不会得到一个(被代理的)实体对象,而是一个关联数组。因此无法懒加载任何其他属性。

有了这个思路,人们就会得出结论,使用案例示例也无法工作。为了将$article视为对象访问,必须将DQL更改为类似于以下内容:

SELECT a FROM Entity\Article a ORDER BY a.createdAt DESC LIMIT 25

如果您想部分加载不是关联的实体属性,则必须明确告知Doctrine:

getContent()返回的属性必须是一个关联,以便不加载所有 25个实体的内容属性。

SELECT partial u.{id, username} FROM Entity\User u WHERE u.id = :id

这将给您一个部分加载的实体对象。

但要注意,部分对象不是代理!惰性加载不适用于它们。因此,通常情况下应避免使用部分对象,因为这是危险的。了解更多信息: 部分对象 — Doctrine 2 ORM 2 文档


1
谢谢,这篇文章提供了比被接受的答案更多关于Doctrine如何使用代理和部分对象的细节!而且对文档的引用也很有帮助。 - Sean the Bean
1
此外,供参考,这是有关代理对象的文档部分:http://doctrine-orm.readthedocs.org/en/latest/reference/advanced-configuration.html#proxy-objects - Sean the Bean
进行贪婪加载时,基本上只是添加结果集吗? - user11995521

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接