Publish messages to dunglas/mercure from any PHP-Based Application including Laravel/Lumen Broadcasting & Notification
Recently I have been doing some hands-on with the dunglas/mercure, and thought to build a package that can help any PHP-based application to publish messages to the Mercure Hub. The package also helps to publish messages from the Laravel/Lumen including the Broadcasting & Notification channel out of the box.
Recently I have published an article on Mercure and how to use it with Laravel/Lumen. If you haven’t read it, then you can give it a try. But the article doesn’t include how to use it with Events & Notifications. Rather instructs how to use it in the frontend and to send events through the framework.
Installation
To install the package, you’ll require to have composer
on your machine. Next, run the following command.
composer require anik/mercure
The above command will install the package and its dependencies on your project.
For any PHP-based applications (Laravel/Lumen developer can skip)
Next, you’ll need to get the publisher instance. From your codebase, you can do the following.
<?php// From wherever you need to call the publisher.
$config = [
"url" => "https://mercure.host/.well-known/mercure",
"jwt" => "PUBLISHER_JWT",
];$publisher = \Anik\Mercure\Factory\Publisher::instance($config);
This is the bare minimum requirement for the configuration to get the publisher instance. Here, in the $config
array, you MUST have to have the url
key with the endpoint explicitly mentioned. Till now, it’s been /.well-known/mercure
. If they change it in the future, just change the endpoint then.
Next, in the $config
array, you can have any of the following set.
jwt
— The signed JWT key, which will be used to publish the messages.provider
— A callable that must return a valid JWT string.secret
andpayload
— If you want to generate a JWT to be created on the fly before making requests, you must have to provide thesecret
with as a string and claims in thepayload
as an array. Make sure you know what to send when generating the JWT from that payload. You can also providealgo
as a part of the$config
‘s root key. That algorithm will be used to generate the JWT. Also, make sure your server/machine has the required modules installed to use that algo.http_client
— The client that is an instance of theSymfony\Contracts\HttpClient\HttpClientInterface
which will be used to push the updates to the Mercure hub. If it is not provided or not an instance of theHttpClientInterface
, then it’ll use the default client.
After getting the $publisher
instance, we can immediately publish the update to the Mercure hub. To publish a message, we just need the following snippet.
<?php
use Symfony\Component\Mercure\Update;// as we have the $publisher now,
$update = new Update(
$arrayOfTopics,
$myMessageInStringFormat,
$isAPrivateMessage,
null,
$typeOfMessageType,
null
);// Publish the message to the mercure hub
$publisher($update);
$arrayOfTopics
can either be an array of topics or a string variable as a single topic name. Any of these will work.$mymessageInStringFormat
is the message that will be delivered to the subscriber. It must have to be a string. Even if you know still mentioning here, thejson_encode
will convert the array into a string.$isAPrivateMessage
is a boolean value that denotes if it’s a private type of message or anyone connected to the topic will receive this message (public)?$typeOfMessageType
a string value, that means if you used anaddEventListener
then what was the type in that listener? If passed an empty or null value, the default "message” type will be used andes.onmessage = () => {}
listener to be used in the frontend.
The 4th param is for string $id
and the 6th param is for int $retry
. You’ll find them in the Mercure hub spec. And can also pass those values.
Note:
If the Mercure hub cannot be connected or the JWT is invalid, it’ll raise Exceptions. You’ll need to catch those exceptions.
Using the package with Laravel/Lumen
At Least Laravel/Lumen version the package requires is Laravel/Lumen 6.x
. If you’re using any version lower than the requirement, you won’t be able to use the package.
Laravel: If you’re using the supported Laravel version and you’re using Laravel, then due to the package discovery feature, you’re all good to go. You can run php artisan vendor:publish
to publish the configuration.
Lumen: If you’re using the supported Lumen version, you’ll need to register the service provider. Register the following provider in your bootstrap/app.php
file.
$app->register(
\Anik\Mercure\Provider\MercureServiceProvider::class
);
As Lumen doesn’t support vendor:publish
command, so you’ll need to copy the vendor/anik/mercure/src/config/mercure.php
config file in your project root’s config
directory.
Understanding the configuration file
mercure.default
— The default connection name to use when resolving the DI instances.mercure.enable_broadcasting
— The service provider doesn’t bind the broadcasting driver by default. If you set the variable to true, it then registers the driver.mercure.enable_notification
— The service provider doesn’t bind the notification channel by default. If you set the variable to true, it then registers the driver.mercure.connections
— A set of connections for the hubs.mercure.default
chooses from this set of connections.mercure.connections.*.url
— A MUST set string URL pointing to the hub endpoint, to which the messages will be published.mercure.connections.*.jwt
— A JWT string that determines the authorization to post the update messages on topics.mercure.connections.*.provider
— An alternative to themercure.connections.*.jwt
. A callable that should return a JWT string.mercure.connections.*.secret
[string] andmercure.connections.*.payload
[array] — An alternative tomercure.connections.*.jwt
andmercure.connections.*.provider
. But*.secret
and*.payload
must have to be available together. Can be used to generate the JWT on the fly. Must know what to set on the*.payload
array.mercure.connections.*.http_client
— An HTTP client. The implementation ofSymfony\Contracts\HttpClient\HttpClientInterface
. Will use the default client if left empty or not an implementation ofHttpClientInterface
.
Publish message from Laravel/Lumen
If you’re using Laravel/Lumen you can also publish messages with the above-mentioned pattern for generic usage. But with Laravel/Lumen, you can use the Dependency Injection for the following classes.
<?php
use Symfony\Component\Mercure\Publisher;
use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Mercure\Update;// in my controller/jobs, wherever DI is supported.
public function controllerMethod (PublisherInterface $publisher)
{
return $publisher(new Update('topic', 'msg', true));
}public function anotherMethod (Publisher $publisher)
{
return $publisher(new Update('topic', 'msg', true));
}
There is also an arbitrary string binding. To use that, you can do the following.
<?php
// anywhere in your codebaseapp('mercure')(new Update('topic', 'msg', true));
In all the above cases, it’ll use the default hub’s connection.
Broadcasting through the Mercure
Laravel ships with broadcasting preinstalled. But if you’re using Lumen, you’ll need to install the illuminate/broadcasting
package. Register the service provider. And copy the broadcasting.php
configuration.
In both Laravel and Lumen, you’ll need to add the driver in your broadcasting.php
config. So, it’ll be like.
<?php
// ... configuration above
'connections' => [
// ... 'mercure' => [
'driver' => 'mercure',
'connection' => 'hub',
], // ...
],
// ...
Here, broadcasting.connections.mercure
is the connection name. You can name it whatever you want. broadcasting.connections.mercure.driver
must have to the mercure
driver. broadcasting.connections.mercure.connection
value is set to hub
pointing which mercure.connections.*
that will be used when broadcasting the event. If unspecified, it’ll then fall back to the config mercure.default
‘s value.
To use the mercure
driver, set mercure.enable_broadcasting
value to true as explained in the configuration section.
You’ll also need to set the broadcasting.default
to mercure
(the name you used in the broadcasting.connections
).
So, after you broadcast the event through Mercure, in your client you’ll receive the message in {"event": "event_name", "data": "data_you_passed"}
format.
Tweaks for sending broadcasts
Like the other broadcasting drivers, you’ll use the same class event class to broadcast the event to Mercure. But as the Mercure allows a set of configurations (private, id, retry, type), you can pass the following values (Event::broadcastWith method or as public property) to work as configuration.
__msg_private
— All the update messages are default sent as private. If you want to send the messages as public, set this variable tofalse
.__msg_id
— If you want to set the message ID. set your desired value.__msg_retry
— No of retries.__msg_type
— The type of the message. Falls back to the event name.
Notes:
- It’s your responsibility to choose the channel names. The package doesn’t touch the channel names. If you set them as private or public or presence, they’ll be cast to string and used as the topic names accordingly.
- The package also sends the message to all the channels. If you want your messages to be treated separately, broadcast them separately.
- If your channels are empty, the package will not send any message to the hub.
- If you use the previously mentioned keys
__msg_*
then these keys will not be forwarded to the client.
Notifications through Mercure
Laravel ships with notification preinstalled. But if you’re using Lumen, you’ll need to install the illuminate/notifications
package. Register the service provider.
Next, to use the mercure
driver, setmercure.enable_notification
value to true as explained in the configuration section.
Then in your Notification instance, from the via
method, you can return mercure
driver as the notification channel. Even if you return \Anik\Mercure\Channel\MercureChannel::class
, it’ll also work. When using the MercureChannel::class
, setting mercure.enable_notification
value to true is not required.
- Your notification class should return the topic name or array of topic names from the
Notification::broadcastOn()
method. - If your class contains the
toMercure
method, whatever is passed will get broadcasted to the Mercure hub. - In absence of the
toMercure
method, it’ll look for thetoArray
method in your notification class and the data will get passed to the hub. - In absence of the both methods, it’ll just pass an empty “string” to the hub.
- The event name will be parsed in order of the presence of the method. Firstly, it’ll check if
broadcastType
method. Otherwise, it’ll check for thebroadcastAs
method. In absence of them, it’ll use the class name as the event name.
Notes:
- It’s your responsibility to choose the channel names. The package doesn’t touch the channel names. If you set them as private or public or presence, they’ll be cast to string and used as the topic names accordingly.
- The package also sends the message to all the channels. If you want your messages to be treated separately, broadcast them separately.
- If your channels are empty, the package will not send any message to the hub.
- If you use keys with
__msg_*
for the payload, then these keys will not be forwarded to the client.
Graceful publishing
By default when using instances from app('mercure')
, app(Publisher::class)
or app(PublisherInterface::class)
and calling the publish
method to send updates manually, it’ll raise an exception if you’re not allowed to publish the update to the hub due to JWT or the Mercure hub cannot be connected.
If you want to publish updates gracefully, the App\Mercure\Adapter\Mercure
instance can be used otherwise. And the publishGracefully
method can be used.
use Anik\Mercure\Adapter\Mercure;(new Mercure($publisherInterfaceInstance))->publishGracefully(
['array', 'of', 'topics'],
'event_name',
['array' => 'containing', 'our' => 'message']
);
The same __msg_*
rule applies here for the third parameter (payload) to pass the Mercure related configuration.
This method can send null
when the channels are empty. Can send bool
when there is anything wrong with submitting the update to the hub and string
when the request is successful.
Here is the package repository. Don’t forget to appreciate by pressing the star button in the project. Feel free to inform the bugs, PRs if you know where to change.
Happy coding. ❤