Doctrine 2: OneToMany relation - php

/** #Entity */
class First
{
/** #OneToMany(targetEntity="Second", mappedBy="parent") */
protected $secondList;
// access methods here
public function __construct()
{
$this->secondList = new ArrayCollection();
}
}
/** #Entity */
class Second
{
/**
* #ManyToOne(targetEntity="First", inversedBy="secondList")
* #JoinColumn(name="First_id", referencedColumnName="Id")
*/
protected $parent;
}
Here is the problem with taking into ArrayCollection $secondList elements from Second class. Second ManyToOne relation is working properly. Perhaps I did something wrong in initializing a persistance (because First_Id in SQL base is null always).
$first = new Second();
$second = new First();
$first->getSecond()->add($second);
$em->persist($second);
$em->persist($first);
Any suggestions?

Doctrine2 docs say this:
In the case of bi-directional associations you have to update the fields on both sides:
This means that you'll have to do something like:
$second->setParent($first);
As $second table has the foreign key.
Or you could add a cascade={"persist"} option to the$first->secondList property.

You should make sure you close your parenthesis in the First class.
/** #OneToMany(targetEntity = "Second", mappedBy = "parent" ) */
If that is not the problem - is there any error message ?

Related

Symfony delete ManyToMany related object when one part is empty?

I'm trying to delete unused tags. Although the relationship between Post and Tag has been deleted, the post-linked tag is not deleted.
"orphanRemoval" does not work because it has deleted all. cascade "remove" does not delete.
Post Entity:
class Post implements \JsonSerializable
{
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Cms\PostTag", inversedBy="posts", cascade={"persist", "remove"})
* #ORM\JoinTable(name="post_tag_taxonomy")
* #Assert\Count(max="5")
*/
private $tags;
}
Tag Entity:
class PostTag {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Cms\Post", mappedBy="tags")
*/
private $posts;
}
Here's a similar example, but for Java. How to delete an ManyToMany related object when one part is empty?
I suggest you use preUpdate event from Doctrine life cycle callbacks. On the event of update a Post, you tell doctrine to check if there's a Tag change (in this case it's to NULL), if yes then query the Tag check if any posts still use it.
In short, you need to :
Add #ORM\HasLifecycleCallbacks before your class to enable life cycles.
Add preUpdate function in Post class :
/**
* #ORM\PreUpdate
* #param PreUpdateEventArgs $event
*/
public function clearChangeSet(PreUpdateEventArgs $event)
{
if ($event->hasChangedField('field_you_want_to_check')
) {
$em = $event->getEntityManager();
// Now use the entityManager to query the tag and check.
}
}
By doing this doctrine will do the check for you, in the logic code you just need to perform the unlinking, no need to care about delete tags there.
Update : as pointed out, for associations in entity you cannot get the changes by $event->hasChangedField, use the method in Symfony 3 / Doctrine - Get changes to associations in entity change set
Solution:
/**
* #ORM\PostUpdate()
*/
public function postUpdate(LifecycleEventArgs $args)
{
/** #var PersistentCollection $tags */
$tags = $args->getEntity()->getTags();
if ($tags->isDirty() && ($deleted = $tags->getDeleteDiff())) {
$em = $args->getEntityManager();
foreach ($deleted as $tag) {
if ($tag->getPosts()->count() === 1) {
$em->remove($tag);
$em->flush($tag);
}
}
}
}

doctrine 2 one-to-one not working as expected

