How to get associations in Doctrine entity? - doctrine-orm

I hope it's possible in Doctrine2. I know Propel does that automatically. What I want to do is this:
I have two tables:
workflow (id, name)
inbox (id, workflow_id, name)
And two entities:
Workflow and Inbox
In my Inbox entity I, of course, have this (to relate two tables):
/**
* #ORM\ManyToOne(targetEntity="Workflow")
* #ORM\JoinColumn(nullable=false)
*/
protected $workflow;
Everything works great. However, I want to be able to get inboxes from the Workflow entity that are associated with that workflow. I can't find how to do it.
Propel does that very simple, you would just do something like this:
$workflow = WorkflowQuery::create()
->filterById(1)
->findOne(1);
$inboxes = $workflow->getInboxs()
//Propel just addes 's' to methods that return associations
How, similar this can be done in Doctrine2? Something like this:
$workflow = $this->getRepository('MyBundle:Workflow')->findById(1);
$inboxes = $workflow->getInboxes();
So, is there a way to do this? Thank you.

Change in controller:
$workflow = $this->getDoctrine()->getRepository('MyBundle:Workflow')->find(1);
$inboxes = $workflow->getInboxes();
Don't forget that you need
// Workflow entity
public function __construct()
{
// make `use` statement for this, not long
$this->inboxes = new \Doctrine\Common\Collections\ArrayCollection() ;
}
and
/**
* #ORM\OneToMany(targetEntity="Inbox", mappedBy="workflow", cascade={"persist"})
*/
protected $inboxes ;
public function getInboxes() { return $this->inboxes ; }
// setInboxes(), addInbox(), removeInbox() here

Related

doctrine: is there a way to use associated entity in findBy

I have an entity 'employee' which is associated to one or more 'manager' entities.
Therefore i use a join table and an association in the employee entity as follows:
/**
* #ManyToMany(targetEntity="manager_entity")
* #JoinTable(name="manager_employees",
* joinColumns={#JoinColumn(name="emp_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="manager_id", referencedColumnName="id", unique=true)}
* )
*/
protected $managers;
this is already working. but now i want to retrieve all employees of a specific manager.
therefore i'm asking if its possible to do something like this:
$mgr = $this->em->getRepository ( 'Entities\manager' )->findOneBy ( array (
"alias" => $this->get('alias'));
// only pseudo code - i know that $managers is a list of managers and $mgr cannot be compared to that
$empList = $this->em->getRepository('Entities\employee')->findBy(array("managers" => $mgr));
Add a function like this to your repository:
public function findByManager($managerId)
{
return $this->getEntityManager()
->createQueryBuilder()
->from('employee', 'e')
->innerJoin('e.managers m')
->where('m.Id = :managerId')
->setParameter('managerId', $managerId)
->getQuery()
->getResult();
}
And then just use:
$employee = $repository->findByManager($manager->getId());

Doctrine: Removing entity in self-referencing (many-to-many)

I would like to ask your help in deleting association.
My User entity:
class User
{
...
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="following")
**/
private $followers;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="followers")
* #ORM\JoinTable(name="friends",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
**/
private $following;
I have two actions:
Profile:follow
// followAction
$entityManager = $this->getDoctrine()->getEntityManager();
$me->addFollowing($targetUser);
$targetUser->addFollower($me);
$entityManager->persist($me);
$entityManager->persist($targetUser);
$entityManager->flush();
Profile:unfollow
$entityManager = $this->getDoctrine()->getEntityManager();
$me->removeFollowing($targetUser);
$targetUser->removeFollower($me);
$entityManager->persist($me);
$entityManager->persist($targetUser);
$entityManager->flush();
Process of following is working in a proper way, and I see appropriate records friends table.
But when I am trying to unfollow user, I receive exception:
An exception occurred while executing 'INSERT INTO friends (user_id, friend_user_id) VALUES (?, ?)' with params {"1":2,"2":10}:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2-10' for key 'PRIMARY'
What am I doing wrong? I've tried with persist and without it, the same. Maybe something in association configs?
Your first mistake is 2 persist actions, you only need one. Check this:
// class User
public function switchFollowingUser(User $user)
{
if ( $this->following->contains($user) )
$this->following->removeElement($user) ;
else
$this->following->add($user) ;
}
and controller would be just
$follower->switchFollowingUser($user) ;
Extract this method into two methods if you want but I kinda prefer this way because it is shorter.
Second thing:
Did you put
$this->following = new ArrayCollection() ;
$this->followers = new ArrayCollection() ;
in __construct() ?
Try if this works.

