Chapters
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3
Subscribe to download the code!Compatible PHP versions: ^7.1.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeIn addition to the payload, a message in RabbitMQ can also have "headers". Check that key out on our message. Woh! This contains a big JSON structure of the original class name and the data and class names of the stamps attached to the message!
Why did Messenger do this? Well, find your terminal and consume the async
transport:
php bin/console messenger:consume -vv async
This still works. Internally, the Symfony serializer uses the info on the headers
to figure out how to take this simple JSON string and turn it into the correct object. It used the type
header to know that the JSON should become an ImagePostDeletedEvent
object and then looped over the stamps and turned each of those back into a stamp object for the envelope.
The really nice thing about using the Symfony serializer in Messenger is that the payload
is this simple, pure JSON structure that can be consumed by any application in any language. It does contain some PHP class info on the headers, but another app can just ignore that. But thanks to those headers, if the same app does both send and consume a message, the Symfony serializer can still be used.
Shouldn't we Always use the Symfony Serializer?
But wait... if that's true - if the Symfony serializer creates messages that can be consumed by external systems or by our same app - then why isn't it the default serializer in Messenger? An excellent question! The reason is that the Symfony serializer requires your classes to follow a few rules in order to be serialized and unserialized correctly - like each property needs a setter method or a constructor argument where the name matches the property name. If your class doesn't follow those rules, you can end up with a property that is set on the original object, but suddenly becomes null when it's read from the transport. No fun.
In other words, the PHP serializer is easier and more dependable when everything is done by the same app.
Configuring the Symfony Serializer
Anyways, if you are using the Symfony serializer, there are also a few things that can be configured. Find your terminal and run:
php bin/console config:dump framework messenger
Check out that symfony_serializer
key. This is where you configure the behavior of the serializer: the format - json
, xml
or something else, and the context
, which is an array of options for the serializer.
Of course, you can also create a totally custom serializer service. And if you have the opposite workflow to what we just described - one where your app consumes messages that were sent to Rabbit from some other system - a custom serializer is exactly what you need. Let's talk about that next.
2 Comments
Hey Mokhtar T.!
Hmm, that's interesting. So it sounds like you have a situation where it might be normal to receive invalid messages from external services? This wasn't something I've really thought about before. But, if you do have this, I see your point: 3 invalid messages in a row will cause your worker process to fail so rapidly, that you would probably hit the startretries
limit. Here's what I'd recommend:
A) If you can, try avoiding this. I know, this seems obvious - and there is probably some reason why you have this situation. But in general, knowing that you may routinely receive invalid messages to your queue seems odd to me.
B) If you can't avoid it, you could set startsecs
to 0 to avoid it thinking that the restart was unsuccessful.
C) When you throw an exception during the decoding process, the normal Symfony console error system is invoked. This means that you should be able to listen to this event - https://symfony.com/doc/current/components/console/events.html#the-consoleevents-error-event or https://symfony.com/doc/current/components/console/events.html#the-consoleevents-terminate-event - and... do something... like add a pause (a literal sleep(1) ) so that the process waits long enough before exiting that supervisor knows that it DID start up successfully. A little odd... but I'm not sure if there is anything else you can do from these listeners.
D) If you're writing a custom deserializer, you could, I think, return a dummy Message object that has a handle that... does nothing. In other words, you WOULD always return a valid message object... but if there was a deserialization problem, then it's a dummy message whose handle does nothing.
So... a few "decent" options - I hope this will help.
Cheers!
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// 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.21.6
"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
}
}
Hello Ryan
sorry to put this question here but as part of serializing message from and external service you mentioned that in case we have an invalid message we should throw MessageEncodingException and this one will kill the process of our consumer and supervisor will restart it but by default supervisor has 3 retires so if we have more than 3 invalid message the queue will switch to idle status and our consumer won't working how to prevent that ? I'm thinking to increase supervisor retries but it looks anugly solution do you have any othe idea ? thanks in advance