Join table is not updated in ManyToMany association in doctrine 2 - doctrine-orm

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.

Related

How to join column and target entity manually in manytoone relation?

I have two fields : account_type_id and account_id.
How can i manually map doctrine TargetEntity to join Company Entity if accountTypeId = 1 OR join User Entity if account_type_id = 2 ?
<?php
/** #Entity */
class Accounts
{
// 1= Company, 2 = User
private $accountType;
/**
* #ManyToOne(targetEntity="Companies")
*/
private $company;
/**
* #ManyToOne(targetEntity="Users")
*/
private $user;
//...
}
Unfortunately, joining different columns on the fly cannot be done automatically, but you can have both fields set as nullable and only set the correct one when persisting the Account entity.
This would be the annotation:
/**
* #ORM\ManyToOne(targetEntity="Users", inversedBy="users")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private $user;
Keep in mind that nullable=true is the default anyway, I'm just being specific here.
If you want to go defensively about this, you can have an additional check in getter
/**
* #return User
* #throws \Exception
*/
public function getUser()
{
if ($this->accountType !== 2) {
throw new \Exception("Entity is not of type 'user'");
}
return $this->user;
}

Doctrine: Is it possible to preload child/related N:N entities with single DB query?

Given the following parent-child entities, how can I preload all child C entities with only single database query when I have many P (already loaded) enties?
/**
* #ORM\Entity
**/
class P {
/** #var Collection #ORM\ManyToMany(targetEntity="C") */
public $childs;
}
/**
* #ORM\Entity
**/
class C {
/** #var int #ORM\Column(type="integer") **/
public $v;
}
Test case, this code should not issue any additional database query once preloaded.
foreach ($ps as $p) {
foreach ($p->childs as $child) { $dummy = $child->v; }
}
The following query preloads all N:N child entities in one query.
note: performance is only about 0.5 times better than foreach over non preloaded data. Probably because the P entity (in my applied case) contained a lot of fields.
$em->createQueryBuilder()->select('p', 'c')
->from(P, 'c')
->leftJoin('l.childs', 'c') // preload
->where('p.id IN (:ps)')->setParameter('ps', $ps)
->getQuery()->getResult();

Add Method not working in a many to many relationship

I have this many to many relationship between bus and driver .
This is the bus entity :
/**
* #var ArrayCollection<Driver> The driver of this bus.
* #ORM\ManyToMany(targetEntity="Driver", inversedBy="bus" , cascade={"persist"})
* #ORM\JoinTable(name="bus_driver")
* #ORM\JoinColumn(name="driver_id", referencedColumnName="id")
* */
private $driver;
public function __construct() {
$this->driver = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addDriver($driver) {
$this->driver[] = $driver;
return $this;
}
And this is the driver entity :
/**
* #var ArrayCollection<Bus> The buses of this driver
* #ORM\ManyToMany(targetEntity="Bus", mappedBy="driver")
* #ORM\JoinTable(name="bus_driver")
* #ORM\JoinColumn(name="bus_id", referencedColumnName="id")
*/
private $bus;
public function __construct() {
$this->bus = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addBus($bus) {
$this->bus[] = $bus;
$bus->addDriver($this);
return $this;
}
My problem is that when I add a bus with a driver the relation is persisted but not when I add a driver whih a bus . It works only from the bus side.
please, consider renaming $driver into $drivers, as there is multiple drivers (same for bus -> buses)
and then you should try that:
#ORM\ManyToMany(targetEntity="xxx", cascade={"persist"})
more details: http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations
Change these (add the null and call it 'drivers'):
use Doctrine\Common\Collections\ArrayCollection;
...
private $drivers = null;
public function __construct() {
$this->drivers = new ArrayCollection();
}
public function addDriver($driver) {
$this->drivers[] = $driver;
return $this;
}
Also, to resolve the problem from the Bus Entity side, you might (but I'm not sure) need this change:
public function addDriver($driver) {
$driver->addBus($this);
$this->drivers[] = $driver;
return $this;
}
Try it, since I have a similar scenario in a ManyToOne relation, and I'm wondering if the above change might work.
The only working scenario was when I had :
A setter for the drivers collection .
An addDriver Method .
A removeDriver Method.
If I remove one of the pervious , addDriver won't even trigger at all .

Check If Record Has References

I wonder if there is any way to find out if a record has references.
I have the following entity:
class Item extends Entity
{
/**
* #Id #GeneratedValue
* #Column(name="id")
*/
protected $id;
/**
* #OneToMany(targetEntity="Citation", mappedBy="citation")
*/
protected $citedIn;
/**
* #Column(name="stamped", type="boolean")
*/
protected $stamped;
/** ... rest of the code ... */
}
And I want to perform the following check:
$qb
->select("
COUNT(i.id),
WHEN (i.stamped = true) THEN 'stamped'
WHEN (i.citedIn IS NOT NULL) THEN 'cited'
ELSE 'just started'
END current_status
")
->from(Entity\Item::class, 'i')
->groupBy('current_status');
Note that I tried to use i.citedIn IS NOT NULL, but it didn't work. What could I use instead?
Since it is a OneToMany relationship I think you need to use IS NOT EMPTY instead of IS NOT NULL. Doctrine will create an empty ArrayCollection for each entity, so technically it's not null, because it's an empty array.
Update
If that doesn't work, try NOT INSTANCE OF YourApp\Model\Citation.

doctrine2 foreign key gets not created

I have two simple entities connected to each other with a OneToMany association.
/**
* #ORM\Entity(repositoryClass="Shopware\CustomModels\JoeKaffeeAbo\Client")
* #ORM\Table(name="joe_kaffee_abo_client")
*/
class Client extends ModelEntity
{
public function __construct() {
$this->abos = new ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="Shopware\CustomModels\JoeKaffeeAbo\Abo", mappedBy="client", cascade= "all")
* #var \Doctrine\Common\Collections\ArrayCollection
*/
protected $abos;
public function addAbo($abo)
{
//$this->abos[] = $abo;
$this->abos->add($abo);
}
The second one is Abo:
/**
* #ORM\Entity(repositoryClass="Shopware\CustomModels\JoeKaffeeAbo\Abo")
* #ORM\Table(name="joe_kaffee_abo_abos")
*/
class Abo extends ModelEntity
{
/**
* #ORM\ManyToOne(targetEntity="Shopware\CustomModels\JoeKaffeeAbo\Client",inversedBy="abos")
* #ORM\JoinColumn(name="clientId", referencedColumnName="id")
*/
protected $client;
}
Somehow the join column gets not filled - it seems that doctrine ignores the association I set up. And if I call $client->addAbo($abo) there gets not relation created...and I cant receive the added abos via a getter function which returns the abo array collection.