I have an issue with the doctrine relationship. I try different ways but anything won't work.
Idea is that I have a News entity and every news should have many comments. So I try next:
The News entity:
/**
* #ORM\OneToMany(targetEntity="App\ORM\Entity\NewsComment", mappedBy="news")
*/
protected \Doctrine\Common\Collections\Collection $comments;
/**
* News constructor.
*/
public function __construct() {
$this->comments = new ArrayCollection();
}
And NewsComment entity:
/**
* #ORM\ManyToOne(targetEntity="App\ORM\Entity\News", inversedBy="comments")
*/
protected \App\ORM\Entity\News $news;
Every entity has its own get and set methods as well.
But, when I receive a News entity a can get comments collection but it always empty. On the other hand, I can take any NewsComment entity and get from this News entity. It is working fine. But not to another way.
Is anything wrong with my code?
Doctrine sets owned (non-inversed) collection as lazy by default.
When retrieving an entity by database, you should see an empty PersistentCollection instead of ArrayCollection, with initialized property set to false.
When calling any method on that collection, doctrine fires the queries needed to initialize the collection and populate it.
Collection emptiness should be only checked invoking isEmpty.
Related
I'm currently building Entity model and one of my Doctrine Entities have ManyToMany relation with an external dictionary (like ENUM). So the entity field will be an Array of Enum.
I'm looking for a way to have it as an array field on my entity, but to store it as a separate DB table.
Would like to get any advice/links/etc.
The question is a bit out of context but..
A many to many is already an array (Iterator) in your entity.
You can create your own entity acting as a Many To Many and set the column as enum.
Finally, I've decided to create an Entity to store this relation. To make sure it will be deleted on unlinking from the parent entity, I've used the orphanRemoval=true option on the OneToMany relation side.
class Entity {
/**
* #ORM\OneToMany(targetEntity="EntityType", mappedBy="entity", orphanRemoval=true)
*/
protected $types;
}
class EntityType {
/**
* #ORM\ManyToOne(targetEntity="Entity")
*/
protected $entity;
/**
* #ORM\Column(type="MyEnum")
*/
protected MyEnum $type;
}
Let's say I have the following entities:
App\Entity\MainEntity:
/**
* #var object
*
* #ORM\OneToOne(targetEntity="App\Entity\DependentEntity", fetch="EAGER")
* #ORM\JoinColumn(name="DependentEntityType1FK", referencedColumnName="DependentEntityIDPK")
*/
private $dependentEntityType1;
/**
* #var object
*
* #ORM\OneToOne(targetEntity="App\Entity\DependentEntity", fetch="EAGER")
* #ORM\JoinColumn(name="DependentEntityType2FK", referencedColumnName="DependentEntityIDPK")
*/
private $dependentEntityType2;
Basically, one-directional 1:1 relationship from main entity to the same dependent entity using two different columns in the main entity table.
It doesn't matter, whether I use fetch="EAGER" or normal lazy loading through Doctrine proxy classes, when I do something like this:
$mainEntity = $this->mainEntityRepository->find(74);
$mainEntity->setDependentEntityType1($this->dependentEntityRepository->find(35));
$this->mainEntityRepository->saveTest($mainEntity);
where ::saveTest() is:
public function saveTest(MainEntity $mainEntity) {
$this->_em->persist($mainEntity->getDependentEntityType1());
$this->_em->merge($mainEntity);
$this->_em->flush();
}
it always tries to INSERT a new dependent entity to the table, even though I never made any changes (and even if I made them, it should have been UPDATE! for it)
The question is: why does Doctrine decide this dependent entity is a new one if I did $this->dependentEntityRepository->find(35) , so loaded an existing one?
I tried fetch="EAGER" thinking that spl_object_hash might return different hashes for a Proxy class instance and the actual DependantEntity one, but it doesn't matter, the DependantEntity is for some reason always considered as "new".
UPDATE: here is the code of ::setDependentEntityType1()
public function setDependentEntityType1(DependentEntity $dependentEntity) : void {
$this->dependentEntity = $dependentEntity;
}
So I already know that I can get changes to a specific entity in the preUpdate lifecycle event:
/**
* Captures pre-update events.
* #param PreUpdateEventArgs $args
*/
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof ParentEntity) {
$changes = $args->getEntityChangeSet();
}
}
However, is there a way to also get changes for any associated Entities? For example, say ParentEntity has a relationship setup like so:
/**
* #ORM\OneToMany(targetEntity="ChildEntity", mappedBy="parentEntity", cascade={"persist", "remove"})
*/
private $childEntities;
And ChildEntity also has:
/**
* #ORM\OneToMany(targetEntity="GrandChildEntity", mappedBy="childEntity", cascade={"persist", "remove"})
*/
private $grandChildEntities;
Is there a way to get all relevant changes during the preUpdate of ParentEntity?
All of the associated entities from a OneToMany or ManyToMany relationships appear as a Doctrine\ORM\PersistentCollection.
Take a look at the PersistentCollection's API, it have some interesting public methods even if they are marked as INTERNAL: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/PersistentCollection.php#L308
For example you can check if your collection is dirty which means that its state needs to be synchronized with the database. Then you can retrieve the entities that have been removed from the collection or inserted into it.
if ($entity->getChildEntities()->isDirty()) {
$removed = $entity->getChildEntities()->getDeleteDiff();
$inserted = $entity->getChildEntities()->getInsertDiff();
}
Also you can get a snapshot of the collection at the moment it was fetched from the database: $entity->getChildEntities()->getSnapshot();, this is used to create the diffs above.
May be this is not optimal, but it can do the job. You can add a version field on ParentEntiy with a timestamp, then on each related entity setter function (Child or GranChild) you need to add a line updating that parent timestamp entity. In this way each time you call a setter you will produce a change on the parent entity that you can capture at the listener.
I have used this solution to update ElasticSearch documents that need to be updated when a change happens on a child entity and it works fine.
I am making something like a wizard and I need to pass an object from one request to another. I use serialization for this. The object is a doctrine entity with a many to many association. For demonstration purposes I will simplify, since this issue regards only the association.
class User
{
// scalar properties
/**
* #var User\Role
* #ORM\ManyToMany(targetEntity="User\Role", cascade={"persist", "remove"})
* #ORM\JoinTable(name="roles")
*/
protected $roles;
// getters, setters
}
Now, when I deserialize the object, it deserializes perfectly, with the associations. Problem is, when I merge it with $em->merge($object); and then flush, the object gets saved into database and all the scalar properties that changed are persisted correctly. But the association is ignored during save. Before saving, there are three roles in the database. I have only one role in my association, but when I flush and then reload the object from database, there are still the three roles that were there before. This issue occurs only with deserialization, if I work with an entity that is originaly loaded from $em, the association gets updated like it should.
One more thing - if I define cascade={"merge"} on the association, the merge operation ends with error "spl_object_hash() expects parameter 1 to be object, array given" in UnitOfWork on line 1810, where an array of roles (in this case with one element) is passed into spl_object_hash() function. Not sure if this is a bug or I'm doing something wrong.
Does anyone have and idea how to get around this issue, or what am I doing wrong? Any help appreciated!
I had the EXACT same issue today, that's why I landed here.
It seems I have solved it:
cascade={"merge"}
was definitely useful and once I configured the ManyToMany relation correctly on both sides, it started working - no "spl_objects_hash()"-issue any more. Pay Attention to inversedBy and Mapped by. Also, but I don't know if this is part of the trick, take a look at the add and remove methods (which I implemented on both sides.) Before, I didn't take care of adding/removing the other side of the relation...
class User {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Project", cascade={"merge"}, inversedBy="users")
* #JMS\Type("Relation<App\Entity\Project>")
*/
private $projects;
public function addProject(Project $project): self
{
if (!$this->projects->contains($project)) {
$this->projects[] = $project;
$project->addUser($this);
}
return $this;
}
public function removeProject(Project $project): self
{
if ($this->projects->contains($project)) {
$this->projects->removeElement($project);
$project->removeUser($this);
}
return $this;
}
}
class Project {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="projects")
* #JMS\Type("Relation<App\Entity\Project>")
*/
private $users;
}
EDIT: SO after having the same issue with another entity, I tested if the removing and adding of the related entity in add()/remove() was necessary in this case: NOPE. cascade={merge} does the trick, which started working after configuring the inverse side correctly.
I know there are loads of posts on this topic in general. Unfortunately those mostly deal with the actual persist-operation to the database. In my case I have a problem that happens before the persist-operation:
I have a form with a (Doctrine) persistenceCollection of entities. You can remove "objects" from the DOM via javascript. After submit, when handleRequest is called on the form, the function in my entity is called which removes the entity from the collection in the object itself, and it is called as I can check in the debugger:
/**
* Remove prices
*
* #param \Whizzpm\Bundle\Entity\Supplier\SupplierPrice $prices
*/
public function removePrice(\Whizzpm\Bundle\Entity\Supplier\SupplierPrice $prices)
{
$this->prices->removeElement($prices);
}
And this is the definition of $prices:
/**
* #var
* #ORM\OneToMany(targetEntity="SupplierPrice", mappedBy="priceList", cascade={"all"})
*/
private $prices;
The basic idea is to compare the updated entity with it's previous state but after the function above has finished the entitiy is still in the collection.
To make this more precise: If I check $this right after the "removeElement($prices)" is through, it still contains the object that just should have been removed.
Maybe this is important:
supplier (main entity)
pricelist (property of main entity - also entity itself)
prices (property of pricelist, collection of entities (price items)
prices is the collection of which the element (price item) should be removed.
Any thoughts on this? I can add any information you need on this question I just don't know, which of it makes sense, sincer there are loads.
Finally I found a solution in this post:
removeElement() and clear() doesn't work in doctrine 2 with array collection property
I have to unset the corresponding value in the owning entity too:
public function removePrice(\Whizzpm\Bundle\Entity\Supplier\SupplierPrice $prices)
{
$this->prices->removeElement($prices);
$prices->setPriceList(null);
}
and add orphanRemoval=true to the entity collection
/**
* #var
* #ORM\OneToMany(targetEntity="SupplierPrice", mappedBy="priceList", cascade={"all"}, orphanRemoval=true)
*/
private $prices;