Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHP, Mercure et IoT Quand PHP devient plus que ...

PHP, Mercure et IoT Quand PHP devient plus que Full-Stack

Conférence donnée le 17 septembre 2026 à l'API Platform Conférence à Lille

Avatar for Yohan Giarelli

Yohan Giarelli

September 21, 2026

More Decks by Yohan Giarelli

Other Decks in Programming

Transcript

  1. SEPTEMBER 17 -18, 2026 - LILLE, FRANCE & ONLINE PHP,

    Mercure et IoT Quand PHP devient plus que Full-Stack
  2. Yohan Giarelli ✔ Développeur expert @ Les-Tilleuls.coop ✔ PHP •

    yohang/CalendR • yohang/Finite • coopTilleuls/sorter ✔ ✔ ✔ Symfony JS, React, Go Sailing fan FOLLOW ME! @rouKs github.com/yohang linkedin.com/in/yohangiarelli
  3. 01 Le besoin 02 Pourquoi PHP ? 03 L’architecture 04

    Les détails 05 Aller plus loin ?
  4. Le besoin Projet client de casiers en libre-service, avec les

    contraintes suivantes : Énergie limitée Données limitées Pas de raccordement électrique Connectivité 4G IoT uniquement Pas d’IHM embarquée Réduction des coûts / énergie Prototypable Tester, valider, itérer
  5. Le besoin Plusieurs solutions matérielles sont possibles pour répondre à

    ce besoin : Serrure “connectée” à ouverture NFC Pros ✔ Alimenté par la clé (Téléphone ou clé physique) ✔ Pas de connexion Internet ✔ Pas de coût “Au point de présence” Cons ✘ Coût “à la porte” important ✘ Lourdeur au développement (SDK NFC propriétaire) ✘ Pas de feedback
  6. Le besoin Plusieurs solutions matérielles sont possibles pour répondre à

    ce besoin : Carte électronique et microcontrôleur Pros Cons ✔ Basse consommation ✘ Coûts de développement importants ✔ Latences très faibles ✘ Compétences requises ✔ Pas cher à produire ✘ Mises à jour complexes
  7. Le besoin Plusieurs solutions matérielles sont possibles pour répondre à

    ce besoin : Nano-Ordinateur et actionneurs “simples” ✔ Pros Cons LGTM ✔ Prototype en quelques jours ✘ Besoin d’énergie (batteries + PV) ✔ Pas de contraintes fonctionnelles ✘ Connexion Internet directe ✔ Pas de vendor-lock in ✘ Coût “Au point de présence” supérieur ✔ Faible coût “à la porte” ✔ Télémétrie / Remontée d’infos
  8. 01 Le besoin 02 Pourquoi PHP ? 03 L’architecture 04

    Les détails 05 Aller plus loin ?
  9. Pourquoi PHP ? Nano-Ordinateur / Raspberry Pi ? ✔ Gâche

    électrique pilotée par un relais ✔ Utilisation d’un Relay Hat pour Raspberry Pi ✔ Contrôle des relais via les GPIOs • General Purpose Input/Output
  10. Pourquoi PHP ? PHP sur Raspberry Pi ? ✔ ✔

    Besoin d'interagir avec les GPIOs • libgpiod -> C/C++ • gpiozero -> Python • node-libgpiod -> NodeJS Rien (à jour) en PHP • Mais assez simple à contourner avec FFI.
  11. Pourquoi PHP ? Asynchrone en PHP ✔ Attendre la consigne

    de l’application ✔ PHP n’est historiquement pas bon pour ça mais : ✔ • stream_select() • curl_multi_select() • Fibers • ReactPHP, Revolt, etc. Aujourd’hui géré par nos abstractions habituelles (symfony/http-client)
  12. Pourquoi PHP ? Avantages de PHP ? ✔ C’est notre

    langage, on le connaît ! ✔ Simplification de la stack ✔ Équipe plus simple à staffer ✔ Écosystème intéressant (on va y venir !)
  13. 01 Le besoin 02 Pourquoi PHP ? 03 L’architecture 04

    Les détails 05 Aller plus loin ?
  14. L’architecture Côté “Casiers” ✔ PHP CLI sur un Raspberry avec

    Pi OS ✔ symfony/console ✔ FFI et libgpiod pour ouvrir les portes ➜ Comment commander l’ouverture ?
  15. L’architecture Communication avec le casier API REST ? AMQP ?

    MQTT ? Mercure Pas possible, aucune garantie d’avoir une route vers le raspberry Routage complexe (Topic Exchange) Parfait pour l’IoT, mais sécurité complexe Simple de mise en œuvre, sécurité adaptée
  16. 01 Le besoin 02 Pourquoi PHP ? 03 L’architecture 04

    Les détails 05 Aller plus loin ?
  17. Recevoir l’update Mercure $client = new EventSourceHttpClient(HttpClient::create()); $source = $client->connect(HUB_URL

    . '?match=*'); while (true) { foreach ($client->stream($source) as $chunk) { if ($chunk->isTimeout()) { continue; } if ($chunk->isLast()) { return; } if ($chunk instanceof ServerSentEvent) { unlockLocker($chunk); } } }
  18. Envoyer l’update Mercure use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\UrlGeneratorInterface; #[ApiResource( operations: [new

    GetCollection(), new Get()], normalizationContext: ['groups' => [self::GROUP_READ]], mercure: [ 'topics' => ['@=iri(object, ' . UrlGeneratorInterface::ABS_PATH . ')'], 'private' => true, ], )] final class Locker { #[Column(type: Types::STRING, enumType: LockerState::class)] #[Groups([self::GROUP_READ])] public LockerState $state = LockerState::CLOSED; }
  19. Changer l’état #[ApiResource( uriTemplate: '/lockers/{code}/open', uriVariables: ['code' => new Link(fromClass:

    Locker::class, identifiers: ['code'])], operations: [ new Post( input: false, output: Locker::class, normalizationContext: ['groups' => [Locker::GROUP_READ]], provider: LockerCommandProvider::class, processor: LockerCommandProcessor::class, ), ], )] final class OpenLocker extends LockerCommand { public static function transition(): string { return LockerState::TRANSITION_OPEN; } }
  20. Changer l’état final readonly class LockerCommandProcessor implements ProcessorInterface { public

    function __construct( private StateMachine $stateMachine, private LockerRepository $lockerRepository, ) {} public function process( mixed $data, Operation $operation, array $uriVariables = [], array $context = [], ): Locker { $locker = $data->locker; $transition = $data::transition(); $this->stateMachine->apply($locker, $transition); $this->lockerRepository->update(); return $locker; } }
  21. Sécurité : le jeton Mercure 1.0 eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjIxMDUxODMxMjUuMTI4MTgzL CJpc3MiOiJodHRwczovL2xvY2FsaG9zdCIsInN1YiI6InVubG9ja2V5LWJhY2tlbmQiLCJjbGl lbnRfaWQiOiJ1bmxvY2tleS1iYWNrZW5kIiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzL y53ZWxsLWtub3duL21lcmN1cmUiLCJhdXRob3JpemF0aW9uX2RldGFpbHMiOlt7InR5cGUiOiJ

    odHRwczovL21lcmN1cmUucm9ja3MvYXV0aG9yaXphdGlvbi1kZXRhaWwiLCJhY3Rpb25zIjpbI nB1Ymxpc2giLCJzdWJzY3JpYmUiXSwidG9waWNzIjpbeyJtYXRjaCI6Ii9hcGkvbG9ja2Vycy9 jaGlwcy06bnVtYmVyKFxcZCspIiwibWF0Y2hfdHlwZSI6InVybHBhdHRlcm4ifV19XSwiaWF0I joxNzg5NTYzOTI1LjEzMzA4NiwianRpIjoiNTdlM2VkODZhZjdkYjlkOWI1MDlhMGRmNjEzZjE 4ZmIifQ.RLph6RdgbvhQdzjgAzQaIdmaVwO6fm_LjSRRMNhDVXc ▪ Header (typ: at+jwt, alg: HS256) ▪ Payload ▪ Signature
  22. Sécurité : le payload du jeton { "authorization_details": [ {

    "type": "https://mercure.rocks/authorization-detail", "actions": ["publish", "subscribe"], "topics": [ { "match": "/api/lockers/chips-:number(\\d+)", "match_type": "urlpattern" } ] } ] }
  23. Merci de votre attention ! Des questions ? FOLLOW ME!

    @rouKs github.com/yohang github.com/yohang/unlockey