Insert entity in preUpdate event - symfony

I'm trying to persist a History entity whenever a Message gets updated. I have too much going on behind the scenes to post all the code here and for it to make sense, but I've basically tracked the issue down to the UnitOfWork::commit method. There, the UOW first loops through the entityInsertions, and finding nothing, continues on to the entityUpdates. There the UOW's entityInsertions gets updated, but since it's already past that loop, it doesn't pick up that it still needs to persist some entities. Is there any way to force the UOW to "restart" this process? If so, how? I'm using Doctrine 2.4.
Thanks for any help!

This might be the dirtiest solution ever, but what I ended up doing was basically the following...
Create an onFlush event subscriber
Inject the entire container into the subscriber (seeing as injecting only the entity manager will result in a circular reference error)
Loop through the UnitOfWork's scheduledEntityUpdates and scheduledEntityInserts (I wasn't interested in deletes)
Handle each scheduled update or insert which you are interested in (in my case, I marked each entity I was interested in with a LoggableInterface, just to know which entities are loggable)
Handle the relevant object with a handler chain (This was just my own algorithm, yours may not require this. This was set up to handle logging of different LoggableInterface objects in different ways)
Persist the entity (the actual history event) via the entity manager, and do the following:
$classMeta = $this->entityManager->getClassMetadata(get_class($historyEntity));
$this->entityManager->getUnitOfWork()->computeChangeSet($classMeta, $historyEntity);
Profit
Hope this helps somebody!

Related

Creating new entities in `postFlush` doctrine event

