Doctrine - how to keep ManyToOne synchronized? - symfony

After reading the Doctrine reference and the Symfony tutorial on it, I started integrating it in a project. I'm experiencing a problem I thought doctrine could solve:
I want to have Libraries with many Collections, which I assume to be a 'ManytoOne' relationship as the Collection will keep the foreign key.
Some snippets:
In Library:
/**
*
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Collection", mappedBy="library")
*/
private $collections;
In collection:
/**
* #var Library
*
* #ORM\ManyToOne(targetEntity="Library", inversedBy="collections")
* #ORM\JoinColumn(name="library_id", referencedColumnName="id")
*/
private $library;
Since most of this annotations are default and could be left out it's a pretty basic setup.
A sample controller code:
$library = new Library();
$library->setName("Holiday");
$library->setDescription("Our holiday photos");
$collection = new Collection();
$collection->setName("Spain 2011");
$collection->setDescription("Peniscola");
$library->addCollection($collection);
$em=$this->getDoctrine()->getManager();
$em->persist($collection);
$em->persist($library);
$em->flush();
The code above won't set the library_id column in the Collection table, which I assume is because Library is not the owner.
$library = new Library();
$library->setName("Holiday");
$library->setDescription("Our holiday photos");
$collection = new Collection();
$collection->setName("Spain 2011");
$collection->setDescription("Peniscola");
$collection->setLibrary($library); <--- DIFFERENCE HERE
$em = $this->getDoctrine()->getManager();
$em->persist($collection);
$em->persist($library);
$em->flush();
Works. But I want to be able to use the library add and remove methods.
Is it common to alter these add and remove methods to call the setLibrary method?
public function addCollection(\MediaBox\AppBundle\Entity\Collection $collections)
{
$this->collections[] = $collections;
$collections->setLibrary($this);
return $this;
}
and
public function removeCollection(\MediaBox\AppBundle\Entity\Collection $collections)
{
$this->collections->removeElement($collections);
$collections->setLibrary(null);
}
I don't think that's very nice.
Is it best practice in doctrine or ORM in general?
Kind regards and thanks in advance!

Obviously this is the way to go.
For the case someone needs it:
http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/working-with-associations.html#association-management-methods
Explains this problem.

Related

Doctrine2 cascade default value

I'm actually learning Symfony3 and more precisely the Doctrine2 relation between objects and I was wondering if there is a default value for the cascade parameter when you don't explicite it.
I seen in tutorials when it's necessary to use the remove value that the parameter is not specified, but there is no explanation about this fact.
So I mean is this
/**
* #ORM\ManyToOne(targetEntity="UTM\ForumBundle\Entity\UtmWebsiteTopics")
* #ORM\JoinColumn(nullable=false)
*/
private $topic;
equivalent to that ?
/**
* #ORM\ManyToOne(targetEntity="UTM\ForumBundle\Entity\UtmWebsiteTopics", cascade={"remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $topic;
Thank you for reading and I hope you'll be able to bring me an answer. :D
In short, those two snippets are not the same. If you were to want to delete a specific entity that has relations to others through FK, you would need to explicitly remove() the related entities to avoid Integrity Constraint Violations.
Examples of each
Not defining cascade={"remove"}
public function removeEntityAction($id)
{
// Get entity manager etc....
$myEntity = $em->getRepository("MyEntity")->findBy(["id" => $id]);
foreach($myEntity->getTopics() as $topic) {
$em->remove($topic);
}
$em->remove($myEntity);
}
Defining cascade={"remove"}
public function removeEntityAction($id)
{
// Get entity manager etc....
$myEntity = $em->getRepository("MyEntity")->findBy(["id" => $id]);
$em->remove($myEntity);
}
Doctrine Cascade Operations
Doctrine - Removing Entities

Symfony entities without relational

I work with Symfony2 and Doctrine and I have a question regarding entities.
In a performance worries, I'm wondering if it is possible to use an entity without going all the associations?
Currently, I have not found another way to create a model inheriting the class with associations and associations specify NULL in the class that inherits.
thank you in advance
OK, a little detail, it's for a API REST (JSON).
This is my class :
/**
* Offerequipment
*
* #ORM\Table(name="offer_equipment")
* #ORM\Entity(repositoryClass="Charlotte\OfferBundle\Repository\Offerequipment")
*/
class Offerequipment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\OfferBundle\Entity\Offer")
* #ORM\JoinColumn(name="offer_id", referencedColumnName="id")
*/
private $offer;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\ProductBundle\Entity\Equipment")
* #ORM\JoinColumn(name="equipment_id", referencedColumnName="id")
*/
private $equipment;
/**
* #VirtualProperty
*
* #return String
*/
public function getExample()
{
return $something;
}
and with QueryBuilder method, i can't get my virtual properties or getters.
Thanks for your help :)
Look at Serialization.
By serialising your entities, you can choose to exclude or expose a property of an entity when you render it.
Look at the Symfony built-in Serializer and/or JMSSerializer.
Otherwise, you can use QueryBuilder and DQL to choose what fields you want to fetch in your queries.
Like this, you can make your own find method in the Repository of your entities.
// src/AcmeBundle/Repository/FooRepository
class FooRepository extends \Doctrine\ORM\EntityRepository
// ...
public function find($id) {
$queryBuilder = $this->createQueryBuilder('e')
->select('e.fieldA', 'e.fieldB') // selected fields
->where('e.id = :id') // where statement on 'id'
->setParameter('id', $id);
$query = $queryBuilder->getQuery();
$result = $query->getResult();
}
// ...
}
Don't forget define the Repository in the corresponding Entity.
/**
* Foo.
*
* #ORM\Entity(repositoryClass="AcmeBundle\Repository\FooRepository")
*/
class Foo
{
// ...
}
By default Doctrine will not automatically fetch all of the associations in your entities unless you specifically each association as EAGER or unless you are using a OneToOne association. So if you are looking to eliminate JOINs, you can just use Doctrine in its default state and it won't JOIN anything automatically.
However, you this will not alleviate all of your performance concerns. Say, for example, you are displaying a list of 50 products in your application on a single page and you want to show their possible discounts, where discounts are an association on your product entity. Doctrine will create 50 additional queries just to retrieve the discount data unless you explicitly join the discount entity in your query.
Essentially, the Symfony profiler will be your friend and show you when you should be joining entities on your query - don't just think that because you aren't joining associations automatically that your performance will always be better.
Finally, after many days, I've found the solution to select only one entity.
VirtualProperties are found :)
public function findAllByOffer($parameters)
{
$queryBuilder = $this->createQueryBuilder('oe');
$queryBuilder->select('oe, equipment');
$queryBuilder->join('oe.equipment', 'equipment');
$result = $queryBuilder->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)->getResult();
return $result;
}

