Doctrine 2 listener - symfony

Hi I using Doctrine 2 listener to check if user group was change.
So I have
- {name: doctrine.event_listener, event: preUpdate } i my service.yml
The method is executed and Im doing such thing
$user = $args->getEntity();
$em = $args->getEntityManager();
if($user instanceof \iTracker\UserBundle\Entity\User) {
$u = $em->getRepository('UserBundle:User')->find($user->getId());
var_dump($u->getUserGroup());
var_dump($user->getUserGroup());
}
And both object are this same. Should object $u with old user group and $user with new group ?? Event is preUpdate so before update object should be different.

Have a look at the Doctrine documentation to see what you can get in the preUpdate lifecycle callback: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#preupdate
You have direct access to the original and changed values, so you don't have to query the database.
And to answer your question, why the two values are the same: I'm not 100% sure on that but most probably the EntityManager actually understands that you want to retrieve the same object as you already have, so it returns it without querying the database. To actually query the DB again you would have to somehow refresh the object in the EM (which will probably end up in loosing your changes).

Related

Symfony update entity without changing field

Is there a way to update the entity without actually changing any fields.
$em = $this->getDoctrine()->getManager();
$test = $em->getRepository('AppBundle:MyObject')->find($id);
$test->setName("Test");
$em->flush();
Works for example if the name was set to something else like "Test2" before. But if it was "Test" before this won't work. So I would like to remove the setName line and just update entity.
I need this cause I have a listener that will only run when MyObject changes.
I tried $em->refresh($test); but it doesn't work.
well, you want to update an object but anything has changed ... I recommend rethinking the implementation of your listener or you can do this (very ugly) workaround
$em = $this->getDoctrine()->getManager();
$test = $em->getRepository('AppBundle:MyObject')->find($id);
$originalName = $test->getName();
$test->setName("Whatever");
$em->flush();
$test->setName($originalName)
$em->flush();
I would not recommend this solution, you should really implement your listener in the way it trigger every time you needed to.
The events in a listener like you're describing work on the Doctrine UnitOfWork --meaning that they fire and then look at the differences between the previous state of the object and the intended state of the object. I can't think of a way to get Doctrine to persist something that hasn't changed.
In this case, I don't think you want a listener. I think you want a service that you call explicitly.
$em = $this->getDoctrine()->getManager();
$test = $em->getRepository('AppBundle:MyObject')->find($id);
$testService = $this->get('test_service');
$testService->whatMethodYouWantToRunRegardlessOfChanges($test);
$em->flush();
And then in TestService:
public function whatMethodYouWantToRunRegardlessOfChanges(Test $test) {
//code that you were running in your listener, called every time
}

Doctrine 2, prevent getting unjoined entities

given a user and his coupons, I want to get a user and all of his coupons:
foreach ($this->createQueryBuilder('x')->select('u, c')->where('x.email = ?0')->setParameter(0, $email)->leftJoin('u.coupons', 'c')->getQuery()->getResult() as $entity)
{
$entity->getCoupons();
}
this is very good until I forget to join the coupons:
foreach ($this->createQueryBuilder('x')->select('u')->where('x.email = ?0')->setParameter(0, $email)->getQuery()->getResult() as $entity)
{
$entity->getCoupons();
}
sadly this still works even though no coupons were joined. Here it does an other SELECT. In additional, this 2nd select will be wrong. Id rather want to get a exception or AT LEAST an empty array instead. Is there any workaround for this?
What you're experiencing is expected doctrine behavior.
When you select a User entity, Doctrine will get the record from the database. If you aren't explicitly joining the Coupon entity (or any other entities with relationship to User), Doctrine will create a Proxy object. Once you access this proxy object by calling $user->getCoupons(), Doctrine will fire a new query to the database to get the coupons for your User entity. This is called lazy-loading.
I'm not sure if there is a way to change this in the way you described.
What you can do is to create a method in your UserRepository called findUserAndCoupons($email) and have your query there. Whenever you need to find a user and his coupons, you could simply retrieve it in your controller using:
class MyController extends Controller {
public function myAction(){
$user = $this->getDoctrine()->getRepository('UserRepository')->findUserAndCoupons($email);
foreach($user->getCoupons() as $coupon) {
// ....
}
}
}
This way you won't need to remember the actual query and copy/paste it all over the place. :)

postPersist event not updating database