I'm struggling to find a way to perform a persist() and flush() methods after the final flush (I mainly want to do it in postFlush event).
I collect the necessary entities in onFLush event (with changeSets) and wait up until all entities (which I collected) got flushed to get their id's (auto incremented).
So that I have at this point an array with all needed entities and their change sets and their id's set.
Then I want to create new entities (let's call them "traces") based on fields of previously collected entities and persist & flush "traces" in database.
But I'm really stuck here as I can't know entities id's in onFlush event, and I can't persist & flush them in postFlush when they already have their id's set.
Currently Doctrine documentation states following:
postFlush is called at the end of EntityManager#flush(). EntityManager#flush() can NOT be called safely inside its listeners.
And if I dare do this, it ends up in a recursion and php fails with an error.
Which approach may I take here?
I believe you could do a check if you aren't flushing "traces" entity and then perform your "traces" creation. That shouldn't loop.
Also you might want to look at symfony's eventDispatcher . You could dispatch your events manually, since it might be cleaner.
More details on "traces" would be helpful, from what I can imagine it is some kind of a changelog, history; so for that I might suggest EntityAuditBundle. It works pretty good with doctrine and is not hard to set up, I am using it myself.

Avoid doctrine postloadEvent if unnecessary

For an blog entry entity I am loading data from an api via the doctrine postloadEvent. For this i created a listener service with an postloadMethod in it.
public function postLoad(BlogEntry $blogEntry)
{
$blogentry->setName($apiClient->getName($blogEntry->getId()))
$blogentry->setContent($apiClient->getContent($blogEntry->getId()))
...
}
This means, there is already a local repository with blog entries. These blogentries are connected to a blog. If i now only want to count the blogentries for each blog, i would implement a getBlogEntryCount() method on the BlogEntry entity and call it.
$blog->getBlogEntryCount();
The problem now is, that the postLoad event is triggered unnecessary, even if i need no data from the api.
How can i avoid this behaviour in doctrine/symfony/sonata admin? Is there mechanism like "lazy loading" for doctrine entities?
Update to Jose M. González Solution
To get only the count of the collection, the extra_lazy loading solution will do it.
For getting local information without triggering the api call i used the said repository function. To get this information working in list view in sonata admin, i created a non-doctrine-related array field "blogEntriesSimple" in my Blog Entity next to my "blogEntries" (which is normally holding the complete entity) field.
I attached an entitylistener with postLoad function to the Blog Entity, which is filling up my blogEntriesSimple array with the information from my custom repository function.
Thats it.
I think this solution is a bit hacky, but until no cleaner solution is available, this will do it.
i think that you can achieve this with the extra lazy associations that permit that you count your related entities without hydrating it
Edited
Also you can do a DQL query that only hidrate a partial view of your entity and it can be used to count the rows and access to your properties, for example:
select be.id,be.title from AppBundle\Entity\BlogEntry be
This query must not trigger the postLoad event
I hope this can help you

Symfony2 - Doctrine2 store changeset for later (or alternative solution to approve changes)

I have several entities, each with its form type. I want to be able, instead of saving the entity straight away on save, to save a copy of the changes we want to perform and store it in DB.
We'd send a message to the user who can approve the change, who will review the original and the changed field(s) and will approve or not. If approved the entity would be properly flushed.
To solve the issue I was thinking about:
1) doing a persist
2) getting the changesets (both the one related to "normal" fields, and the one relative to collections)
3) storing it in DB
4) Performing $em->refresh() to discard changes.
Later what I need is to get the changset(s) back, ask the (other) user to approve it and flush it.
Is this doable? What I'm especially concerned about is that the entity manager that generated the first changeset is not the same we are going to use to perform the flush, I basically need to "load" a changeset.
Any idea on how to solve the issue (this way, or another way ;) )
Another solution (working only for "normal" fields, not reference ones that come from other entities to the current one, like a many to many) would be to clone the current entity, store it, and then once approved copy the field(s) from the cloned to the original one. But it does not work for all fields (if the previous solution does not work we'd limit the feature just to "normal" fields).
Thank you!
SN
Well, you could just treat the modifications as entities themselves, so that every change is stored in the database, and then all the changes that were approved are executed against the entity.
So, for example, if you have some Books stored in the database, and you want to make sure that all the modifications made to these are approved, just add a model that would contain the changeset that has to be processed, and a handler that would apply these changes:
<?php
class UpdateBookCommand
{
// If you'll store these commands in a database, perhaps this field would be a relation,
// or you could just store the ID
public $bookId;
public $newTitle;
public $newAuthor;
// Perhaps this field should be somehow protected from unauthorized changes
public $isApproved;
}
class UpdateBookHandler
{
private $bookRepository;
private $em;
public function handle(UpdateBookCommand $command)
{
if (!$command->isApproved) {
throw new NotAuthorizedException();
}
$book = $this->bookRepository->find($command->bookId);
$book->setTitle($command->newTitle);
$book->setAuthor($command->newAuthor);
$this->em->persist($book);
$this->em->flush();
}
}
Next, in your controller you would just have to make sure that the commands are somehow stored (in a database or maybe even in a message queue), and the handler gets called when the changesets could possibly get applied.
P.S. Perhaps I could have explained this a bit better, but mostly the inspiration for this solution comes from the CQRS pattern that's explained quite well by Martin Fowler. However, I guess in your case a full-blown CQRS implementation is unnecessary and a simpler solution should work.

Using Doctrine 2 / DataMapper, how do I persist new entities in the domain layer?

I have a tree-like structure with a couple of entities: a process is composed of steps and a step may have sub-processes. Let's say I have 2 failure modes: abort and re-do. I have tree traversal logic implemented that cascades the fail signal up and down the tree. In the case of abort, all is well; abort cascades correctly up and down, notifying its parent and its children. In the case of re-do, the same happens, EXCEPT a new process is created to replace the one that failed. Because I'm using the DataMapper pattern, the new object can't save itself, nor is there a way to pass the new object to the EntityManager for persistence, given that entities have no knowledge of persistence or even services in general.
So, if I don't pass the EntityManager to the domain layer, how can I pick up on the creation of new objects before they go out of scope?
Would this be a good case for implementing AOP, such as with the JMSAopBundle? This is something I've read about, but haven't really found a valid use case for.
If I understand your problem correctly (your description seems to be written a bit in a hurry), I would do the following:
mark your failed nodes and your new nodes with some kind of flag (i.e. dirty flag)
Have your tree iterator count the number of failed and new nodes
Repeat tree-iteration / Re-Do prcocess as often as you want, until no more failed or new nodes are there that need to be handled
I just found a contribution from Benjamin Eberlei, regarding business logic changes in the domain layer on a more abstract level: Doctrine and Domain Events
Brief quote and summary from the blog post:
The Domain Event Pattern allows to attach events to entities and
dispatch them to event listeners only when the transaction of the
entity was successfully executed. This has several benefits over
traditional event dispatching approaches:
Puts focus on the behavior in the domain and what changes the domain triggers.
Promotes decoupling in a very simple way
No reference to the event dispatcher and all the listeners required except in the Doctrine UnitOfWork.
No need to use unexplicit Doctrine Lifecycle events that are triggered on all update operations.
Each method requiring action should:
Call a "raise" method with the event name and properties.
The "raise" method should create a new DomainEvent object and set it into an events array stored in the entity in memory.
An event listener should listen to Doctrine lifecycle events (e.g. postInsert), keeping entities in memory that (a) implement events, and (b) have events to process.
This event listener should dispatch a new (custom) event in the preFlush/postFlush callback containing the entity of interest and any relevant information.
A second event listener should listen for these custom events and trigger the logic necessary (e.g. onNewEntityAddedToTree)
I have not implemented this yet, but it sounds like it should accomplish exactly what I'm looking for in a more automated fashion that the method I actually implemented.

Symfony2: best approach to use business (repository) logic in entity or controller

I'm having a design issue in my project, related to where put some business logic.
I have three entities, Event, TicketOrder and Ticket. One Event has a lot of TicketOrders and one TicketOrder has a lot of Tickets.
In my template, I have to show how many tickets an Event has. I've thinking of the best approach to achieve this and didn't get a good solution. I've tried this:
1) Create a private member 'ticketsCount' in Event entity, with setTicketsCount and getTicketsCount method. Create a 'loadTicketsCount' method with a LifeCycleCallback 'PostLoad', to access the TicketRepository method 'findByEvent'. This was impossible because I can't access repository in an entity class.
2) In the action that will be used to display the Event, I can access Ticket Repository and set event 'ticketsCount' property manually. I don't know if it is a good approach because if my action is listing a lot of events I'll have to loop trough all events and make a repository call to each of then.
I really don't know the best approach to achieve this and will really appreciate if someone can help me.
Thanks! ;)
When you use findAll, findBy or findBy* methods of doctrine entity repository, a simple php array is returned containing the entity objects.
The array class implements countable interface. So using twigs length filter
{{ ticketOrder.tickets|length }}
you perform a simple php count() on the array.
Actually it makes now sense to perform a count query, because you already have the result in memory. So it seems more efficient to count the result and retrieve it from memory, because when you access associations they are completely loaded into memory.
However associations between entities can get pretty large. So imagine you have associations with hundred thousands of entities. You won't those entites to be loaded all together and kept in memory all the time. So in Doctrine 2.1 you can annotate an association as Extra Lazy. If you do so in your case a count query is performed when you call the above twig filter. But the result is not kept in memory.
http://docs.doctrine-project.org/en/2.0.x/tutorials/extra-lazy-associations.html
According to your latest comment:
I can imagine one way to do this. In a template you can call a controller's action with the render statement like
{% render YourMainBundle:getTickets with { 'event_id' : event.id } %}
and in this action you can call a query that looks for all tickets associated to the certain event. This action has to return html, e.g. an template filled with data.

Resources