Remove all related children in Symfony entity

Suppose we have a field with ManyToMany relation as
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Users")
* #ORM\JoinTable(name="users_roles",
* joinColumns={#ORM\JoinColumn(name="User_Id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="Role_Id", referencedColumnName="id")})
*/
protected $userRole;
To remove one related element from table we can have this function in our entity:
/**
* Remove userRole
* #param \Acme\MyBundle\Entity\Users $user
*/
public function remvoveUserRole(\Acme\MyBundle\Entity\Users $user)
{
$this->userRole->removeElement($user);
}
The Question:
The ArrayCollection type has the function removeElement which is used to remove one element of the relationship. There is another function clear which in api says Clears the collection, removing all elements, therefore can I have a function like below in my entity to clear all the related elements so that by flushing it removes all?
/**
* Remove all user Roles
*/
public function remvoveAllUserRole()
{
$this->userRole->clear();
}
will it work for just ManyToMany related tables or it might work for ManyToOne, too?
Ţîgan Ion is right - removeElement/clear only removes those elements from memory.
However, I think you could achieve something as close depending on how did you configure cascade and orphanRemoval in your relationship.
$em = ...; // your EntityManager
$roles = $user->getRoles();
$roles->clear();
$user = $em->merge($user); // this is crucial
$em->flush();
In order for this to work, you need to configure User relationship to
cascade={"merge"} - this will make $em->merge() call propagate to roles.
orphanRemoval = true - since this is #ManyToMany, this will make EntityManager remove free-dangling roles.
Can't test this now, but as far as I can see it could work. I will try this out tomorrow and update the answer in need be.
Hope this helps...
Note: This logic works for ManyToMany relationship, but not for ManyToOne
I tested the way to delete all related roles for specific use (ManyToMany) and it worked. What you need is to define a function in your UserEntity as
/**
* Remove all user Roles
*/
public function remvoveAllUserRole()
{
$this->userRole->clear();
}
Then in your controller or anywhere else(if you need) you can call the function as below
$specificUser = $em->getRepository('MyBundle:Users')->findOneBy(array('username' => 'test user'));
if (!empty($specificUser)) {
$specificUser->removeAllUserRole();
$em->flush();
}
Then it will delete all related roles for the test user and we don't need to use the for loop and remove them one by one
if I'm not mistaken, this will not work, you have to delete the "role
s" from the arrayCollection directly from the database
$roles = $user->getRoles()
foreach $role from $roles
$em->remove($role);
$em->flush();
now you should get an empty collection
p.s: the best way is to test your ideas

Doctrine2 deletes entity when removing ManyToOne Relation

In my setup I have a simple OneToMany relation without cascading or orphan removal.
class Position {
/**
* #var \Vorgaenge\Basis\DBBundle\Entity\Vorgang
*
* #ORM\ManyToOne(targetEntity="\Vorgaenge\Basis\DBBundle\Entity\Vorgang", inversedBy="Positionen")
* #ORM\JoinColumn(name="VID", referencedColumnName="VID")
*/
protected $Vorgang;
}
class Vorgang {
/**
* #var \Vorgaenge\Basis\DBBundle\Entity\Position
*
* #ORM\OneToMany(targetEntity="\Vorgaenge\Basis\DBBundle\Entity\Position", mappedBy="Vorgang")
* #ORM\OrderBy({"PID" = "ASC"})
*/
protected $Positionen;
}
All I do in my unittest is creating related entities ....
$entity = new \Vorgaenge\Basis\DBBundle\Entity\Vorgang();
$pos = new \Vorgaenge\Basis\DBBundle\Entity\Position();
$pos->Vorgang = $entity;
$pos2 = new \Vorgaenge\Basis\DBBundle\Entity\Position();
$pos2->Vorgang = $entity;
$em->persist($entity);
$em->persist($pos);
$em->persist($pos2);
$em->flush($entity);
.... and removing one of the relations after all entities an relations have been saved.
$pos->Vorgang = NULL;
$em->flush();
But somehow Doctrine deletes the entire entity $pos instead of only removing the relation by setting VID to 0.
I checked Doctrine's UnitOfWork doRemove and scheduleForDelete methods, but none of them seems to be involved.
Can anyone help me to understand why the Position entity is deleted and what needs to be done to prevent this?
Try to persist the object that you want keep like this:
$pos->Vorgang = NULL;
$em->persist($pos2);
$em->flush();
Problem solved. The view which showed the DB results after the script was inadequate. It now works as expected. The position is there.

Symfony2: Error persisting ManyToMany/OneToMany Relationships

I don't know why, maybe i am missing some basic logic but I always run again into the same issue. I can't persists ManyToMany collections, and it also faces me with OneToMany collections, though I can work around that.
I read through the doctrine documentation, and I think I do understand the thing with mappedBy and inversedBy (where the last one is always the owner and therefor responsible for persisting the data, please correct me if I am wrong).
So here's a basic example that I have right now, which I can't figure out.
I have an Entity called Site:
#Site.php
...
/**
* #ORM\ManyToMany(targetEntity="Category", mappedBy="sites")
*/
protected $categories;
and another one called Category:
#Category.php
...
/**
* #ORM\ManyToMany(targetEntity="Site", inversedBy="categories")
* #ORM\JoinTable(name="sites_categories")
*/
protected $sites;
Using the Symfony2 entity genenerator it added me some getters and setters to my Entites which look like this.
Site:
#Site.php
...
/**
* Add categories
*
* #param My\MyBundle\Entity\Category $categories
*/
public function addCategory(\My\MyBundle\Entity\Category $categories)
{
$this->categories[] = $categories;
}
/**
* Get categories
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
The same counts for
Category:
#Category.php
...
/**
* Add sites
*
* #param My\MyBundle\Entity\Site $sites
*/
public function addSite(\My\MyBundle\Entity\Site $sites)
{
$this->sites[] = $sites;
}
/**
* Get sites
*
* #return Doctrine\Common\Collections\Collection
*/
public function getSites()
{
return $this->sites;
}
Fair enough.
Now in my controller, I am trying to persist a Site object:
public function newsiteAction() {
$site = new Site();
$form = $this->createFormBuilder($site); // generated with the FormBuilder, so the form includes Category Entity
// ... some more logic, like if(POST), bindRequest() etc.
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($site);
$em->flush();
}
}
The result is always the same. It persists the Site Object, but not the Category entity. And I also know why (I think): Because the Category entity is the owning side.
But, do I always have to do something like this for persisting it? (which is actually my workaround for some OneToMany collections)
$categories = $form->get('categories')->getData();
foreach($categories as $category) {
// persist etc.
}
But I am running into many issues here, like I would have to do the same loop as above for deleting, editing etc.
Any hints? I will really give a cyber hug to the person who can clear my mind about that. Thanks!
.
.
.
UPDATE
I ended up changing around the relationship (owning and inverse side) between the ManyToMany mapping.
If somebody else runs into that problem, you need to be clear about the concept of bidrectional relationships, which took me a while to understand too (and I hope I got it now, see this link).
Basically what anserwed my question is: The object you want to persist must always be the owning site (The owning site is always the entity that has "inversed by" in the annotiation).
Also there is a concept of cascade annotation (see this link, thanks to moonwave99)
So thanks, and I hope that helps somebody for future reference! :)
Regarding OneToMany relationship, you want to know about cascade annotation - from Doctrine docs [8.6]:
The following cascade options exist:
persist : Cascades persist operations to the associated entities.
remove : Cascades remove operations to the associated entities.
merge : Cascades merge operations to the associated entities.
detach : Cascades detach operations to the associated entities.
all : Cascades persist, remove, merge and detach operations to associated entities.
following docs example:
<?php
class User
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* #OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
//...
}
When you add comments to the author, they get persisted as you save them - when you delete the author, comments say farewell too.
I had same issues when setting up a REST service lately, and cascade annotation got me rid of all the workarounds you mentioned before [which I used at the very beginning] - hope this was helpful.

Resources