I have 2 entities, Invoice and Advance. Advances are related to Invoices, with ManyToOne relation. And when adding or editing and advance, I want the Invoice total to be edited accordingly. I have a listener set for Advances postPersist which looks like this :
class PostPersistListener
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$em = $args->getEntityManager();
// Advance Listener
if ($entity instanceof Advance) {
// Modification du total de la facture mère
$invoice = $entity->getInvoice();
echo '<pre>';
\Doctrine\Common\Util\Debug::dump($invoice);
echo '</pre>';
$newTotal = $invoice->getTotalMaster() - $entity->getTotalMaster();
$invoice->setTotalMaster($newTotal);
echo '<pre>';
\Doctrine\Common\Util\Debug::dump($invoice);
echo '</pre>';
$em->flush();
}
}
}
The event is well triggered. The first dump() displays something like this :
object(stdClass)#1379 (49) {
["__CLASS__"]=>
string(32) "Evo\BackendBundle\Entity\Invoice"
["id"]=>
int(1)
["totalMaster"]=>
float(250)
}
And the second dump() this :
object(stdClass)#1379 (49) {
["__CLASS__"]=>
string(32) "Evo\BackendBundle\Entity\Invoice"
["id"]=>
int(1)
["totalMaster"]=>
float(240)
}
So the "totalMaster" property is modified. But the $em->flush() doesn't seem to update the field in the database. Am i missing something ?
To start with, as it's name implies, PostPersist only get's triggered after the initial persist operation i.e. only for new objects. Editing an existing object will not trigger the event.
From: http://docs.doctrine-project.org/en/latest/reference/events.html#lifecycle-events
postPersist - The postPersist event occurs for an entity after the entity has been made persistent.
It will be invoked after the database **insert** operations.
Generated primary key values are available in the postPersist event.
There are also strict limits to what you can do with these events. Basically the flush operation is already underway so anything that updates an entity is problematical at best. Specifically:
From: http://docs.doctrine-project.org/en/latest/reference/events.html#postupdate-postremove-postpersist
The three post events are called inside EntityManager#flush().
Changes in here are not relevant to the persistence in the database,
but you can use these events to alter non-persistable items, like non-mapped fields, logging or
even associated classes that are directly mapped by Doctrine.
It's barely possible you might get this to work using prePersist and preUpdate but those have their own issues.
#kix has the correct approach. Updating an invoice total is a domain function and should be handled using a domain event.
P.S. You don't need to call persist on existing objects retrieved through Doctrine.
You've missed the $em->persist($invoice) line.
Also, I'd suggest implementing this logic with Symfony's built-in events, not with prePersist listeners. The simple reason behind this is that your code is dealing with business logic and not persistence logic. You should dispatch an AdvanceModified event with the event_dispatcher service (it should be available to your controller) and then process it in a listener.

Updating an entity through Doctrine while it's persisted by a backend process

I've got an issue with Doctrine being somehow nasty with automagical tracking of entities and changes. I've got a UserManager which gets data for a new user from a form and sends the data to the backend which creates the corresponding database entries. As the backend is only inserting some basic data like username, I want to persist everything else like a collection of user roles given through the form.
So my method should look like this:
public function create(User $user)
{
$this->createUserInBackend($user);
$this->em->merge($user);
$this->em->persist($user);
$this->em->flush();
}
My issue now is that Doctrine either drops the data from the given $user and replaces it with the database content. This way I lose the chosen user roles. Without the merge() Doctrine tries an INSERT which is clearly not what I want.
I tried everything coming to my mind from fetching a managed copy before the merge, cloning or whatever. In all cases the objects are linked so I lose the data from the form (although their spl_object_hash differ).
Some more simplified details as requested:
class User
{
// Username is tracked by backend
private $username;
// Fullname is only tracked by frontend/Doctrine
private $fullname;
}
Variant 1:
public function create(User $user)
{
// The user entity gets passed to the backend, which does some stuff
// and also inserts the entity in the database.
$this->createUserInBackend($user);
// The entitiy is in the database, but not managed by the EM.
// Therefore Doctrine does an INSERT.
$this->em->persist($user);
$this->em->flush();
}
Variant 2:
public function create(User $user)
{
// The user entity gets passed to the backend, which does some stuff
// and also inserts the entity in the database.
$this->createUserInBackend($user);
// Now there's an entity in the db with ID and username, but not the fullname
$user = $this->em->merge($user);
// The merge finds the entry in the database and refreshes its data.
// This leads to $user->fullname which was given in the form to be emptied. :(
$this->em->persist($user);
$this->em->flush();
}
I also tried to put the return value from merge into an $otherUser variable, but the objects are linked and fullname still gets dropped.
I'd just need something to tell Doctrine that the new entity is managed, but it should not touch its data. I've looked into the underlying UnitOfWork, but couldn't find a trick to solve this.

Event Subscriber postUpdate query is not updated

I'm trying to compare data on the database before and after an upgrade, in order to perform the operations.
Specifically:
Association: Artist ManyToMany Soundtrack.
When I remove an artist by a record soundtrack, I check if that artist has been without associations, if true removes the artist.
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$em = $args->getEntityManager();
if ($entity instanceof Soundtrack) {
//$this->difference contains the difference between the artists before and the artists after the change.
if(!empty($this->difference)) {
foreach($this->difference as $m) {
$art = $em->getRepository('AcmeUserBundle:Artist')->findArtistByIdJoinSoundtrack($m);
var_dump($art->getId());
}
}
}
}
query findArtistByIdJoinSoundtrack():
public function findArtistByIdJoinSoundtrack($id) {
$qb = $this->createQueryBuilder('a');
$query = $qb->select(array('partial a.{id}'))
->innerJoin('a.soundtrack', 's')
->where('a.id = :id')
->setParameter('id', $id)
->setMaxResults(1)
->getQuery();
return $query->getSingleResult();
}
The problem comes when I run a query to see if the artist has been without associations, in fact querying in postUpdate:
$em->getRepository('AcmeUserBundle:Artist')->findArtistByIdJoinSoundtrack($m);
for example, if an artist had only one association with the soundtrack with id 71
and I just removed from the soundtrack with id 71 that artist, the query does not return an empty response but returns an element soundtrack with just 71 id.
In other words it is as if the database is not updated despite having used the event postUpdate that is invoked after the flush().
Why does this happen?
I also cleared the cache several times to make sure it was not that's the problem!
Please have a look at my answer here. Generally changing related entities is not allowed using a preUpdate listener. Your thought ...
[...] event postUpdate [...] is invoked after the flush().
... is not exactly correct - postUpdate is not invoked after flush() but inside.
The three post [postUpdate, postRemove, postPersist] events are called inside EntityManager#flush(). Changes
in here are not relevant to the persistence in the database.
(documentation)

Resources