Worker Command

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Even if I refresh the page, now that our messages aren't being handled immediately... the four most recent photos don't have Ponka in them. That's tragic! Instead, those messages were sent to the doctrine transport and are waiting patiently inside of a messenger_messages table.

So... how can we read these back out and process them? We need something that can fetch each row one-by-one, deserialize each message back into PHP, then pass it to the message bus to be actually handled. That "thing" is a special console command. Run:

php bin/console messenger:consume

You won't see any output... yet... but, unless we messed something up, this is doing exactly what we need: reading each message, deserializing it, and sending it back to the bus for handling.

So... let's go refresh. Woh! It did work! All 4 messages now have Ponka on them! We're saved!

messenger:consume -vv

To make this more interesting, as you can see, it says to run this command with -vv if you want to see what it's doing behind-the-scenes. But... interesting, once the command finished reading and handling all 4 messages... it didn't quit: it's still running. And if we restart it with -vv on the end:

php bin/console messenger:consume -vv

... it does the same. A command that "handles" messages from a queue is called a "worker". And the job of a worker is to watch and wait for new messages to be added to the queue and handle them the instant one is added. It waits and runs... forever! Well, that's not totally true - but more on that later when we talk about deployment.

Let's peek back over in our "queue" - the messenger_messages table:

SELECT * FROM messenger_messages \G

Yep! This holds zero rows because all those messages were processed and removed from the queue. Back at the browser, let's upload... how about... 5 new photos. Woh... that was awesome fast!

Ok, ok, move back to the terminal that's running the worker! We can see it doing its job! It says: "Received message", "Message handled by AddPonkaToImageHandler" then "AddPonkaToImage was handled successfully (acknowledging)". That last part, "acknowledging" means that Messenger notified the Doctrine transport that the message was handled and can be removed from the queue.

Then... it keeps going to the next message... and the next... and the next... until it's done. So if we refresh... Ponka was added to all of these! Let's do it again - upload 5 more photos. And... let's refresh and watch... there's Ponka! We can see them being handled little-by-little. So much wonderful Ponka!

Ok, this would be cooler if our JavaScript automatically refreshed the image when Ponka was added... instead of me needing to refresh the page... but that's a totally different topic, and one that's covered by the Mercure component in Symfony.

And... that's it! This messenger:consume command is something that you'll have running on production all the time. Heck, you might decide to run multiple worker processes. Or, you could even deploy your app to a totally different server - one that's not handling web requests - and run the worker processes there! Then, handling these messages wouldn't use any resources from your web server. We'll talk more about deployment later.

Problem: Database Didn't Update?

Because right now... we have a problem... a kinda weird problem. Refresh the page. Hmm, the original photos all say something like:

Ponka visited 13 minutes ago. Ponka visited 11 minutes ago.

But, since we made things asynchronous, these all say:

Ponka is napping. Check back soon.

Open up the ImagePost entity and find the $ponkaAddedAt property. This is a datetime field, which records when Ponka was added to the photo. The message on the front-end comes from this value.

For the original ones... back when the whole process was synchronous, this field was set successfully. But now... it looks like it isn't. Let's check the database to be sure. Over in MySQL, run:

SELECT * FROM image_post \G

All the way back in the beginning... ponka_added_at was being set. But now they're all null. So... our images are being processed correctly, but, for some reason, this field in the database is not. If we look inside AddPonkaToImageHandler... yea... right here: $imagePost->markPonkaAsAdded(). That sets the property. So... why isn't it saving?

Let's figure out what's going on next and learn a bit more about some "best practices" when it comes to building your message class.

Leave a comment!

This tutorial is built with Symfony 4.3, but will work well on Symfony 4.4 or 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "intervention/image": "^2.4", // 2.4.2
        "league/flysystem-bundle": "^1.0", // 1.1.0
        "sensio/framework-extra-bundle": "^5.3", // v5.3.1
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/flex": "^1.1", // v1.4.4
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.5", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.7
        "symfony/maker-bundle": "^1.0", // v1.12.0
        "symfony/test-pack": "^1.0" // v1.0.6
    }
}