Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Running the Mercure Service in the symfony Binary

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

Mercure itself is a "service" or "server" - kind of like MySQL or Elasticsearch. The Mercure server is called the "hub"... and there are several good ways to get it running. First, they have a managed version where they handle it all for you. This is great for production: it keeps things simple and you can help support the project.

Or, you can download Mercure and set it up locally. Or you can set up Mercure with Docker - that's totally supported. Or the final or is... if you're using the Symfony binary as your local web server then... well... it's already running!

The Embedded Mercure Hub

Head to your open terminal tab, clear the screen and run:

symfony server:status

As a reminder, way back at the start of this tutorial, we used the Symfony binary to run a local web server for us. Back at the browser, open a new tab and go to https://127.0.0.1:8000 - the URL to our site - then /.well-known/mercure.

Tip

The latest symfony binary no longer embeds Mercure. But it's still easy to set up. First, add a mercure service to your docker-compose.yaml file:

... line 1
services:
... lines 3 - 13
mercure:
image: dunglas/mercure
command: caddy run -config /etc/caddy/Caddyfile.dev
ports: ['80']
environment:
SERVER_NAME: ':80'
MERCURE_PUBLISHER_JWT_KEY: '!ChangeMe!'
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeMe!'

You can also copy the code block from the script below the video. Start the container by running:

docker-compose up -d

That's it! But instead of being accessible at the URL you see in the tutorial, the Mercure hub will be exposed on a random port. To find it, run:

symfony var:export --multiline

And look for the MERCURE_URL value - it should equal something similar to http://127.0.0.1:64150/.well-known/mercure. Put this into your address bar to see your Mercure Hub (you'll see the same error as in the video).

If everything is working... yes! You should see this error:

Missing "topic" parameter.

This is a Mercure hub. Yup, the Symfony binary comes with Mercure already running at this URL. We get that for free.

The Environment Variables

To communicate with this, head back over to your editor and open the .env file.

39 lines .env
... lines 1 - 29
###> symfony/mercure-bundle ###
# See https://symfony.com/doc/current/mercure.html#configuration
# The URL of the Mercure hub, used by the app to publish updates (can be a local URL)
MERCURE_URL=https://127.0.0.1:8000/.well-known/mercure
# The public URL of the Mercure hub, used by the browser to connect
MERCURE_PUBLIC_URL=https://127.0.0.1:8000/.well-known/mercure
# The secret used to sign the JWTs
MERCURE_JWT_SECRET="!ChangeMe!"
###

These three environment variables define values that are used in a new config file: config/packages/mercure.yaml. MERCURE_PUBLIC_URL is the public URL to the Mercure hub that our JavaScript will use to subscribe to messages and MERCURE_URL is the URL that our PHP code will use to publish messages. These are usually the same. MERCURE_SECRET is basically a password that will allow us to publish: more on that later.

mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: '*'

In our case, both URL variables already, by chance, point to the correct URL! Yay! But actually, if you're using the latest version of the Symfony binary... we don't even need these variables in this file! Why? Well, in addition to setting up Mercure for us, the Symfony binary also sets these environment variables automatically to their correct values.

Check it out. Back over in our editor, open public/index.php. Let me close a few things... then open it. Cool. Right after the runtime load, I'll paste in some code.

14 lines public/index.php
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
dd(array_filter($_SERVER, function($item) {
return str_contains($item, 'MERCURE');
}, ARRAY_FILTER_USE_KEY));
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

This looks fancy, but I'm basically dumping the $_SERVER variable... except only the keys that contain MERCURE. The $_SERVER variable - among other things - will contain all environment variables. I'm filtering for MERCURE basically... because I don't want to accidentally publish any secret keys from my computer to the internet... as much fun as that would be.

Anyways, this will run before the .env file is loaded, so it will only print real environment variables. Back over on our site, refresh!

Tip

If you're using the docker-compose.yaml setup described earlier, you will only see 2 environment variables here... which are the only 2 you need anyways.

Yay! We see 4 environment variables including 2 we need! The first one is just a flag that tells us that the Symfony binary is running Mercure... and that last one is there for legacy reasons: we don't need it.

This means that our app is already configured and ready to talk to our Mercure Hub! In production, you'll need to run a real Mercure Hub and set these environment variables manually, however you do that in your hosting environment.

So... we have a Mercure hub running! What does that... mean? Well, it's a central place where some things can listen for messages and other things can publish messages. Next, let's do both of these things: listen to a Mercure "topic" in JavaScript and publish messages to it, both from the command line - just to see how it works - and from PHP, which is our real goal.

Leave a comment!

26
Login or Register to join the conversation
Fredbadlieutenant Avatar
Fredbadlieutenant Avatar Fredbadlieutenant | posted 1 year ago

Hello team :)

I'm using the symfony CLI 4.26 and when i try to access localhost:8000/.well-known/mercure, it's a 404 i receive.
Then, when i try to dump the env vars, searching for MERCURE, it returns an empty array.

the symfony changelog states, for 4.26 :

- Remove built-in Mercure support (use explicit Docker Compose support instead) .

