Doctrine 2, trigger event after EAGER fetch - php

Multiple entities in my application have a gallery associated, defined this way
/**
* #ORM\ManyToOne(targetEntity="Gallery")
* #ORM\JoinColumn(name="gallery_id", referencedColumnName="id")
*/
protected $gallery;
I like to perform some custom logic when those entities are loaded, using postLoad doctrine event. I eventually learnt that Doctrine doesn't advice to play with associations on postLoad event, as they are not fully loaded...
In order to improve performance, I added a fetch="EAGER" on this association. Which is nice to reduce the number of requests while fetching a collection.
But now the getGallery() is empty in the postLoad event.
Is there any way to trigger something after the associations have been performed ? I could wrap a service and/or model around my entity to do the logic but I would prefer a simple event.
Thanks !

Related

Doctrine - Remove parent association when deleting child record

I have a FileUpload entity that is child of other entities using the following association:
/**
* #ORM\ManyToOne(targetEntity="FileUpload", cascade={"persist", "remove"} )
* #ORM\JoinColumn(name="image_id", referencedColumnName="id")
*/
protected $image;
The FileUpload entity contains various information about an uploaded file as well as a boolean field to mark it for deletion (handled by a checkbox on the form). I am trying to find a good way of managing this deletion process without having to duplicate the code in every entity that has a FileUpload entity.
I tried creating a service tagged with doctrine.event_listener to remove the FileUpload in postUpdate(), however since there is still an association with the parent of the FileUpload this failed. Does anyone know a way of clearing any associations with the FileUpload when it is deleted? Or any other method of handling this process?
Sorry for the late answer. Now I understand your problem.
In the relation you described on the side has to be the owner of the relation. If let's say A is in relation with B and let's say that A is the owner of the relation, that implies that A has control over all the aspects of the relation so B can't be deleted without A say so.
Think about the foreign key relation of a database. The database won't let you delete the line as long as it is part of a relationship and is not the owner of the relation(this being your current problem).
If you get into a place where you need to delete a FileUpload without knowing the Comment that has a relation to it you may have an architectural problem in your application/database design. If you know the Comment that has a relation to the FileUpload at the point that you want to remove the file then orphanRemoval is what you need. The way you remove it is not by asking the Manager to remove it (cause it can't do it without the approval of the owner of the relation, as explained in the example above). Instead, you ask the owner of the relation to removing it something like this
//for OneToOne relation
$comment->setFileUpload(null);
//for OneToMany relation
$comment->getFileUpload()->removeElement($fileUpload);
After the above statement call flush and it should work. Also for OneToMany make sure that you initialize
$this->fileUpload = new ArrayCollection();
in the Comment entity constructor.
NOTE1: As mentioned before careful with orphan removal cause it doesn't work as you will expect in relation to refresh function of the manager. After an object was market in doctrine unit of work as an orphan, it will get removed even if you called refresh on it, or it's parent. Found a way around this (using the doctrine onFlush event) but is better to not need this and try to avoid the situation.
NOTE2: orphanRemoval has the effect of hard delete in the database. If as some point you need to do add this code to a doctrine subscriber or onFlush listener
public function onFlush(OnFlushEventArgs $args)
{
foreach ($args->getEntityManager()->getUnitOfWork()->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof SoftRemovableInterface) {
$args->getEntityManager()->remove($entity);
$args->getEntityManager()->persist($entity);
$entity->remove();
}
$args->getEntityManager()->getUnitOfWork()->computeChangeSet($args->getEntityManager()->getClassMetadata(get_class($entity)), $entity);
}
}
Where the remove+persist calls are there to take the entity out of the orphan removal list in unit of work (part of the fix for refreshing entities in NOTE1, this is the only place and only way i found that you can stop the removal of an orphan after it was marked as such by doctrine), and $entity->remove(); is the method of the SoftRemovableInterface that handles the soft delete, something like
class Comment implements SoftRemovableInterface
{
/........../
function remove()
{
$this->deleted = true;
}
}
Hope this brings some light to your issue. Happy coding.
Alexandru Cosoi

Symfony and Doctrine: lazy loading is not working

Using Symfony 2.8.
I have Community and MenuItem entities, where a Community has a set of MenuItems.
Community.php has the following code:
...
/**
* #ORM\OneToMany(targetEntity="MenuItem", mappedBy="community", fetch="LAZY")
* #ORM\OrderBy({"sequence" = "ASC"})
*/
private $menuItems;
...
MenuItem.php has the following code:
...
/**
* #var Community
*
* #ORM\ManyToOne(targetEntity="Community", inversedBy="menuItems")
*/
private $community;
...
The point is, when I use:
$menuItems = $community->getMenuItems();
the $menuItems variable will be an empty collection.
The problem can be solved by setting fetch="EAGER" instead of fetch="LAZY", because in that way the $menuItems attribute of the Category entity is loaded immediatly.
LAZY vs EAGER (source) :
Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading.
Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application.
The point is that while EAGER loading is working as expected, LAZY loading seems not working at all. Any idea about why?
This seems to work to load the lazy relationship.
$logs = $entity->getLogs(); // lazy relationship
$this->getDoctrine()->getManager()->initializeObject($logs);
$logs will now populate.
Docs for initializeObject:
Helper method to initialize a lazy loading proxy or persistent
collection.
When you are doing $community->getMenuItems(); :
In EAGER mode: the data are already fetched, so the array is returned.
In LAZY mode: the database request is done when you do the call. Behind the scene it works by generating "proxies" in front of your entities that will call doctrine for you.
Carefull with lazy loading :
Traversing the object graph for parts that are lazy-loaded will easily trigger lots of SQL queries and will perform badly if used to heavily.
It's better to fetch directly the data, by doing a DQL fetch. See for instance http://blog.bemycto.com/good-practices/2015-05-31/understanding-doctrine-orm-lazy-load-fetch-join/
Personally, I'm not a fan of lazy/eager loading as many queries will be fired when they can be done in one query with joins.
Please see my answer here on how to implement a custom repository to build a custom query, link