how works createQueryBuilder and leftJoin?

I dont get how to make it work.
I have:
a table partner with fields id and name
a table partner_address with two fields: id_partner and id_address
a table address with fields id and external key id_town which references town(id)
a table town with fields id, a name, and postal_code
I want to select all partners that are in towns with specific postal_code
This query works:
SELECT p.nom, v.nom
FROM partner p
JOIN partner_address pa
ON pa.id_partner=p.id
JOIN address a
ON pa.id_address = a.id
JOIN town t
ON a.id_town=t.id
WHERE t.postal_code='13480';
Now I want to "translate" it into Doctrine 2 full syntax, following the documentation.
So I've made a custom repository:
src/Society/Bundle/MyProjectBundle/Repository/PartnerRepository.php
In this repository, I'm trying to create the corresponding function:
<?php
namespace HQF\Bundle\PizzasBundle\Repository;
use Doctrine\ORM\EntityRepository;
class PartenaireRepository extends EntityRepository
{
/**
* Get all active partners from a given postal code.
*/
public function findAllActiveByCp($cp)
{
return $this->createQueryBuilder('p')
->where('p.dateVFin IS NULL')
->andWhere('p.cp=:cp')
->addOrderBy('p.cp', 'DESC')
->setParameter('cp', $cp);
}
}
Nota: the query in the code is not the right one but this code works in another custom repository I've made, so I'm trying to start from this code.
I'm trying something like this but it doesn't work:
public function findAllActiveByCp($cp)
{
$qb = $this->createQueryBuilder('p');
return $qb
->leftJoin('partner_address pa ON pa.id_partner=p.id')
->leftJoin('address a ON pa.id_address = a.id')
->leftJoin('town t ON a.id_ville=t.id')
->where('p.dateVFin IS NULL')
->andWhere('t.cp=:cp')
->addOrderBy('t.cp', 'DESC')
->setParameter('cp', $cp);
}
I get this error:
Warning: Missing argument 2 for Doctrine\ORM\QueryBuilder::leftJoin(),
called in
/blabla/Repository/PartenaireRepository.php
on line 18 and defined in
/blabla/symfony/vendor/doctrine/orm/lib/Doctrine/ORM/QueryBuilder.php
line 767
You have to join only properties, that the selected entity have.
In first parameter of join() or leftJoin() or xxxJoin() you pass the attribute name related to selected object, and in the second - alias for joined entity.
Try similar to this:
$q = $this->em()->createQueryBuilder();
$q->select(['item', 'itemContact'])
->from('ModuleAdmin\Entity\CustomerEntity', 'item')
->leftJoin('item.contacts', 'itemContact')
->andWhere($q->expr()->like('item.name', ':customerNameStart'));
Of course, the CustomerEntity contains OneToMany relation in field contacts.
Remember, that in select statement you have to select the root entity (in my example CustomerEntity aliased as item).
Edit by Olivier Pons to add how I found out the solution, and to mark this answer as valid, because it put me on the right track, thank you Adam!
In the file PartenaireRepository.php I've used the createQueryBuilder('p') properly. Here's how to make two joins in a row, using createQueryBuilder():
class PartenaireRepository extends EntityRepository
{
/**
* Retrieval of all partners given for a given postal code.
*/
public function findAllActiveByCp($cp)
{
return $this->createQueryBuilder('p')
->leftJoin('p.adresses', 'a')
->leftJoin('a.ville', 'v')
->where('v.cp=:cp')
->setParameter('cp', $cp);
... blabla
}
}
I believe for what you're doing, you will need to provide four arguments to the leftJoin method.
->leftJoin('partner_address', 'pa', 'ON', 'pa.id_partner = p.id')
So your query builder chain should look like this
public function findAllActiveByCp($cp)
{
$qb = $this->createQueryBuilder('p');
return $qb
->leftJoin('partner_address', 'pa', 'ON', 'pa.id_partner = p.id')
->leftJoin('address', 'a', 'ON', 'pa.id_address = a.id')
->leftJoin('town', 't', 'ON', 'a.id_ville = t.id')
->where('p.dateVFin IS NULL')
->andWhere('t.cp=:cp')
->addOrderBy('t.cp', 'DESC')
->setParameter('cp', $cp)
;
}