Should i understand that the Mercure hub is no more embedded in the symfony binary ? Or am i doing something wrong ?
i'm quite confuse . For now, i think i'll try to set things up using Docker

Thanks for your help and for the great tutorials .

1 Reply

Hey Fredbadlieutenant!

Yup - Fabien pinged me about this. It's on my TODO list (hopefully tomorrow) to make some fixes to the tutorial for it :). You WILL now need some Docker config. Once you have it, the Symfony binary should see it (just like with the database stuff) and expose the env vars. But I haven't played with it yet to be sure. Here's the config that you should need in your docker-compose.yaml file (but it's untested by my at the moment):


# docker-compose.yaml
# ...
services:
# ...

mercure:
image: dunglas/mercure
command: caddy run -config /etc/caddy/Caddyfile.dev
ports: ['80']
environment:
SERVER_NAME: ':80'
MERCURE_PUBLISHER_JWT_KEY: '!ChangeMe!'
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeMe!'

Cheers!

Reply
Fredbadlieutenant Avatar
Fredbadlieutenant Avatar Fredbadlieutenant | weaverryan | posted 1 year ago | edited

Many thanks weaverryan for the quick reply,
I will try that ... Thanks !

EDIT: it works perfectly !

Reply

YES! Thanks for letting me know - I'm jumping in right now to prep the changes for the tutorial :)

Reply

Hi Team
after watching the video and doing the same I get this:
No route found for "GET https://127.0.0.1:8000/.well-known/mercure"
What can be please ?

Reply

Hey Blerim,

Hm, do you run Symfony built-in server? I.e. started the server with "symfony serve" command? Or do you have a real web server set up like Apache or Nginx?

Also, please see the TIP in this chapter about the latest symfony binary no longer embeds Mercure. But it's still easy to set up, just follow the instructions in that TIP note.

Cheers!

Reply

Hi victor
Im using web server also I started with "symfony serve", which server should I use for mercure ?
Thanks a lot

Reply

Hey Basha,

Yes, you need to start it with Symfony web server, i.e. run:

$ symfony serve

But please, read the first TIP on this page: https://symfonycasts.com/sc... - follow the instructions there to make it work, it's not working out of the box anymore, the note is explaining things well I think.

Cheers!

Reply

I fixed them,
Thank you

Reply

Hey Basha,

Great! I'm happy you were able to fix it. Thanks for confirming it works for you now.

Cheers!

Reply

HI victor
That you answer me, but can I use the "finally or If I using the Symfony binary as my local web server", It must be already runing"
https://symfonycasts.com/sc..., min.-3:54 .
I tried to download mercure.exe file and put it in my project ... but again i can not activate mercure

Reply

Hey Basha,

