Doctrine2中的自定义集合

18
刚开始使用Doctrine2,想知道如何/是否可以使用自定义集合类。搜索结果指向文档的这部分

集合值持久字段和属性必须根据Doctrine\Common\Collections\Collection接口进行定义。应用程序可能使用集合实现类型来初始化字段或属性,使其在实体成为持久化之前。一旦实体变为托管状态(或分离状态),后续访问必须通过接口类型进行。

虽然我相信这对某些人来说非常清晰明了,但我还是有点困惑。

如果我设置我的实体以初始化(比如在__construct()中)集合变量为一个实现正确接口的类,那么Doctrine2会继续将该类用作集合吗?我理解得对吗?

更新:此外,我从各种线程中收集到的信息表明,懒加载中使用的占位符对象可能会影响如何使用自定义集合。


2
对于那些来到这里寻找答案的人:截至目前,功能仍未实现,请在功能讨论线程中做出贡献(点赞、使用案例、您的解决方案)。 - Arkemlar
@Arkemlar,请查看此内容,欢迎评论:https://gist.github.com/Itach1Uchixa/83fedd850e4705b0b51589afaefdfdf1 - Javlonbek
@Javlonbek 我最终采用了自己的方式解决了这个问题: https://gist.github.com/Arkemlar/d1dca641e9f6932d37d052af3265a71d - Arkemlar
1
自从我发布了这个Gist之后,我发现了一些错误/愚蠢的代码,但是由于时间不足,无法将修复程式从项目代码转移到Gist中。 - Arkemlar
3个回答

23

让我尝试澄清什么是可能的、不可能的和计划中的,并且通过例子来说明。

手册中的引用基本上意味着您可以拥有以下自定义实现类型:

use Doctrine\Common\Collections\Collection;

// MyCollection is the "implementation type"
class MyCollection implements Collection {
    // ... interface implementation

    // This is not on the Collection interface
    public function myCustomMethod() { ... }
}

现在你可以按如下方式使用它:

class MyEntity {
    private $items;
    public function __construct() {
        $this->items = new MyCollection;
    }
    // ... accessors/mutators ...
}

$e = new MyEntity;
$e->getItems()->add(new Item);
$e->getItems()->add(new Item);
$e->getItems()->myCustomMethod(); // calling method on implementation type

// $em instanceof EntityManager
$em->persist($e);

// from now on $e->getItems() may only be used through the interface type
换句话说,只要一个实体是NEW(不是MANAGED、DETACHED或REMOVED),你就可以自由地使用集合的具体实现类型,即使它不太美观。如果它不是NEW,你必须仅访问接口类型(最好在其上进行类型提示)。这意味着实现类型并不真正重要。当从数据库中检索到持久化的MyEntity实例时,它将不使用MyCollection(构造函数永远不会被Doctrine调用,因为Doctrine只重新构建已经存在/持久化的对象,它不会创建“新”的对象)。并且由于这样的实体是MANAGED,访问必须通过接口类型进行。

现在来看一下计划中的内容。拥有自定义集合的更美观方式是还要拥有自定义的接口类型,例如IMyCollection和MyCollection作为实现类型。然后,为了使其与Doctrine 2持久性服务完美配合,您需要实现自定义的PersistentCollection实现,例如MyPersistentCollection,它看起来像这样:

class MyPersistentCollection implements IMyCollection {
    // ...
}

在映射中告诉Doctrine使用MyPersistentCollection包装那个集合(记住,PersistentCollection一个集合实现类型,实现相同的接口,以便在委托给基础集合实现类型之前/之后可以执行所有持久化工作)。

因此,自定义集合实现由3部分组成:

  1. 接口类型
  2. 实现类型(实现接口类型)
  3. 持久性包装类型(实现接口类型)

这不仅使编写与Doctrine 2 ORM无缝配合的自定义集合成为可能,而且还可以仅编写自定义持久性包装类型,例如优化特定集合的惰性加载/初始化行为以满足特定应用程序需求。

目前还不能这样做,但将来会有。这是编写和使用完全自定义集合的唯一真正优雅和完全功能的方法,它可以完美地集成Doctrine 2提供的透明持久化方案。


这里的细节非常好,解释得非常清楚。对我来说澄清了很多文档。 - cantera
你好,现在距离你写下“计划中”的那个时候已经过去了2年,但是我看到这个功能仍然没有实现。你有任何线索这个功能何时能够被实现吗? - grongor
+1 想知道/希望这已经完成了。我正在使用Doctrine 2.3,但没有看到任何迹象表明这种方法已被启用。 - Nathan Labenz
@GRoNGoR,请查看下面答案中的Jira链接。 - Wilt
1
PersistentCollection类有一个名为unwrap()的方法,其类型提示应返回一个Collection,但似乎它总是返回一个ArrayCollection。是否有任何方法可以使unwrap()返回自定义集合类? - Matthew Webb
这里是有关特性问题的新链接(可行)。https://github.com/doctrine/doctrine2/issues/5057 如果您希望实现此功能,请投票支持此问题。 - Arkemlar

1
不,每当Doctrine返回Doctrine\Common\Collections\Collection接口的实现时,它将是一个Doctrine\ORM\PersistentCollection实例。你不能在集合上放置更多自定义逻辑。然而,这甚至不是必要的。
比如你有一个实体(订单有许多订单项),那么计算订单总价的方法不应该位于集合上,而应该位于订单项上。因为这是你领域模型中具有意义的地方。
class Order
{
    private $items;

    public function getTotalSum()
    {
        $total = 0;
        foreach ($this->items AS $item) {
            $total += $item->getSum();
        }
        return $total;
    }
}

Collections(集合)是ORM中的技术部分,它们帮助实现和管理对象之间的引用关系,仅此而已。


我的主要想法是提供排序/过滤功能,而不是对实体进行计算。 - Tim Lytle
即使是排序/过滤方法,最好也放在保存集合的实体上。但是也有一些接受闭包并为您完成工作的方法,例如:$collection->map(function($element) { return $element->getProperty(); }); - beberlei
@beberlei 但是如果多个实体具有相同的集合,则最终会复制代码(除非您使用匿名函数)。 - Tim Lytle
我同意@Tim的观点。能够使用自定义集合将是很棒的事情。一个只包含一种对象类型的集合可能有一组行为,而该对象的单个实例可能没有。这是强类型语言中非常常见的特性。 - Bryan M.
1
避免重复代码的面向对象方法不仅限于扩展对象。使用组合(辅助对象)肯定足够了。此外,集合不应与一个类型的对象列表混淆。集合只在两个实体之间的引用中有意义。但是,DQL或存储库查找方法将始终返回对象数组。 - beberlei
4
集合是执行一些操作(比如获取其中元素的总和)的理想场所。仅仅为了证明工具的限制而讨论面向对象设计是毫无意义的。 - carles

0

同样的问题在这里,附带有关于官方教义Jira问题页面的参考,其中包含了这个“特性”的详细信息和状态...你可以在那里跟踪开发进展!


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