Un client MQTT PHP permet à votre application web existante d'envoyer des commandes à des appareils et de réagir à leurs données sans ajouter un nouveau langage à votre stack. Dans ce tutoriel, vous allez utiliser le package php-mqtt/client pour publier depuis une requête web, exécuter un worker subscriber durable, sécuriser les deux avec TLS et maintenir le worker en vie en production.
Deux rôles très différents pour un client MQTT PHP
Le modèle de requête de PHP détermine la façon d'utiliser MQTT ; distinguez donc les deux cas dès le départ :
| Rôle | Durée de vie | Modèle |
|---|---|---|
| Publier une commande quand un utilisateur clique sur un bouton | Une requête HTTP | Connexion, publication, déconnexion |
| Réagir à chaque message envoyé par les appareils | Permanente | Worker CLI séparé avec une boucle bloquante |
Essayer de s'abonner à l'intérieur d'une requête web est l'erreur la plus courante. Les workers PHP-FPM sont faits pour se terminer en quelques millisecondes ; un abonnement nécessite un processus qui reste connecté pendant des jours. Gardez les subscribers dans un processus CLI supervisé par systemd, Supervisor ou votre runtime de conteneurs, et limitez les requêtes web à de courtes publications.
Installer php-mqtt/client
php-mqtt/client est une implémentation PHP pure de MQTT 3.1 et 3.1.1 avec prise en charge de TLS, maintenue sur GitHub et installée via Composer. Elle ne nécessite aucune extension PHP au-delà de la prise en charge standard des sockets et d'OpenSSL incluse dans la plupart des builds.
composer require php-mqtt/client
Le README du package constitue la documentation officielle de php-mqtt/client et liste tous les paramètres de connexion. Pour une comparaison avec les clients d'autres langages, consultez le guide des bibliothèques clientes MQTT.
Paramètres de connexion partagés
Regroupez les détails de connexion en un seul endroit. L'exemple cible le serveur MQTT public gratuit, réservé aux tests, à l'adresse broker.connectmq.com en TLS sur le port 8883, et bascule automatiquement vers des identifiants privés lorsque les variables d'environnement sont définies.
mqtt.php :
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
const MQTT_HOST = 'broker.connectmq.com';
const MQTT_PORT = 8883;
function mqtt_topic_prefix(): string
{
return getenv('MQTT_TOPIC_PREFIX') ?: 'connectmq/demo/php-5e19c2';
}
function mqtt_client(string $role): MqttClient
{
$clientId = getenv('MQTT_CLIENT_ID') ?: sprintf('php-%s-%s', $role, bin2hex(random_bytes(4)));
return new MqttClient(MQTT_HOST, MQTT_PORT, $clientId, MqttClient::MQTT_3_1_1);
}
function mqtt_settings(bool $reconnect = false): ConnectionSettings
{
$settings = (new ConnectionSettings())
->setUseTls(true)
->setTlsVerifyPeer(true)
->setTlsVerifyPeerName(true)
->setConnectTimeout(10)
->setSocketTimeout(5)
->setKeepAliveInterval(30);
if ($user = getenv('MQTT_USERNAME')) {
$settings = $settings
->setUsername($user)
->setPassword(getenv('MQTT_PASSWORD') ?: null);
}
if ($reconnect) {
$settings = $settings
->setReconnectAutomatically(true)
->setMaxReconnectAttempts(10)
->setDelayBetweenReconnectAttempts(3000);
}
return $settings;
}
ConnectionSettings est immuable : chaque setter renvoie une nouvelle instance, c'est pourquoi le code réaffecte $settings. La vérification TLS utilise le magasin de CA du système, qui fait déjà confiance au certificat Let's Encrypt servi par ConnectMQ.
Publier depuis une requête web
Ce endpoint reçoit un formulaire POST et publie une commande vers un appareil. Avec le QoS 1, le broker doit acquitter le message, et php-mqtt/client ne traite cet acquittement que dans sa boucle ; le code exécute donc la boucle jusqu'à ce que la file sortante soit vide avant de se déconnecter.
public/command.php :
<?php
require __DIR__ . '/../mqtt.php';
use PhpMqtt\Client\Exceptions\MqttClientException;
use PhpMqtt\Client\MqttClient;
$device = preg_replace('/[^a-z0-9_-]/i', '', $_POST['device'] ?? '');
$action = in_array($_POST['action'] ?? '', ['on', 'off'], true) ? $_POST['action'] : null;
if ($device === '' || $action === null) {
http_response_code(400);
exit('Invalid device or action');
}
$topic = mqtt_topic_prefix() . "/devices/{$device}/set";
$payload = json_encode(['action' => $action, 'ts' => time()]);
try {
$mqtt = mqtt_client('web');
$mqtt->connect(mqtt_settings(), true);
$mqtt->publish($topic, $payload, MqttClient::QOS_AT_LEAST_ONCE);
$mqtt->loop(true, true); // attendre la réception du PUBACK
$mqtt->disconnect();
echo "Sent {$action} to {$device}";
} catch (MqttClientException $e) {
error_log('MQTT publish failed: ' . $e->getMessage());
http_response_code(502);
echo 'Device gateway unavailable';
}
Remarquez l'assainissement de l'entrée device. Les utilisateurs ne doivent jamais pouvoir injecter #, + ou / dans un nom de topic, sinon ils pourraient adresser des appareils qui ne leur appartiennent pas.
Une connexion TLS par requête ajoute de la latence, généralement un ou deux allers-retours de négociation. Pour un panneau d'administration qui envoie quelques commandes par minute, c'est acceptable. Pour une publication à fort volume depuis PHP, placez le travail dans une file (Redis, une table de base de données, les queues Laravel) et laissez un worker durable maintenir une connexion MQTT unique pour la vider.
Un subscriber MQTT PHP durable
Le subscriber est un script CLI. Il s'abonne avec un wildcard, décode le JSON et s'arrête proprement sur SIGTERM pour que systemd ou Docker puisse le redémarrer sans perdre un message en cours de traitement.
bin/subscriber.php :
<?php
require __DIR__ . '/../mqtt.php';
use PhpMqtt\Client\Exceptions\MqttClientException;
use PhpMqtt\Client\MqttClient;
pcntl_async_signals(true);
$mqtt = mqtt_client('worker');
$stop = function () use ($mqtt) {
fwrite(STDOUT, "Shutting down...\n");
$mqtt->interrupt();
};
pcntl_signal(SIGINT, $stop);
pcntl_signal(SIGTERM, $stop);
$filter = mqtt_topic_prefix() . '/devices/+/telemetry';
try {
$mqtt->connect(mqtt_settings(reconnect: true), true);
$mqtt->subscribe($filter, function (string $topic, string $message, bool $retained) {
$parts = explode('/', $topic);
$device = $parts[count($parts) - 2] ?? 'unknown';
$data = json_decode($message, true);
if (!is_array($data)) {
fwrite(STDERR, "Ignoring non-JSON payload on {$topic}\n");
return;
}
// Remplacez par votre propre persistance, par ex. un insert PDO ou un modèle du framework
printf("[%s] %s%s %s\n", date('c'), $device, $retained ? ' (retained)' : '', json_encode($data));
}, MqttClient::QOS_AT_LEAST_ONCE);
fwrite(STDOUT, "Subscribed to {$filter}\n");
$mqtt->loop(true);
$mqtt->disconnect();
} catch (MqttClientException $e) {
fwrite(STDERR, 'MQTT error: ' . $e->getMessage() . "\n");
exit(1);
}
Exécutez-le :
php bin/subscriber.php
Dans un autre terminal, jouez le rôle d'un appareil :
mosquitto_pub -h broker.connectmq.com -p 1883 -i php-sim-pump1 \
-t "connectmq/demo/php-5e19c2/devices/pump1/telemetry" \
-m '{"flow": 12.4, "pressure": 2.1}'
L'extension pcntl est disponible dans le PHP CLI sous Linux, mais généralement pas sous PHP-FPM ni sous Windows : une raison de plus de garder le subscriber dans un processus CLI.
Exécuter le worker en production
/etc/systemd/system/php-mqtt-worker.service :
[Unit]
Description=PHP MQTT subscriber
After=network-online.target
Wants=network-online.target
[Service]
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php bin/subscriber.php
EnvironmentFile=/etc/php-mqtt-worker.env
Restart=always
RestartSec=5
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target
/etc/php-mqtt-worker.env (mode 600) contient les identifiants du broker privé :
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
MQTT_TOPIC_PREFIX=acme/site1
Les processus PHP de longue durée ont leurs propres modes de défaillance. Passez en revue cette liste avant de vous reposer sur le worker :
- Mémoire. Libérez les grands tableaux avec unset, évitez d'accumuler des données dans des propriétés statiques et envisagez de quitter après quelques heures pour que systemd redémarre un processus propre.
- Connexions à la base de données. Les connexions PDO expirent pendant que le worker est inactif ; reconnectez-vous en cas d'échec au lieu de garder une connexion indéfiniment.
- Client IDs uniques. Le publisher web et le worker ont besoin de client IDs différents. Si deux processus en partagent un, le broker déconnecte le plus ancien, et ils s'éjectent mutuellement en boucle.
- Limites de reconnexion. Avec
setMaxReconnectAttempts(10), la boucle finit par lever une exception ; laissez-la se terminer avec un code non nul pour que le superviseur la redémarre. - Sessions persistantes. Si le worker ne doit manquer aucun message pendant les redémarrages, connectez-vous avec clean session désactivé et un client ID fixe, et abonnez-vous en QoS 1. Le guide des sessions persistantes explique le compromis.
Sur un broker ConnectMQ privé, les client IDs sont liés aux identifiants qui vous sont émis : prévoyez donc un jeu d'identifiants par processus connecté simultanément.
Choisir QoS, retain et last will depuis PHP
Pour les commandes envoyées depuis une application web, le QoS 1 est le choix par défaut raisonnable : le broker accuse réception, et l'appareil doit publier l'état résultant sur son propre topic pour que l'interface affiche ce qui s'est réellement passé. Évitez le flag retain sur les commandes. Un on retenu serait rejoué à l'appareil à chaque reconnexion, parfois des heures plus tard ; le retain sert à l'état, pas aux actions, comme l'explique le guide des retained messages MQTT.
Le worker subscriber peut annoncer son propre état de santé avec un last will. Ajoutez ->setLastWillTopic(mqtt_topic_prefix() . '/workers/php/status'), ->setLastWillMessage('offline'), ->setLastWillQualityOfService(1) et ->setRetainLastWill(true) aux paramètres de connexion du worker, puis publiez un online retenu juste après la connexion. Si le worker plante ou perd son réseau, le broker publie offline à sa place, et votre supervision peut alerter sur ce topic au lieu de deviner à partir du silence. Le plan ConnectMQ Serverless gratuit prend en charge les QoS 0 et 1 et des messages jusqu'à 256 Ko, ce qui couvre largement les payloads de commandes et de télémétrie d'une application PHP.
Notes pour Laravel et Symfony
Le même code s'intègre dans les frameworks. Dans Laravel, encapsulez le subscriber dans une commande Artisan (php artisan mqtt:listen) exécutée sous Supervisor, et publiez depuis un job en file plutôt que depuis le contrôleur. Dans Symfony, une commande console joue le même rôle. Le package communautaire php-mqtt/laravel-client ajoute une configuration et une façade au-dessus de la même bibliothèque si vous préférez ce style.
Dépannage
| Erreur | Cause probable |
|---|---|
ConnectingToBrokerFailedException avec une erreur TLS |
Mauvais port pour TLS (utilisez 8883), ou bundle de CA obsolète sur le serveur |
| Connexion refusée pour identifiants incorrects | MQTT_USERNAME ou MQTT_PASSWORD absent de l'environnement du worker |
| La publication retourne mais l'appareil ne reçoit rien | Le script s'est terminé avant que la boucle ne traite l'acquittement QoS 1 |
| Le worker se reconnecte toutes les quelques secondes | Un autre processus utilise le même client ID |
Call to undefined function pcntl_signal |
Exécution sous FPM ou sans l'extension pcntl |
Démarrer avec ConnectMQ
Offrez à votre application PHP un broker privé et sécurisé en quelques minutes. Créez un broker Serverless gratuit sur app.connectmq.com sans carte bancaire, placez les identifiants générés dans votre fichier d'environnement et utilisez le client intégré au navigateur du portail pour tester vos publications. Un broker MQTT managé signifie que TLS, montée en charge et mises à jour du broker ne pèsent plus sur votre équipe PHP. Pour d'autres langages back-end, consultez le tutoriel du client MQTT Ruby et le tutoriel Paho MQTT C.
Questions fréquentes
php-mqtt/client prend-il en charge MQTT 5 ?
Non. Il prend en charge MQTT 3.1 et 3.1.1, que ConnectMQ accepte aux côtés de MQTT 5.0. Pour la plupart des flux de commandes du web vers les appareils, 3.1.1 suffit.
Puis-je m'abonner à des topics MQTT dans une requête web PHP ?
Vous le pouvez, mais vous ne devriez pas. Les abonnements nécessitent un processus qui reste connecté : exécutez-les dans un worker CLI et limitez les requêtes web à de courtes publications.
Pourquoi ma publication QoS 1 depuis PHP n'arrive-t-elle pas toujours ?
Le script s'est probablement terminé avant que le client ne traite l'acquittement du broker. Appelez $mqtt->loop(true, true) après la publication pour que la file se vide avant disconnect().
Comment connecter PHP à MQTT en TLS ?
Activez setUseTls(true) dans les paramètres de connexion et connectez-vous au port 8883. La vérification du pair fonctionne immédiatement avec les brokers dotés de certificats reconnus publiquement, comme broker.connectmq.com.