As the note (I'm remferencing to) says - this behavior was changed in a newer version of Symfony CLI, so you have to do a few moe extra steps to make it running.

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | posted 1 year ago

I'm using ubuntu 20.10 and symfony binary version 4.26.8
I installed docker.io and docker-composer
I run also sudo apt install virtualbox-qt
But when I try docker-compose up -d raise an error:
Couldn't connect to Docker daemon at http+docker://localhost - is it running?

If it's at a non-standard location, specify the URL with the DOCKER_HOST environment variable.

I don't know how to get rid of this error

Best regards

Reply

Hey Diaconescu,

IIRC you have to run the Docker manually, please, check the Docker docs about how to start it properly for your specific OS. When the Docker will be up and running in your system - it should work.

Cheers!

Reply

Hi team!
Are you going to publish a demo of mercure without using symfony turbo in this course?
Some reactjs example using api platform?

Thanks!

Reply

Hey JuanLuisGarciaBorrego!

Not in this course, unfortunately. But Mercure as a general topic *is* something that is frequently requested and that I'd like to cover. But no specific plans for that tutorial at the moment :). However, we cover the mechanics of Mercure pretty well in this tutorial. The biggest difference would be that you would want to publish some JSON information to Mercure instead of turbo-stream HTML. Then, in JavaScript/React, you would read this JSON and do work with it. For example, you might use the Symfony serializer to publish new or updated JSON representations of some API resource so that you could update that data in React from Mercure instantly. I know that's a bit of a generic description, but let me know if that helps.

Cheers!

Reply
Tristan P. Avatar
Tristan P. Avatar Tristan P. | posted 1 year ago

Following the tutorial using symfony server I got no problems getting the mercure hub running.

Trying to succeed doing the same thing in my ddev environment on the other hand is hopeless. Can anybody help me? I'm trying since two days to configure it.

I created a docker-compose.mercure.yaml in my .ddev directory which is a slightly edited version of the example docker-compose.yaml from the mercure docs (here):


version: "3.7"

services:
caddy:
image: dunglas/mercure
restart: unless-stopped
environment:
# Uncomment the following line to disable HTTPS
#SERVER_NAME: ':80'
MERCURE_PUBLISHER_JWT_KEY: '!ChangeMe!'
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeMe!'
MERCURE_EXTRA_DIRECTIVES: |-
cors_origins "http://127.0.0.1"
anonymous
# Uncomment the following line to enable the development mode
#command: /usr/bin/caddy run -config /etc/caddy/Caddyfile.dev
ports:
- "80"
- "443"
volumes:
- caddy_data:/data
- caddy_config:/config

volumes:
caddy_data:
caddy_config:


The only modifications i made were to only define the internal ports according to the ddev-documentary here where they say:

* Define only the internal port in the ports section for docker-compose. The hostPort:containerPort

convention normally used to expose ports in docker should not be used

here, since we are leveraging the ddev router to expose the ports.

If I don't do so, I oviously get the following error-message:


Failed to restart prgmgmt-api: Unable to listen on required ports, port 443 is already in use,
Troubleshooting suggestions at https://ddev.readthedocs.io...

But that is still not the right config, since i can't reach my mercur-hub. I can not open the ../.well-known/mercure URL and get an error when i try to use the broadcast-function:


Failed to send an update.

Can anybody help me how to configure a mercure-hub using ddev?

Reply

Hey Tristan P.!

Ok, let's see! I might have a simple explanation for you, depending on what you're trying to achieve.

It is this: in order to use the Mercure hub in your dev environment with the symfony binary web server, you need ZERO docker config. This is different than the Symfony binary integration with, for example, MySQL. In that case, you DO add MySQL to your docker-compose.yaml, and then the Symfony binary "notices" it and helps export the environment variables to you.

But for Mercure, the Symfony binary itself comes packages with Mercure inside automatically. There is nothing to install - it "just works". When you start the Symfony binary web server, Mercure is already running.

Does this help? Or did you purposely want to get Mercure running via Docker?

Cheers!

Reply
Tristan P. Avatar

Thanks for the answer. I see. When I used the symfony binary plus the Symfony Local Web Server I had no problems using the Mercure hub and I had nothing to configure. But I want to use/test Turbo Streams in a "real" project with my team. And in the team we are not using the symfony server, we are using a tool called ddev here

DDEV is an open source tool that makes it dead simple to get local PHP development environments up and running within minutes. It's powerful and flexible as a result of its per-project environment configurations, which can be extended, version controlled, and shared. In short, DDEV aims to allow development teams to use Docker in their workflow without the complexities of bespoke configuration.

.

It is a nice tool for teams, since you can be sure, that evereybody is working in the same environment.

A custom service like mercure is implemented by creating a docker-compose.[servicename].yaml in the .ddev directory.

That is the reason why I want to get Mercure running via Docker.

Reply

Hey Tristan P.!

Gotcha! I haven't set up Mercure with Docker yet (and I haven't used ddev), but I'll do my best to offer some debugging suggestions :).

Based on the ddev docs, it looks like you did the correct thing with the ports config, but it also looks like you are supposed to have HTTP_EXPOSE and HTTPS_EXPOSE env vars to tell ddev how to map things - source: https://ddev.readthedocs.io... (though, I'm doing some guessing here, since ddev is new to me!)

Also, you can apparently run ddev debug compose-config to see the whole, final docker setup, which might help you spot the issue. You might also try running on some ports *other* than 80 and 443, as these are bound to your web server. The reason it works with the Symfony binary is that it integrates Mercure directly *into* its web server. But my guess in your situation is that you have a web server already running in 80/443, and now you want to load an additional web server only for Mercure.

This... maybe won't be helpful at all - but let me know :).

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | posted 1 year ago

I have a repository at git@github.com:petre-symfony/books-with-vue-and-symfony.git. In file treeBookContent.vue that's rendered in a modal I have 26 line event.stopPropagation. How can I change the code to get rid of this line?

Reply

Hey Diaconescu,

It would be easier if you linked to that line in your comment... I suppose you're talking about this line: https://github.com/petre-sy... - I'm not sure it's possible, as you should stop propagation, otherwise the event propagation will continue. The question is why do you want to get rid of it? Well, you can literally remove that line, this way you will get rid of it... but I suppose this does not fit for you somehow? :) Having more context about it would be great.

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | victor | posted 1 year ago

No it doesn't fit. Idea is something like so. The content of the book is displayed with al the nodes collapsed when it's open first time and looks something like so https://imgur.com/a/9lij93g. After that you can click left arrow and unfold the node like here https://imgur.com/a/STJhbs7. But as you can see one node can contain other more nodes that can be unfolded too as here https://imgur.com/a/OVDEMKt. Further I want to collapse the nodes inside the another one, one by one, like here https://imgur.com/a/NOrqDe4. Without stopPropagation line not only PEIZAJE node would be collapsed but the entire 1907 node which I don't want.
I feared that stopPropagation wouldn't have potentially side effects

Reply

Hey Diaconescu,

OK, so, without stopPropagation line the given node and all its child nodes will be collapsed as I understand, and it's not something you need, so you need to keep that stopPropagation line. But what's the problem with keeping that line? I still didn't get. Does it work as you want with stopPropagation?

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | victor | posted 1 year ago

It's ok. It does what I want to do

Reply

Hey Diaconescu,

Perfect! Than you just need this line in your case, and it's nothing wrong with it, that's OK :)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=7.4.0",
        "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.18.5
        "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
    }
}