在Doctrine中加载相关实体的子集

3
我不确定我想做的事情是否可行,但是我找不到任何相关文档。希望我只是在错误的地方寻找。
我想做的是加载一个对象,该对象将始终加载相关对象的子集。一些背景-我想保留用户删除的联系人的历史记录,因此当删除联系人时,我会将数据库中的"isDeleted"列设置为true。我很少需要加载已删除的联系人,所以稍后再考虑这个问题。但是,当我加载用户,然后加载联系人(在我的情况下通过序列化整个"user"对象),所有的联系人都被加载,包括已删除的联系人。因此,我只想加载尚未删除的联系人。这就是我现在拥有的,我不知道为什么它不起作用。
use Doctrine\Common\Collections\Criteria,
    Doctrine\Common\Collections\ArrayCollection;

class User{
/**
 * @ORM\OneToMany(targetEntity="Contacts", mappedBy="user", cascade={"persist"}, fetch="EXTRA_LAZY")
 * @ORM\JoinColumn(name="contact_id", referencedColumnName="contact_id")
 */
private $contacts;

public function __construct(){
    $this->contacts = new ArrayCollection();
}

/**
 * Get contacts
 *
 * @return \Doctrine\Common\Collections\ArrayCollection
 */
public function getContacts()
{
    // This is where I am filtering the contacts
    $criteria = Criteria::create()
    ->where(Criteria::expr()->eq("isDeleted", false));
    return $this->contacts->matching($criteria);
}

然后我在控制器中使用JMS序列化器和FOSRest来加载和序列化用户。

public function getUserAction($slug){
    $data = $this->getDoctrine()->getRepository('MyCoreBundle:User')->find($slug);
    $view = $this->view($data, 200);

    return $this->handleView($view);
}

以下是生成的JSON结果:
{
userId: 7,
creationDate: "2014-07-28T22:05:43+0000",
isActive: true,
username: "myUser",
contacts: [
    {
        contactId: 23,
        firstName: "Jim",
        lastName: "Bin",
        email: "zzz@zzz.com",
        phone: "1231231231",
        isDeleted: true
    }
]
}

我不确定为什么仍然加载已删除的用户。如果我在控制器中进行筛选而不是在实体类中进行筛选,则筛选将起作用。但我不想在应用程序中每次加载联系人时都这样做。如果我能在实体类中得到可工作的解决方案,那就太理想了。有人做过类似的事情可以起作用吗?
我正在使用Symfony 2.3。
2个回答

3
我认为你的问题在于当你从仓库中获取用户时,实际上并没有调用getContacts方法,而是使用了一个标准的Doctrine查询,由于用户和联系人之间存在关联,无论联系人是否已被删除,它们都会被检索出来。一种解决方案是创建自己的UserRepository,并添加一个findUserWithActiveContacts()方法,执行更合适的查询,然后使用它代替原有的方法。
例如: UserRepository.php
namespace Demo\MyBundle\Entity;

use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository
{
    public function findWithActiveContacts($slug)
    {
        $result= $this->createQueryBuilder('u')
            ->join('u.contacts', 'c')
            ->select('u,c')
            ->where('c.isDeleted = false')
            ->andWhere('u.slug = :slug')
            ->setParameter('slug', $slug)
            ->getQuery()
            ->getResult();

        return $result;
    }
}

将实体与新存储库连接起来:
class User{
/**
 * @ORM\OneToMany(targetEntity="Contacts", mappedBy="user", cascade={"persist"}, fetch="EXTRA_LAZY")
 * @ORM\JoinColumn(name="contact_id", referencedColumnName="contact_id")
 * @ORM\Entity(repositoryClass="Demo\MyBundle\Entity\UserRepository")
 */

然后在控制器中:

public function getUserAction($slug){
    $data = $this->getDoctrine()->getRepository('MyCoreBundle:User')->findWithActiveContacts($slug);
    $view = $this->view($data, 200);

    return $this->handleView($view);
}

只要使用findWithActiveContacts而不是find,你就可以始终获取未删除的联系人,无需更改其他内容。如果您想要的话,我认为您可能可以在客户存储库中覆盖find本身,但是对于您来说,这只会更改您调用的方法名称,我担心其他代码(例如为表单获取实体的代码)也会开始使用它,这可能是您想要的,但也可能会产生不可预测的后果。在这种情况下,官方Doctrine方法是使用过滤器,它实际上向通过Doctrine的所有内容添加了一个额外的where子句。这就是SoftDeleteable扩展如何完成的。

我也考虑过这个问题,但是我该如何让Doctrine默认使用新函数来获取联系人呢? - Sehael
如果我能让它工作,那就太好了,因为我不需要在我的数据库中更改或添加任何字段。 - Sehael

1
我强烈推荐使用“可软删除”扩展。您需要使用一个名为“deletedAt”的字段,当对象没有被删除时,该字段为null;当对象被删除时,该字段为时间戳。
具体操作如下:
/**
 * @ORM\Entity
 * @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
 */
class User
{
    /**
     * @ORM\Column(name="deletedAt", type="datetime", nullable=true)
     */
    private $deletedAt;

    public function getDeletedAt()
    {
        return $this->deletedAt;
    }

    public function setDeletedAt($deletedAt)
    {
        $this->deletedAt = $deletedAt;
    }

}

请查看这个扩展程序:可软删除扩展程序


谢谢,这是一个很棒的扩展。在研究了一下之后,我还发现了一个包含此扩展及其他扩展的Symfony Bundle。如果有其他人感兴趣,这里是这个Bundle。哈哈,真希望在构建我的应用之前就知道这个Bundle了。 - Sehael
我决定花时间修改我的数据库,以便我可以使用这个扩展,它确实是一个非常有用的扩展。感谢您指引我发现它! - Sehael

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