Doctrine SoftDelete OneToOne Relationship

I have these entities on my code.
class Review extends BaseEntity {
/** #ORM\OneToOne(targetEntity="Action", mappedBy="review") */
protected $action;
}
class Action extends BaseEntity {
/** #ORM\OneToOne(targetEntity="Review", inversedBy="action") */
protected $review;
}
As you can see it's a OneToOne relationship between Action and Review. My problem is I use soft-delete for my entities as well, so when I delete an entity is not actually removed only a deletion date is set. Later in audit trail I need to show deleted reviews and I also need information from the parent action of course. My question is, do I need to make this relationship OneToMany? or is there a better approach?
To be honest i'm not very found of the soft-delete behaviour. What you need to be aware is that soft-deleting an entity is a strong compromise in a relational database.
You may want to consider in this particular instance an event sourcing approach. I would recommend to store the information about the deletion and the (soft)deleted entity in a document based DB.
Any other approach (like OneToMany) is still fine but keep in mind that the risk here is degrading your relation by introducing a incoherent relationship.
That being said I'm fully aware that real life it's quite different than theory :) good luck.
Regards.

Updating an entity

I am using DDD now for quiet some time, it took some time to get used to and separate everything. But am now stuck at updating my entity ...
Setup
Now I have a repo interface that defines the following methods
/**
* #param AccountEntity $account
* #return AccountEntity
*/
public function create(AccountEntity $account);
/**
* #param AccountEntity $account
* #return mixed
*/
public function update(AccountEntity $account);
My repo does exactly 0 in the function because it passes it to the mapper, which in turn creates/updates the data. So far so good.
The application service has the method create which accepts an array, the array is validated and if valid it will use an EntityBuilder to create the entity. (Entity requires the data by __construct). If the data is invalid it will throw an exception.
Now my issue is how to handle my update in the application service.
I get an array of data, just like the create, in the update and an id of the entity.
Should I use an hydrator to loop over the array and update the fields in the entity and then throw it to my repo.
cons, I then have an entity hydrator and builder.
pros, it is easy in use
Should I just validate the data and throw the array + id to the repo and let him figure out what to do
cons, just looks wrong
pros, again easy to make
Should I do toArray or something similar on my entity, merge that with the form data & then use the entity builder to build the entity again and throw the entity then to the repo
cons, don't think an entity should have a toArray function
pros, sounds like it encapsulates the task of building the entity in one place so that seems right.
?
So in short, how to convert an array with data to an entity, or how to save it. I am not using Doctrine in any way, and not planning to
First of all, your input data (array) should be validated somewhere at the controller level, to ensure is in the correct format (we're not talking about business rules, just formatting).
Then your entity can have something like this
class MyEntity
{
public function update($data)
{
//update properties, enforce the relevant business rules
//perhaps events are generated
}
}
The controller will probably use a service method to do the updating. The service will ask the repository for the entity, eventually creates the input format the entity expects it (if there is a difference) then calls the update method .
Then you send the entity to the repository which takes care of persisting it. Remember that the Repository is there to save/restore your entities not to change them.
This should probably have been a comment, but that requires 50 rep...
You should have a look at this article about datamappers: http://www.sitepoint.com/integrating-the-data-mappers/
I have been exactly in your situation, and the articles by that writer (Alejandro Gervasio) helped me immensely.

Best Way To Handle Removing Records in Symfony2

I have a lot of entities that have ManyToOne and OneToMany associations in Symfony2. As all know if you remove a record and it doesnt set a null value on the association in the other tables, things start to go haywire. So, what is the best way in Symfony2 to handle setting the value as null in other tables when a record is removed in Symfony2?
What do I need to set in my entities to ensure it persists across all associations.
First of all you can manually set ON DELETE behavior for the FK by using onDelete property for the joinColumn annotation in your entity:
/**
* #OneToOne(targetEntity="SomeEntity")
* #JoinColumn(name="some_entity_id", referencedColumnName="id", onDelete="cascade")
*/
private $someEntity;
Secondly, you always can implement event listener for the entity removing event. For example:
public function preRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof SomeEntity) {
return;
}
// Here you can do whatever you want before
// entity record is removed from the DB.
}
You can read about the doctrine event system using following links:
Doctrine events (official manual)
How to Register Doctrine Event Listeners and Subscribers (Symfony2 cookbook)
The difference between them is that the first method works on the database layer. In other words, it would work even if you delete record using raw sql query in the console. Second one is more powerfull because you can also run any php code when entity is removed (for example you can send emails, etc.) but it will only be fired if record is removed via doctrine.

Categories