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;
Related
I'm developing a custom content management system with Symfony 5 and Doctrine.
I'm trying to implement a relation between the entities Document and Video (actually there are many more, but for simplicity sake let's say are just two) and the User entity.
The relation represent the User who wrote the document or recorded the video. So the relation here is called Author. Each document or video can have one or more author. Each User can have none or more document or video.
I would like to use just a single associative Author associative entity, like this:
entity_id|author_id|entity
Where:
entity_id: is the id of the document or video
author_id: is the user_id who authored the entity
entity: is a constant like document or video to know to which entity the relation refer to
The problem is that I cannot understand how to build this in Doctrine. Was this a classic SingleEntity<-->Author<-->Users relationship I would have build it as a ManyToMany item, but here it's different.
Author would probably contain two ManyToOne relations (one with the User entity and one with either the Document or the Video entity) plus the entity type field, but I really don't know how to code the "DocumentorVideo`" part. I mean:
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=??????????, inversedBy="authors")
* #ORM\JoinColumn(nullable=false)
*/
private $entity; // Document or Video
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="articles")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="smallint")
*/
private $entityType;
How should I manage the first field?
Don't know if would be better to store it under two differents attributes. If not and mandatory, I think those "objects" should have a common interface or something, so take a look to doctrine inheritance that should fulfill your needs
My suggestion is to store the entity namespace Ex. Acme\Entity\Document in a property and the id in another and to use the entity manager to get the entity.
Edit: Though you won't have the relation, I prefer that way over others because it is reusable and the performance is rather the same. Also if I need to pass it to a JSON response, I just create a normalizer and I am good to go.
/**
* #ORM\Column(type="string")
*/
private $entityNamespace;
/**
* #ORM\Column(type="integer")
*/
private $entityId;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getEntity()
{
return $this->em->getRepository($this->entityNamespace)->find($this->entityId);
}
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.
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 want to serialize into JSON entity Category with collection of Presentation entities (see below) to use for REST API.
The endpoint will look something like this /api/v1/categories/1
When dataset is small and when Category only has only 5-10 related Presentations then the resulting response is not too large. However when Category starts to have let's say 100 or 200 related Presentations then obviously I do not want to return all of them, but would like to "paginate" the results, eg. when calling endpoint:
/api/v1/categories/1?page=2 - would return only "2nd page"
/api/v1/categories/1/page=3 - would return "3rd page"
or even it can be with offset and limit:
/api/v1/categories/1?offset=20&limit=10
but the problem is: how to make JMS serializer serialize only a slice of the collection?
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\CategoryRepository")
*/
class Category
{
/**
* #var string
* #ORM\Column(type="string")
* #JMS\Expose()
* #JMS\Groups({"get-category"})
*/
private $title;
// ...
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Presentation", mappedBy="categories", fetch="EXTRA_LAZY")
* #JMS\Groups({"get-category"})
* #JMS\Expose()
*/
private $presentations;
// ...
}
ps. I know that for example if I want to get always first 5 elements of the collection, I can add created #VirtualProperty and slice the doctrine ArrayCollection as shown below. But the problem here is that I cannot pass the offset parameters to this method. As it would be called internally by JMSSerializer somewhere...
/**
* #JMS\VirtualProperty()
*
*/
public function getFirstFivePresentations(){
return $this->presentations->slice(0,5);
}
You are trying to implement the incorrect approach in your REST API. Each entity must have it's own path.
The right way is to have two different endpoints:
/api/v1/categories/1 -> Serialized category with no presentations
/api/v1/categories/1/presentations -> Serialized collection of presentaions
And here you should use pagination
/api/v1/categories/1/presentations?offset=20&limit=10