I am not sure if what I want to do is possible or not, but I can't find any documentation on it. Hopefully I am just looking in the wrong places.
What I would like to do is load an object that will always load a subset of a related object. some background - I want to keep a history of the contacts that a user deletes, so when a contact is deleted, I set a isDeleted
column in the db to true. I will rarely want to load the deleted contacts, so I will worry about that later. But when I load a user, and then load the contacts (in my case by serializing the whole user
object), all of the contacts are loaded including the deleted ones. So I would like to only load the contacts that have not been deleted yet. This is what I have right now, and I don't know why it is not working.
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);
}
And then I am loading and serializing a user using the JMS Serializer and FOSRest in my Controller.
public function getUserAction($slug){
$data = $this->getDoctrine()->getRepository('MyCoreBundle:User')->find($slug);
$view = $this->view($data, 200);
return $this->handleView($view);
}
And the result is the following 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
}
]
}
I am not sure why this is still loading the deleted user. If I do the filtering in my controller instead of the Entity class, then the filtering works. But I don't want to have to do that everywhere in my application that a contact may be loaded. If I could get something working in the Entity class, that would be ideal. Has anyone done something similar that could work?
I'm using Symfony 2.3
I'd highly recommend the Soft-Deleteable extension. You'd have to use a deletedAt
field which is null
when the object is not deleted, and a timestamp
if deleted.
It goes like this:
/**
* @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;
}
}
Have a look at this extension here: Soft-Deleteable Extension
I think your problem is that getContacts
doesn't actually get called when you fetch a User from its repository, that's done using a standard Doctrine query, and since User and Contacts are related regardless of whether a Contact has been deleted, they all come across. One solution would be to create your own UserRepository with a findUserWithActiveContacts()
method, which executes a more appropriate query, and use that instead.
For example:
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;
}
}
And hook up the entity to the new repository:
User.php
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")
*/
And then in the controller:
public function getUserAction($slug){
$data = $this->getDoctrine()->getRepository('MyCoreBundle:User')->findWithActiveContacts($slug);
$view = $this->view($data, 200);
return $this->handleView($view);
}
So as long as you use findWithActiveContacts
instead of find
you should always get just the non-deleted contacts, without having to change anything else.
I think you could probably override find
itself in your customer Repository if you wanted, but for you that would just change the name of the method you're calling, and I'd be worrying that other code (e.g. stuff which fetches entities for forms) would start using it, which might be what you want but also might have unpredictable consequences.
The official Doctrine way of tweaking all queries in this sort of fashion is to use Filters, which actually slips in an extra where
clause to everything which goes through Doctrine. This is how the SoftDeleteable extension does it.