I'm trying to do bi-directional one-to-one with doctrine.
User model
<?php
/**
* #Entity
* #Table(name="User")
*/
class User {
/**
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
* #OneToOne(targetEntity="Cart",mappedBy="user",cascade={"persist"})
*/
public $cart;
}
Cart model
<?php
/**
* #Entity
* #Table(name="Cart")
*/
class Cart {
/**
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
* #OneToOne(targetEntity="User",inversedBy="cart")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
public $user;
}
And I want to set the cart like that
<?php
$user->cart = new Cart;
$entityManager->flush();
The cart has been created, but the user_id is not set. (NULL)
What's wrong ?
(I've not created getters/setters for test)
So I did some extended testing to see what could be done.
Your relationship code appears correct. I'd personally drop the join column annotation, as it defaults to that anyway.
You DO need getters/setters inside each of the entities such as:
// In User entity
public function setCart(Cart $cart) {
$this->cart = $cart;
}
// In Cart entity
public function setUser(User $user) {
$this->user = $user;
}
You also need to do something like:
$user = new User();
$em->persist($user);
$cart = new Cart();
$cart->setUser($user);
$em->persist($cart);
$user->setCart($cart);
$em->flush();
Using this snippet I was able to save two relationships to the DB and load them again. The documentation specifically states that new owners with cascade do not cascade.
"Doctrine 2 does not cascade the persist operation to all nested
entities that are new as well."
In my example, I've specifically set and persisted both the new items. Existing parents with new cascading children don't have this problem.
Hopefully you can use this information to figure out exactly what needs to change.
To simplify:
You DO need getters/setters inside each of the entities such as:
// In User entity
public function setCart(Cart $cart) {
$this->cart = $cart;
}
// In Cart entity
public function setUser(User $user) {
$user->setCart($this);
$this->user = $user;
}
You also need to do something like:
$user = new User();
$em->persist($user);
$cart = new Cart();
$cart->setUser($user);
$em->persist($cart);
$em->flush();
This process "$user->setCart($cart);" is deported to setter of User in cart;
It's a pretty old question but I faced something similar and found another alternative to kinghfb answer. You can also do the following on your inverse side's entity (User in case of this question).
/**
* User's Constructor
*/
public function __construct()
{
$this->cart= new Cart();
$this->cart->setUser($this);
}
Note that you will need getters and setters as suggested in accepted answer but after doing above, you do not need to create and persist the Cart entity every time you are saving User.
Caution: This solution is for the case where a user will always have a cart.
Hope this helps someone facing similar issues. Thanks!

Doctrine ManyToOne does not cascade persist

I'm trying to do the following :
/**
* #Entity
*/
class Player {
/**
*#Column
*#Id
*/
private $uuid; //gets assigned a Uuid in the constructor
/**
* #ManyToOne(targetEntity="Team", cascade={"persist"})
* #JoinColumn(referencedColumnName="uuid")
*/
private $team;
public function setTeam(Team $team) {
$this->team = $team;
}
//...
}
/**
* #Entity
*/
class Team {
/**
* #Column
* #Id
*/
private $uuid; //gets assigned a Uuid in the constructor
//...
}
$player = new Player;
$team = new Team;
$player->setTeam($team);
$entityManager->persist($player);
$entityManager->flush();
The team is not persisted in to the database.
I do not want to call $entityManager->persist($team) as in my case, the Team is created in a part of the code where I don't have knowledge of persistence.
My expectation is that the cascade={"persist"} option should make the EntityManager also persist the Team.
Why is my expectation wrong or what am I doing wrong?
I was not mistaken... This works as expected!
Taking a second look, there was never a flush after a added the Team to the Player.
Reading my own question again, I realised what that I missed it...
I should talk to my rubber duck more often, so it seems...

Symfony2 detach entity with ManyToOne association

Background:
In my application I have an entity that has a self referencing ManyToOne association (many children can point to a single parent). And I have a feature that does mass updates on many entities at one time using the Doctrine ORM. To keep performance from dropping dramatically due to many entities being loaded I detach entities once they've been updated.
Problem:
When I detach an entity that has children and later try to update any of those children Doctrine complains that it doesn't know the parent anymore. Even if I merge the parent entity before trying to update the child.
Question:
What am I doing wrong when I detach the parent entity? I've tried doing cascade="merge" and/or "detach" on the parent column and Doctrine still complains about the parent being an unknown entity when I try to persist.
I've mocked up a simple example that reproduces this. See below.
Test Code:
Entity\Thing.php
/**
* #ORM\Entity()
* #ORM\Table(name="things")
*/
class Thing
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Thing", inversedBy="children", cascade={"detach","merge"})
* #ORM\JoinColumn(name="parentId", referencedColumnName="id", onDelete="SET NULL")
*/
protected $parent;
/**
* #ORM\OneToMany(targetEntity="Thing", mappedBy="parent")
*/
protected $children;
/**
* #ORM\Column(type="string", length=64)
*/
protected $name;
public function __construct($name = null)
{
$this->children = new ArrayCollection();
$this->name = $name;
}
// .. SNIP ...
}
Test Action:
public function testThingAction($_route)
{
$em = $this->getDoctrine()->getEntityManager();
$repo = $em->getRepository('AcmeThingBundle:Thing');
// simple setup of a couple things in the DB
$t1 = $repo->findByName('Thing1');
if (!$t1) {
$t1 = new Thing('Thing1');
$t2 = new Thing('Thing2');
$t2->setParent($t1);
$em->persist($t1);
$em->persist($t2);
$em->flush();
return $this->redirect($this->generateUrl($_route));
}
list($t1, $t2) = $repo->findAll();
// detach and re-merge Thing1
// This should cause Thing1 to be removed and then re-added
// to the doctrine's known entities; but it doesn't!?
$em->detach($t1);
$em->merge($t1);
// try to update T2
$t2->setName('Thing2 - ' . time());
$em->persist($t2);
// will fail with:
// A new entity was found through the relationship Thing#parent
$em->flush();
return array();
}
The issue is that the child has a relationship to a specific parent object that is no longer managed by Doctrine. When you call $entityManager->merge($entity) you get a new managed entity back from that function.
When you get that back, you need to manually call setParent() on each of your children with the newly managed entity.