Join table is not updated in ManyToMany association in doctrine 2

I have tow entities Slaplans and Slaholidays and a join table slaplans_slaholidays.
After creating two Slaholidays objects, I persist them both, add them to the Slaplans and flush. The problem is that only the slaplans and slaholidays tables are updated, but the join table isn't.
Slaplans Entity :
<?php
namespace ZC\Entity;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Slaplans
*
* #Table(name="slaplans")
* #Entity(repositoryClass="Repositories\Slaplans")
*/
class Slaplans
{
/*
* #ManyToMany(targetEntity="Slaholidays",inversedBy="plans", cascade={"ALL"})
* #JoinTable(name="slaplans_slaholidays",
* joinColumns={#JoinColumn(name="slaplanid" ,referencedColumnName="slaplanid")},
* inverseJoinColumns={#JoinColumn(name="slaholidayid" ,referencedColumnName="slaholidayid")})
* }
*/
private $holidays;
public function __construct()
{
$this->holidays = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getHolidays() {
return $this->holidays;
}
public function setHolidays($holidays)
{
$this->holidays=$holidays;
}
/*public function addHoliday($holiday) {
$this->holidays[]=$holiday;
}*/
}
Slaholidays Entity:
<?php
namespace ZC\Entity;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Slaholidays
*
* #Table(name="slaholidays")
* #Entity(repositoryClass="Repositories\Slaholidays")
*/
class Slaholidays
{
/**
* #var integer $slaholidayid
*
* #Column(name="slaholidayid", type="integer", nullable=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
*/
private $slaholidayid;
/*
* #ManyToMany(targetEntity="Slaplans",mappedBy="holidays", cascade={"ALL"})
*/
private $plans;
/*public function getPlans(){
return $this->plans;
}*/
}
Code to persist the entities:
$allholidays=array();
$holiday=$this->_em->getRepository('ZC\Entity\Slaholidays')->find($value);
$holiday=new ZC\Entity\Slaholidays();
//..sets holiday fields here
$this->_em->persist($holiday);
$allholidays[]=$holiday;
$slaplan->setHolidays($allholidays);
foreach ($slaplan->getHolidays() as $value) {
$this->_em->persist($value);
}
$this->_em->persist($slaplan);
$this->_em->flush();
The are two issues in your code:
The first one: you are persisting each Slaholiday twice: first with
$this->_em->persist($holiday);
and second with
foreach ($slaplan->getHolidays() as $value) {
$this->_em->persist($value);
}
There is no problem actually, as they are not actually persisted in the db until flush are called, but anyway, you don't need that foreach.
The reason why your join table is not updated is in $slaplan->setHolidays method. You are initializing $slaplan->holidays with ArrayCollection (which is right) and in setHolidays you set it to the input parameter (which is $allholidays Array, and this is not right).
So, the correct way to do that is to use add method of the ArrayCollection
public function setHolidays($holidays)
{
//$this->holidays->clear(); //clears the collection, uncomment if you need it
foreach ($holidays as $holiday){
$this->holidays->add($holiday);
}
}
OR
public function addHolidays(ZC\Entity\Slaholiday $holiday)
{
$this->holidays->add($holiday);
}
public function clearHolidays(){
$this->holidays->clear();
}
//..and in the working script...//
//..the rest of the script
$this->_em->persist($holiday);
//$slaplan->clearHolidays(); //uncomment if you need your collection cleaned
$slaplan->addHOliday($holiday);
Although Doctrine checks the owning side of an association for things that need to be persisted, it's always important to keep both sides of the association in sync.
My advise is to have get, add and remove (no set) methods at both sides, that look like this:
class Slaplans
{
public function getHolidays()
{
return $this->holidays->toArray();
}
public function addHoliday(Slaholiday $holiday)
{
if (!$this->holidays->contains($holiday)) {
$this->holidays->add($holiday);
$holiday->addPlan($this);
}
return $this;
}
public function removeHoliday(Slaholiday $holiday)
{
if ($this->holidays->contains($holiday)) {
$this->holidays->removeElement($holiday);
$holiday->removePlan($this);
}
return $this;
}
}
Do the same in Slaplan.
Now when you add a Slaholiday to a Slaplan, that Slaplan will also be added to the Slaholiday automatically. The same goes for removing.
So now you can do something like this:
$plan = $em->find('Slaplan', 1);
$holiday = new Slaholiday();
// set data on $holiday
// no need to persist $holiday, because you have a cascade={"ALL"} all on the association
$plan->addHoliday($holiday);
// no need to persist $plan, because it's managed by the entitymanager (unless you don't use change tracking policy "DEFERRED_IMPLICIT" (which is used by default))
$em->flush();
PS: Don't use cascade on both sides of the association. This will make things slower than necessary, and in some cases can lead to errors. If you create a Slaplan first, then add Slaholidays to it, keep the cascade in Slaplan and remove it from Slaholiday.

Doctrine 2 - Insert new item in database

I'm trying to make something very simple.. but I do wrong, and I don't know what is the problem. Just I'm trying to insert new item to database with Doctrine 2:
$favouriteBook = new UserFavouriteBook;
$favouriteBook->user_id = 5;
$favouriteBook->book_id = 8;
$favouriteBook->created_at = new DateTime("now");
$this->_em->persist($favouriteBook);
$this->_em->flush();
As you can see.. is very simple, but that, give me next error:
Error: Message: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null
Obviosly, if I make a "dump" before "persist" and "flush" of $favouriteBook, all looks be correct..
This is my "favouriteBook" entity:
/** #Column(type="integer")
* #Id
*/
private $user_id;
/** #Column(type="integer")
* #Id
*/
private $book_id;
/**
* #ManyToOne(targetEntity="Book", inversedBy="usersFavourite")
* #JoinColumn(name="book_id", referencedColumnName="id")
*/
private $book;
/**
* #ManyToOne(targetEntity="User", inversedBy="favouriteBooks")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/** #Column(type="datetime") */
private $created_at;
public function __get($property) {
return $this->$property;
}
public function __set($property, $value) {
$this->$property = $value;
}
Anyone can image what is the problem? .. I don't know what else try.. Thanks
I think what beberlei is saying is that within your favouriteBook entity, you don't need to define the user_id and book_id as class properties, b/c the book and user properties you have set already recognize these as the relevant join columns. Also, your attempt to persist the favouriteBook entity failed because you need to set the book and user entity associations within the favouriteBook entity, not the foreign keys. So it would be:
$favouriteBook = new UserFavouriteBook;
$favouriteBook->book = $book;
$favouriteBook->user = $user;
$favouriteBook->created_at = new DateTime("now");
$this->_em->persist($favouriteBook);
$this->_em->flush();
You are mapping foreign keys and the associations. You have to modify the association not the foreign key field. Its bad-practice to map them both, you should remove $book_id and $user_id completly.