La façon standard d'utiliser MQTT en PHP est la bibliothèque php-mqtt/client : installez-la avec Composer, créez un MqttClient, appelez connect(), puis publish() depuis n'importe où et subscribe() avec une closure dans une boucle CLI de longue durée. Elle parle MQTT 3.1 et 3.1.1 sur TCP ou TLS, ne requiert aucune extension PHP au-delà de celles par défaut et dispose d'un wrapper Laravel officiel.
Ce tutoriel couvre la publication (y compris le détail du QoS 1 que la plupart des gens manquent), un subscriber robuste, TLS avec identifiants, le Last Will et la façon de tester chaque élément.
Installation
php-mqtt/client nécessite PHP 8 et Composer :
composer require php-mqtt/clientPHP dialogue avec un broker de deux manières très différentes, et les confondre est à l'origine de la plupart des problèmes :
| Rôle | Où il s'exécute | Durée de vie | Exemple |
|---|---|---|---|
| Publisher | Requête web (PHP-FPM, Apache), cron, job de file | Connexion, publication, déconnexion en quelques millisecondes | Envoyer une commande quand un utilisateur clique sur un bouton |
| Subscriber | Processus CLI (php subscriber.php) | Heures ou jours, supervisé | Enregistrer des mesures de capteurs en base de données |
Les workers PHP-FPM sont recyclés et les requêtes expirent : n'essayez donc jamais de garder un abonnement ouvert dans une requête web.
Publier un message
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
$clientId = 'testmqtt-php-' . bin2hex(random_bytes(4));
$mqtt = new MqttClient('broker.connectmq.com', 1883, $clientId, MqttClient::MQTT_3_1_1);
$settings = (new ConnectionSettings)
->setConnectTimeout(5)
->setKeepAliveInterval(30);
$mqtt->connect($settings, true); // true = clean session
$payload = json_encode(['temp' => 21.5, 'ts' => time()]);
$mqtt->publish('testmqtt/php/sensors/room1', $payload, MqttClient::QOS_AT_LEAST_ONCE);
$mqtt->loop(true, true); // wait for the PUBACK, then return
$mqtt->disconnect();
echo "published\n";Deux détails comptent :
- Le client ID aléatoire. Chaque requête est une connexion distincte. Si deux requêtes simultanées utilisaient le même ID fixe, le broker déconnecterait la première — voir client ID en double. Le générateur de client ID montre de bons formats.
loop(true, true)après une publication en QoS 1 ou 2.publish()ne fait que mettre le message en file ; l'acquittement est traité dans la boucle. Le second argument indique àloop()de rendre la main dès que plus rien n'est en attente. En QoS 0, vous pouvez vous déconnecter tout de suite. Les niveaux de QoS MQTT expliquent le compromis.
Ouvrir une connexion par requête convient pour des événements ponctuels. Si une page publie à chaque affichage, ou si vous envoyez des rafales de messages, confiez le travail à un worker de file qui garde une connexion ouverte — le modèle présenté dans MQTT avec Laravel.
Messages retenus pour l'état
Le quatrième argument de publish() est le flag retain. Un message retenu est stocké par le broker et livré immédiatement à chaque nouveau subscriber, ce qui le rend idéal pour les topics « état courant », comme la configuration d'un appareil modifiée depuis un panneau d'administration PHP :
$config = ['interval' => 60, 'unit' => 'C'];
$mqtt->publish('testmqtt/php/devices/42/config', json_encode($config), MqttClient::QOS_AT_LEAST_ONCE, true);
// An empty retained payload deletes the stored message
$mqtt->publish('testmqtt/php/devices/42/config', '', MqttClient::QOS_AT_LEAST_ONCE, true);
$mqtt->loop(true, true);Utilisez retain pour des états, pas pour des événements : un message retenu « bouton pressé » serait rejoué à chaque client qui s'abonne plus tard. Gardez des payloads en JSON compact avec un horodatage, pour que les consommateurs sachent de quand date l'état.
S'abonner dans un worker CLI
Un subscriber enregistre une closure par filtre de topic, puis appelle loop(true), qui bloque et distribue les messages jusqu'à ce que vous l'interrompiez :
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
$mqtt = new MqttClient('broker.connectmq.com', 1883, 'testmqtt-php-worker', MqttClient::MQTT_3_1_1);
$mqtt->connect((new ConnectionSettings)->setKeepAliveInterval(30), true);
$mqtt->subscribe('testmqtt/php/sensors/+', function (string $topic, string $message, bool $retained) {
$data = json_decode($message, true);
if (!is_array($data)) {
fwrite(STDERR, "invalid JSON on $topic\n");
return;
}
printf("%s%s temp=%s\n", $topic, $retained ? ' (retained)' : '', $data['temp'] ?? '?');
}, MqttClient::QOS_AT_LEAST_ONCE);
// Stop cleanly on Ctrl+C or a SIGTERM from systemd / Docker
pcntl_async_signals(true);
pcntl_signal(SIGINT, fn () => $mqtt->interrupt());
pcntl_signal(SIGTERM, fn () => $mqtt->interrupt());
$mqtt->loop(true);
$mqtt->disconnect();Le joker + correspond à un seul niveau de topic : ce filtre reçoit donc testmqtt/php/sensors/room1 mais pas testmqtt/php/sensors/room1/battery. Vérifiez vos filtres avec le topic matcher avant de déployer.
Lancez php subscriber.php dans un terminal et php publish.php dans un autre. La mesure devrait s'afficher en quelques millisecondes.
Survivre aux déconnexions
Un worker qui tourne pendant des semaines finira par perdre sa connexion : redémarrages du broker, timeouts NAT, déploiements. La bibliothèque lève alors une exception depuis loop(). Le modèle robuste le plus simple est une boucle externe qui se reconnecte avec back-off et se réabonne :
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\Exceptions\MqttClientException;
use PhpMqtt\Client\MqttClient;
$settings = (new ConnectionSettings)
->setKeepAliveInterval(30)
->setLastWillTopic('testmqtt/php/worker/status')
->setLastWillMessage('offline')
->setLastWillQualityOfService(MqttClient::QOS_AT_LEAST_ONCE)
->setRetainLastWill(true);
$delay = 1;
while (true) {
try {
$mqtt = new MqttClient('broker.connectmq.com', 1883, 'testmqtt-php-worker', MqttClient::MQTT_3_1_1);
$mqtt->connect($settings, true);
$mqtt->publish('testmqtt/php/worker/status', 'online', MqttClient::QOS_AT_LEAST_ONCE, true);
$mqtt->subscribe('testmqtt/php/sensors/+', function (string $topic, string $message) {
echo "$topic $message\n";
}, MqttClient::QOS_AT_LEAST_ONCE);
$delay = 1; // connected: reset back-off
$mqtt->loop(true);
} catch (MqttClientException $e) {
fwrite(STDERR, 'MQTT error: ' . $e->getMessage() . "\n");
sleep($delay);
$delay = min($delay * 2, 30);
}
}Le Last Will publie offline (retenu) si le worker meurt sans déconnexion propre, et le worker l'écrase avec online après chaque connexion — un indicateur de santé peu coûteux auquel d'autres services peuvent s'abonner. Le Last Will MQTT détaille le mécanisme.
La session propre est ici volontaire : le worker se réabonne à chaque connexion et ne dépend donc pas de ce que le broker conserve. Si vous avez besoin que les messages soient mis en file pendant que le worker est hors ligne, passez à connect($settings, false) avec un client ID fixe et testez-le contre votre propre broker privé — l'endpoint partagé broker.connectmq.com ne garde aucune session hors ligne. Voir les sessions persistantes.
php-mqtt/client propose aussi setReconnectAutomatically(true) avec setMaxReconnectAttempts() et setDelayBetweenReconnectAttempts() (en millisecondes), pratique pour les publishers qui gardent une connexion ouverte entre deux jobs.
TLS et authentification
Les brokers de production exigent TLS sur le port 8883 et des identifiants. Seuls le port et les paramètres changent :
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
$settings = (new ConnectionSettings)
->setUsername(getenv('MQTT_USER'))
->setPassword(getenv('MQTT_PASS'))
->setUseTls(true)
->setTlsVerifyPeer(true)
->setTlsVerifyPeerName(true)
->setTlsSelfSignedAllowed(false);
// ->setTlsCertificateAuthorityFile('/path/to/ca.pem') for a private CA
$mqtt = new MqttClient('broker.connectmq.com', 8883, 'testmqtt-php-' . bin2hex(random_bytes(4)), MqttClient::MQTT_3_1_1);
$mqtt->connect($settings, true);
$mqtt->publish('testmqtt/php/secure', 'hello over TLS', MqttClient::QOS_AT_LEAST_ONCE);
$mqtt->loop(true, true);
$mqtt->disconnect();Sans fichier de CA explicite, l'OpenSSL de PHP utilise le magasin de confiance du système (ou openssl.cafile dans php.ini). Laissez la vérification du pair activée ; la désactiver masque de vrais problèmes de certificat.
L'utiliser dans Laravel
Les mêmes auteurs maintiennent php-mqtt/laravel-client, qui lit les paramètres de connexion dans config/mqtt-client.php et .env et expose une facade :
composer require php-mqtt/laravel-client
php artisan vendor:publish --provider="PhpMqtt\Client\MqttClientServiceProvider" --tag="config"use PhpMqtt\Client\Facades\MQTT;
MQTT::publish('testmqtt/php/laravel/hello', 'Hello from Laravel');Pour vous abonner, appelez MQTT::connection() dans une commande Artisan et lancez-y loop(true). Le modèle complet — jobs en file pour publier, commande supervisée pour s'abonner, et tests — se trouve dans MQTT avec Laravel.
Erreurs courantes
| Symptôme | Cause | Correctif |
|---|---|---|
ConnectingToBrokerFailedException avec « Establishing a connection to the MQTT broker failed » | Hôte ou port erroné, pare-feu, TLS sur un port en clair (ou l'inverse) | Faites correspondre le port et setUseTls() : 1883 en clair, 8883 en TLS |
| Connexion refusée après le CONNECT | Identifiants erronés ou non autorisé | Vérifiez nom d'utilisateur, mot de passe et ACL |
| Le worker se déconnecte toutes les quelques secondes | Deux processus partagent un client ID | IDs uniques par processus, IDs aléatoires par requête |
| Message QoS 1 manquant | Le script s'est déconnecté avant le PUBACK | loop(true, true) avant disconnect() |
Call to undefined function pcntl_async_signals() | pcntl indisponible (Windows ou SAPI web) | Gestion des signaux uniquement en CLI sous Unix |
Tester
Observez ce que publie votre code PHP avec un subscriber en terminal ou avec le client MQTT en ligne :
mosquitto_sub -h broker.connectmq.com -p 1883 -t 'testmqtt/php/#' -vEnvoyez ensuite un message à votre subscriber, y compris un message invalide pour prouver que la vérification JSON fonctionne :
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/php/sensors/room1' -m '{"temp":22.1}' -q 1
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/php/sensors/room1' -m 'not json' -q 1Enfin, tuez le worker avec kill -9 et vérifiez que le statut retenu offline apparaît sur testmqtt/php/worker/status. D'autres recettes en ligne de commande vous attendent dans exemples mosquitto_pub et mosquitto_sub.
Observer les topics PHP dans le client en ligne
php-mqtt/client rend MQTT en PHP simple dès lors que vous respectez le modèle de processus de PHP : publiez depuis les requêtes avec des client IDs uniques et un loop(true, true) pour le QoS 1, abonnez-vous uniquement depuis des workers CLI supervisés qui se reconnectent seuls, et activez TLS et identifiants avant la mise en production.
Questions fréquentes
PHP peut-il s'abonner à des topics MQTT ?
Oui, mais pas dans une requête web classique. Un subscriber doit rester connecté : exécutez-le comme un script CLI de longue durée (php subscriber.php) qui appelle $mqtt->loop(true), supervisé par systemd, Supervisor ou Docker. Les requêtes web ne devraient que publier.
php-mqtt/client prend-il en charge MQTT 5 ?
Au moment de la rédaction, il implémente MQTT 3.1 et 3.1.1, que tout broker courant accepte. Si vous avez besoin de fonctionnalités MQTT 5 comme les reason codes ou les user properties, traitez cette partie du système dans un autre langage ou vérifiez si le projet propose des versions plus récentes.
Pourquoi mon message QoS 1 envoyé depuis PHP n'arrive-t-il jamais ?
publish() ne fait que mettre en file les messages QoS 1 et 2 ; l'acquittement est traité dans loop(). Appelez $mqtt->loop(true, true) après la publication pour que le client attende le PUBACK avant de se déconnecter, sinon le script peut se terminer avant.

