Scroll down to the script below, click on any sentence (including terminal blocks!) to jump to that spot in the video!
With a Subscription, click any sentence in the script to jump to that part of the video!Login Subscribe
We've got a strange issue: we know that
AddPonkaToImageHandler is being called successfully by the worker process.... because it's actually adding Ponka to the images! But, for some reason... even though we call
$imagePost->markAsPonkaAdded()... which sets the
$ponkaAddedAt property... and then
$this->entityManager->flush()... it doesn't seem to be saving!
So.. you might wonder:
Do I need to call persist() on
Let's try it:
$this->entityManager->persist($imagePost). In theory, we should not need this: you only need to call
persist() on new objects that you want to save. It's not needed... and normally does nothing... when you call it on an object that will be updated.
But... what the heck... let's see what happens.
Oh! But before we try this... we need to do something very important! Find your terminal, press Ctrl+C to stop the worker, then restart it:
php bin/console messenger:consume
Why? As you know, workers sit there and run... forever. The problem is that, if you update some of your code, the worker won't see it! Until you restart it, it still has the old code stored in memory! So anytime you make a change to code that a worker uses, be sure to restart it. Later, we'll talk about how to do this safely when you deploy.
Let's see what happens now that we've added that new
persist() call. Upload one new file, find your worker and... yep! It was handled successfully. Did that fix the entity saving problem? Refresh the page.
Yikes! What just happened! The image shows up twice! One with the date set... and one without. To the database!
SELECT * FROM image_post \G
Yea... this one image is on two rows: I know because they're pointing to the exact same file on the filesystem. The worker... somehow... duplicated that row in the database.
This... is a confusing bug... but it has an easy fix. First, let's look at things from Doctrine's perspective. Internally, Doctrine keeps track of a list of all the entity objects that it's currently dealing with. When you query for an entity, it adds it to this list. When you call
persist(), if it's not already in the list, it's added. Then, when we call
flush(), Doctrine loops over all of these objects, looks for any that changed, and creates the appropriate UPDATE or INSERT queries. It knows whether or not an object should be inserted or updated because it knows whether or not it was responsible for querying for that object. By the way, if you want to nerd out on this topic more, this "list" is called the identity map... and it's just a big array that starts empty at the beginning of each request and gets bigger as you query or persist things.
So now let's think about what happens in our worker. When it deserializes the
AddPonkaToImage object, it also deserializes the
ImagePost object that lives inside. At that moment, Doctrine's identity map does not contain this object... because it did not query for it inside this PHP process - from inside the worker. That's why originally, before we added
persist(), when we called
flush(), Doctrine looked at the list of objects in its identity map - which was empty - and... did absolutely nothing: it doesn't know it's supposed to save the
When we added
persist(), we created a different issue. Doctrine is now aware that it needs to save this... but because it didn't original query for it, it mistakenly thinks that this should be inserted into the database as a new row, instead of updating.
Phew! I wanted you to see this because... it is kinda hard to debug. Fortunately, the fix is easy. And it touches on an important best-practice for your messages: include only the information you need. That's next.