Symfony2/Doctrine: How to re-save an entity with a OneToMany as a cascading new row

Firstly, this question is similar to How to re-save the entity as another row in Doctrine 2
The difference is that I'm trying to save the data within an entity that has a OneToMany relationship. I'd like to re-save the entity as a new row in the parent entity (on the "one" side) and then as new rows in each subsequent child (on the "many" side).
I've used a pretty simple example of a Classroom having many Pupils to keep it simple.
So me might have ClassroomA with id=1 and it has 5 pupils (ids 1 through 5). I'd like to know how I could, within Doctrine2, take that Entity and re-save it to the database (after potential data changes) all with new IDs throughout and the original rows being untouched during the persist/flush.
Lets first define our Doctrine Entities.
The Classroom Entity:
namespace Acme\TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="classroom")
*/
class Classroom
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $miscVars;
/**
* #ORM\OneToMany(targetEntity="Pupil", mappedBy="classroom")
*/
protected $pupils;
public function __construct()
{
$this->pupils = new ArrayCollection();
}
// ========== GENERATED GETTER/SETTER FUNCTIONS BELOW ============
}
The Pupil Entity:
namespace Acme\TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="pupil")
*/
class Pupil
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $moreVars;
/**
* #ORM\ManyToOne(targetEntity="Classroom", inversedBy="pupils")
* #ORM\JoinColumn(name="classroom_id", referencedColumnName="id")
*/
protected $classroom;
// ========== GENERATED FUNCTIONS BELOW ============
}
And our generic Action function:
public function someAction(Request $request, $id)
{
$em = $this->getDoctrine()->getEntityManager();
$classroom = $em->find('AcmeTestBundle:Classroom', $id);
$form = $this->createForm(new ClassroomType(), $classroom);
if ('POST' === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
// Normally you would do the following:
$em->persist($classroom);
$em->flush();
// But how do I create a new row with a new ID
// Including new rows for the Many side of the relationship
// ... other code goes here.
}
}
return $this->render('AcmeTestBundle:Default:index.html.twig');
}
I've tried using clone but that only saved the parent relationship (Classroom in our example) with a fresh ID, while the children data (Pupils) was updated against the original IDs.
Thanks in advance to any assistance.
The thing with clone is...
When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties. Any properties that are references to other variables, will remain references.
If you are using Doctrine >= 2.0.2, you can implement your own custom __clone() method:
public function __clone() {
// Get current collection
$pupils = $this->getPupils();
$this->pupils = new ArrayCollection();
foreach ($pupils as $pupil) {
$clonePupil = clone $pupil;
$this->pupils->add($clonePupil);
$clonePupil->setClassroom($this);
}
}
NOTE: before Doctrine 2.0.2 you cannot implement a __clone() method in your entity as the generated proxy class implements its own __clone() which does not check for or call parent::__clone(). So you'll have to make a separate method for that like clonePupils() (in Classroom) instead and call that after you clone the entity. Either way, you can use the same code inside your __clone() or clonePupils() methods.
When you clone your parent class, this function will create a new collection full of child object clones as well.
$cloneClassroom = clone $classroom;
$cloneClassroom->clonePupils();
$em->persist($cloneClassroom);
$em->flush();
You'll probably want to cascade persist on your $pupils collection to make persisting easier, eg
/**
* #ORM\OneToMany(targetEntity="Pupil", mappedBy="classroom", cascade={"persist"})
*/
protected $pupils;
I did it like this and it works fine.
Inside cloned Entity we have magic __clone(). There we also don't forget our one-to-many.
/**
* Clone element with values
*/
public function __clone(){
// we gonna clone existing element
if($this->id){
// get values (one-to-many)
/** #var \Doctrine\Common\Collections\Collection $values */
$values = $this->getElementValues();
// reset id
$this->id = null;
// reset values
$this->elementValues = new \Doctrine\Common\Collections\ArrayCollection();
// if we had values
if(!$values->isEmpty()){
foreach ($values as $value) {
// clone it
$clonedValue = clone $value;
// add to collection
$this->addElementValues($clonedValue);
}
}
}
}
/**
* addElementValues
*
* #param \YourBundle\Entity\ElementValue $elementValue
* #return Element
*/
public function addElementValues(\YourBundle\Entity\ElementValue $elementValue)
{
if (!$this->getElementValues()->contains($elementValue))
{
$this->elementValues[] = $elementValue;
$elementValue->setElement($this);
}
return $this;
}
Somewhere just clone it:
// Returns \YourBundle\Entity\Element which we wants to clone
$clonedEntity = clone $this->getElement();
// Do this to say doctrine that we have new object
$this->em->persist($clonedEntity);
// flush it to base
$this->em->flush();
I do this:
if ($form->isValid()) {
foreach($classroom->getPupils() as $pupil) {
$pupil->setClassroom($classroom);
}
$em->persist($classroom);
$em->flush();
}

Categories