If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe just broke our image deleting process into smaller pieces by creating a new command class, a new handler and dispatching that new command from within the handler! This... technically isn't anything special, but it is cool to see how you can break each task down into as small pieces as you need.
But let's... make sure this actually works. Everything should still process synchronously. Delete the first image and... refresh to be sure. It's gone!
Before we handle the new command class asynchronously, we need to think about something. If, for some reason, there's a problem removing this ImagePost
from the database, Doctrine will throw an exception right here and the file will never be deleted. That's perfect: the row in the database and file on the filesystem will both remain.
But if deleting the row from the database is successful... but there's a problem deleting the file from the filesystem - like a temporary connection problem talking to S3 if our file were stored there... that file would... actually.. never be deleted! And... maybe you don't care. But if you do, you could wrap this entire block in a Doctrine transaction to make sure it's all successful before finally removing the row. Of course... once we change this message to be handled asynchronously, deleting the actual file will be done later... and we will be, kinda "trusting" that it will be handled successfully. We're going to talk about failures and retries really soon.
Anyways, now that we've broken this into two pieces, head over to config/packages/messenger.yaml
. Copy the existing line, paste and route the new DeletePhotoFile
to async
.
framework: | |
messenger: | |
... lines 3 - 12 | |
routing: | |
... lines 14 - 15 | |
'App\Message\DeletePhotoFile': async |
Cool! With any luck, the row in the database will be deleted immediately... then the file a few seconds later.
And because we just made a change to some handler code, go over, stop our worker and restart it:
php bin/console messenger:consume -vv
Testing time! Refresh to be safe... and let's try deleting. Check out how much faster that is! If you scoot over to the worker terminal... yea, it's doing all kinds of good stuff here. Oh, and fun! An exception occurred while handling one of the messages - a file wasn't found. I think that's from the duplicate row caused by the Doctrine bug a few minutes ago: the file was already gone when the second image was deleted. The cool thing is that it's already retrying that message in case it was a temporary failure. Eventually, it gives up and "rejects" the message.
Let's try this whole crazy system together! Upload a bunch of photos... then... quick! Delete a couple! If you look at the worker... it's all beautifully mixed up: a few AddPonkaToImage
objects are handled here... then DeletePhotoFile
.
Oh, and by the way: if you look at the routing
section in messenger.yaml
, you'll usually route thing by their exact class name: App\Message\AddPonkaToImage
goes to async
. But you can also route via interfaces or base classes. For example, if you have a bunch of classes that should go to the async
transport, you could create your very own interface - maybe AsyncMessageInterface
- make your messages implement that, then only need to route that interface to async
here. But be careful because, if a class matches multiple routing lines, it will be sent to all those transports. Oh, and last thing - in case you have a use-case, each routing entry can send to multiple transports.
Next: remember how the serialized message in the database was wrapped in something called an Envelope
? Let's learn what that is and how its stamp system gives us some cool superpowers.
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // v1.8.0
"doctrine/doctrine-bundle": "^1.6.10", // 1.11.2
"doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
"doctrine/orm": "^2.5.11", // v2.6.3
"intervention/image": "^2.4", // 2.4.2
"league/flysystem-bundle": "^1.0", // 1.1.0
"phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.1
"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.9", // v1.18.7
"symfony/framework-bundle": "4.3.*", // v4.3.2
"symfony/messenger": "4.3.*", // v4.3.4
"symfony/property-access": "4.3.*", // v4.3.2
"symfony/property-info": "4.3.*", // v4.3.2
"symfony/serializer": "4.3.*", // v4.3.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": {
"easycorp/easy-log-handler": "^1.0.7", // v1.0.7
"symfony/debug-bundle": "4.3.*", // v4.3.2
"symfony/maker-bundle": "^1.0", // v1.12.0
"symfony/monolog-bundle": "^3.0", // v3.4.0
"symfony/stopwatch": "4.3.*", // v4.3.2
"symfony/twig-bundle": "4.3.*", // v4.3.2
"symfony/var-dumper": "4.3.*", // v4.3.2
"symfony/web-profiler-bundle": "4.3.*" // v4.3.2
}
}