Chapters
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
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!
Mercure: Pushing Stream Updates Async
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 SubscribeTurbo streams would be much more interesting if we could subscribe to something that could send us stream updates in real time.
The Use-Case: Pushing Streams Directly to Users
Like, imagine we're viewing a page... and generally minding our own business. At the same moment, someone else - on the other side of the world - adds a new review to this same product. What if that review instantly popped onto our page and the quick stats updated? That would be... incredible!
Or imagine if, in ProductController, inside of the reviews action, after a successful form submit, we could still return a redirect like we were doing before... but we could also push a stream to the user that updates some other parts of the page, like the quick stats area. I said earlier that returning a redirect and a stream isn't possible. But... that's not entirely true.
The truthiest truth is that both of these scenarios are totally possible. How? Turbo Streams comes with built-in support to listen to a web socket the returns Turbo Stream HTML. It also supports doing that same thing with server-sent events, which are kind of a modern web socket: it's a way for a web server to push information to a browser without us needing to make an Ajax call to ask for it.
Hello Mercure!
And fortunately, in the Symfony world, we have great support for a technology that enables server-sent events: Mercure. Mercure could... probably be its own tutorial, so we'll just cover the basics.
Mercure is a "service" that you run, kind of like your database service, Elasticsearch or Redis. It allows, in JavaScript for example, to subscribe to messages. Then, in PHP, we can publish messages to Mercure. Anything that has subscribed will instantly receive those messages and can do something with them. If you're familiar with WebSockets, it has a similar feel.
Installing the Mercure Libraries
We're going to get Mercure rocking... and it's going to really make things fun. To start, install a package that makes it easy to work with Mercure and Turbo. At the command line, run:
composer require "symfony/ux-turbo-mercure:^1.3"
Tip
The symfony/ux-turbo-mercure is deprecated in favor of symfony/ux-turbo which already
contains the cool Mercure stuff. Just install symfony/mercure-bundle to get it working:
composer require symfony/mercure-bundle
Or to get the version used in the tutorial, continue with:
composer require "symfony/ux-turbo-mercure:^1.3"
This installs several things. First, a PHP library called mercure that helps talk to the Mercure service in PHP. Second, a MercureBundle that makes that even easier in Symfony. And third, a symfony/ux-turbo-mercure library that gives us a special Stimulus controller that helps Mercure and Turbo Streams work together. Go team!
This executed a recipe... so run git status to see what it did.
git status
Ok cool. Let's look at .env first. At the bottom, we have three new environment variables that will help us talk to Mercure. More about these in a few minutes. The recipe also modified controllers.json. Remember: this means that a new Stimulus controller is now available that lives inside this bundle. We'll use that 2 chapters from now.
Tip
Instead of a new section in this file, find the existing `@symfony/ux-turbo section.
It will have a key called mercure-turbo-stream. Change its enabled key to true`
to activate the Stimulus controller we'll be using.
This also enabled a bundle... and added a new library to our package.json file. We've seen this several times before with UX packages: this adds a new package to our project... but instead of downloading the code, it already lives in the vendor/ directory.
To get that part properly set up, near the bottom of the terminal output, it tells us to stop Encore and run yarn install --force.
In the other tab, hit Ctrl+C to stop Encore and run:
yarn install --force
When that finishes, restart Encore:
yarn watch
Ok, we just installed some PHP and JavaScript code that's going to help us communicate with Mercure. But... we don't actually have a Mercure service running yet! That's like installing Doctrine... but without MySQL or Postgresql running!
So next, let's get the Mercure service running. There are a bunch of ways to do this. But if you're using the Symfony binary web server like we are... then... it's already done!
14 Comments
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
"doctrine/annotations": "^1.0", // 1.13.1
"doctrine/doctrine-bundle": "^2.2", // 2.3.2
"doctrine/orm": "^2.8", // 2.9.1
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"sensio/framework-extra-bundle": "^6.1", // v6.1.4
"symfony/asset": "5.3.*", // v5.3.0-RC1
"symfony/console": "5.3.*", // v5.3.0-RC1
"symfony/dotenv": "5.3.*", // v5.3.0-RC1
"symfony/flex": "^1.3.1", // v1.21.6
"symfony/form": "5.3.*", // v5.3.0-RC1
"symfony/framework-bundle": "5.3.*", // v5.3.0-RC1
"symfony/property-access": "5.3.*", // v5.3.0-RC1
"symfony/property-info": "5.3.*", // v5.3.0-RC1
"symfony/proxy-manager-bridge": "5.3.*", // v5.3.0-RC1
"symfony/runtime": "5.3.*", // v5.3.0-RC1
"symfony/security-bundle": "5.3.*", // v5.3.0-RC1
"symfony/serializer": "5.3.*", // v5.3.0-RC1
"symfony/twig-bundle": "5.3.*", // v5.3.0-RC1
"symfony/ux-chartjs": "^1.1", // v1.3.0
"symfony/ux-turbo": "^1.3", // v1.3.0
"symfony/ux-turbo-mercure": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.0-RC1
"symfony/webpack-encore-bundle": "^1.9", // v1.11.2
"symfony/yaml": "5.3.*", // v5.3.0-RC1
"twig/extra-bundle": "^2.12|^3.0", // v3.3.1
"twig/intl-extra": "^3.2", // v3.3.0
"twig/string-extra": "^3.3", // v3.3.1
"twig/twig": "^2.12|^3.0" // v3.3.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
"symfony/debug-bundle": "^5.2", // v5.3.0-RC1
"symfony/maker-bundle": "^1.27", // v1.31.1
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/stopwatch": "^5.2", // v5.3.0-RC1
"symfony/var-dumper": "^5.2", // v5.3.0-RC1
"symfony/web-profiler-bundle": "^5.2", // v5.3.0-RC1
"zenstruck/foundry": "^1.10" // v1.10.0
}
}
What JavaScript libraries does this tutorial use?
// package.json
{
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.13.13
"@fortawesome/fontawesome-free": "^5.15.3", // 5.15.3
"@hotwired/turbo": "^7.0.0-beta.5", // 1.2.6
"@popperjs/core": "^2.9.1", // 2.9.2
"@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
"@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/Resources/assets", // 0.1.0
"@symfony/ux-turbo-mercure": "file:vendor/symfony/ux-turbo-mercure/Resources/assets", // 0.1.0
"@symfony/webpack-encore": "^1.0.0", // 1.3.0
"bootstrap": "^5.0.0-beta2", // 5.0.1
"chart.js": "^2.9.4",
"core-js": "^3.0.0", // 3.13.0
"jquery": "^3.6.0", // 3.6.0
"react": "^17.0.1", // 17.0.2
"react-dom": "^17.0.1", // 17.0.2
"regenerator-runtime": "^0.13.2", // 0.13.7
"stimulus": "^2.0.0", // 2.0.0
"stimulus-autocomplete": "https://github.com/weaverryan/stimulus-autocomplete#toggle-event-always-dist", // 2.0.0
"stimulus-use": "^0.24.0-1", // 0.24.0-2
"sweetalert2": "^11.0.8", // 11.0.12
"webpack-bundle-analyzer": "^4.4.0", // 4.4.2
"webpack-notifier": "^1.6.0" // 1.13.0
}
}