8000 symfony-docs/mercure.rst at 5.1 · symfony/symfony-docs · GitHub
[go: up one dir, main page]

Skip to content
{"payload":{"allShortcutsEnabled":false,"fileTree":{"":{"items":[{"name":".github","path":".github","contentType":"directory"},{"name":".symfony","path":".symfony","contentType":"directory"},{"name":"_build","path":"_build","contentType":"directory"},{"name":"_images","path":"_images","contentType":"directory"},{"name":"_includes","path":"_includes","contentType":"directory"},{"name":"bundles","path":"bundles","contentType":"directory"},{"name":"components","path":"components","contentType":"directory"},{"name":"configuration","path":"configuration","contentType":"directory"},{"name":"console","path":"console","contentType":"directory"},{"name":"contributing","path":"contributing","contentType":"directory"},{"name":"controller","path":"controller","contentType":"directory"},{"name":"create_framework","path":"create_framework","contentType":"directory"},{"name":"deployment","path":"deployment","contentType":"directory"},{"name":"doctrine","path":"doctrine","contentType":"directory"},{"name":"event_dispatcher","path":"event_dispatcher","contentType":"directory"},{"name":"form","path":"form","contentType":"directory"},{"name":"frontend","path":"frontend","contentType":"directory"},{"name":"getting_started","path":"getting_started","contentType":"directory"},{"name":"http_cache","path":"http_cache","contentType":"directory"},{"name":"introduction","path":"introduction","contentType":"directory"},{"name":"logging","path":"logging","contentType":"directory"},{"name":"messenger","path":"messenger","contentType":"directory"},{"name":"notifier","path":"notifier","contentType":"directory"},{"name":"profiler","path":"profiler","contentType":"directory"},{"name":"quick_tour","path":"quick_tour","contentType":"directory"},{"name":"reference","path":"reference","contentType":"directory"},{"name":"routing","path":"routing","contentType":"directory"},{"name":"security","path":"security","contentType":"directory"},{"name":"serializer","path":"serializer","contentType":"directory"},{"name":"service_container","path":"service_container","contentType":"directory"},{"name":"session","path":"session","contentType":"directory"},{"name":"setup","path":"setup","contentType":"directory"},{"name":"templating","path":"templating","contentType":"directory"},{"name":"testing","path":"testing","contentType":"directory"},{"name":"translation","path":"translation","contentType":"directory"},{"name":"validation","path":"validation","contentType":"directory"},{"name":"workflow","path":"workflow","contentType":"directory"},{"name":".alexrc","path":".alexrc","contentType":"file"},{"name":".doctor-rst.yaml","path":".doctor-rst.yaml","contentType":"file"},{"name":".editorconfig","path":".editorconfig","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":".symfony.cloud.yaml","path":".symfony.cloud.yaml","contentType":"file"},{"name":"CODE_OF_CONDUCT.md","path":"CODE_OF_CONDUCT.md","contentType":"file"},{"name":"Dockerfile","path":"Dockerfile","contentType":"file"},{"name":"LICENSE.md","path":"LICENSE.md","contentType":"file"},{"name":"README.markdown","path":"README.markdown","contentType":"file"},{"name":"best_practices.rst","path":"best_practices.rst","contentType":"file"},{"name":"bundles.rst","path":"bundles.rst","contentType":"file"},{"name":"cache.rst","path":"cache.rst","contentType":"file"},{"name":"configuration.rst","path":"configuration.rst","contentType":"file"},{"name":"console.rst","path":"console.rst","contentType":"file"},{"name":"controller.rst","path":"controller.rst","contentType":"file"},{"name":"deployment.rst","path":"deployment.rst","contentType":"file"},{"name":"docs.json","path":"docs.json","contentType":"file"},{"name":"doctrine.rst","path":"doctrine.rst","contentType":"file"},{"name":"email.rst","path":"email.rst","contentType":"file"},{"name":"event_dispatcher.rst","path":"event_dispatcher.rst","contentType":"file"},{"name":"forms.rst","path":"forms.rst","contentType":"file"},{"name":"frontend.rst","path":"frontend.rst","contentType":"file"},{"name":"http_cache.rst","path":"http_cache.rst","contentType":"file"},{"name":"http_client.rst","path":"http_client.rst","contentType":"file"},{"name":"index.rst","path":"index.rst","contentType":"file"},{"name":"lock.rst","path":"lock.rst","contentType":"file"},{"name":"logging.rst","path":"logging.rst","contentType":"file"},{"name":"mailer.rst","path":"mailer.rst","contentType":"file"},{"name":"mercure.rst","path":"mercure.rst","contentType":"file"},{"name":"messenger.rst","path":"messenger.rst","contentType":"file"},{"name":"migration.rst","path":"migration.rst","contentType":"file"},{"name":"notifier.rst","path":"notifier.rst","contentType":"file"},{"name":"page_creation.rst","path":"page_creation.rst","contentType":"file"},{"name":"performance.rst","path":"performance.rst","contentType":"file"},{"name":"profiler.rst","path":"profiler.rst","contentType":"file"},{"name":"routing.rst","path":"routing.rst","contentType":"file"},{"name":"security.rst","path":"security.rst","contentType":"file"},{"name":"serializer.rst","path":"serializer.rst","contentType":"file"},{"name":"service_container.rst","path":"service_container.rst","contentType":"file"},{"name":"session.rst","path":"session.rst","contentType":"file"},{"name":"setup.rst","path":"setup.rst","contentType":"file"},{"name":"templates.rst","path":"templates.rst","contentType":"file"},{"name":"testing.rst","path":"testing.rst","contentType":"file"},{"name":"translation.rst","path":"translation.rst","contentType":"file"},{"name":"validation.rst","path":"validation.rst","contentType":"file"},{"name":"web_link.rst","path":"web_link.rst","contentType":"file"},{"name":"workflow.rst","path":"workflow.rst","contentType":"file"}],"totalCount":84}},"fileTreeProcessingTime":4.704501,"foldersToFetch":[],"incompleteFileTree":false,"repo":{"id":521583,"defaultBranch":"7.3","name":"symfony-docs","ownerLogin":"symfony","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2010-02-17T08:43:51.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/143937?v=4","public":true,"private":false,"isOrgOwned":true},"codeLineWrapEnabled":false,"symbolsExpanded":false,"treeExpanded":true,"refInfo":{"name":"5.1","listCacheKey":"v0:1748857018.0","canEdit":false,"refType":"branch","currentOid":"15c1b9f752247326f59ad84e6dd3fd9c74c8a122"},"path":"mercure.rst","currentUser":null,"blob":{"rawLines":null,"stylingDirectives":null,"colorizedLines":null,"csv":null,"csvError":null,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/symfony/symfony-docs/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"mercure.rst","displayUrl":"https://github.com/symfony/symfony-docs/blob/5.1/mercure.rst?raw=true","headerInfo":{"blobSize":"21.5 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"ff45874","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Fsymfony%2Fsymfony-docs%2Fblob%2F5.1%2Fmercure.rst","isCSV":false,"isRichtext":true,"toc":[{"level":2,"text":"Pushing Data to Clients Using the Mercure Protocol","anchor":"pushing-data-to-clients-using-the-mercure-protocol","htmlText":"Pushing Data to Clients Using the Mercure Protocol"},{"level":3,"text":"Installation","anchor":"installation","htmlText":"Installation"},{"level":4,"text":"Installing the Symfony Component","anchor":"installing-the-symfony-component","htmlText":"Installing the Symfony Component"},{"level":4,"text":"Running a Mercure Hub","anchor":"running-a-mercure-hub","htmlText":"Running a Mercure Hub"},{"level":3,"text":"Configuration","anchor":"configuration","htmlText":"Configuration"},{"level":3,"text":"Basic Usage","anchor":"basic-usage","htmlText":"Basic Usage"},{"level":4,"text":"Publishing","anchor":"publishing","htmlText":"Publishing"},{"level":4,"text":"Subscribing","anchor":"subscribing","htmlText":"Subscribing"},{"level":3,"text":"Async dispatching","anchor":"async-dispatching","htmlText":"Async dispatching"},{"level":3,"text":"Discovery","anchor":"discovery","htmlText":"Discovery"},{"level":3,"text":"Authorization","anchor":"authorization","htmlText":"Authorization"},{"level":3,"text":"Programmatically Generating The JWT Used to Publish","anchor":"programmatically-generating-the-jwt-used-to-publish","htmlText":"Programmatically Generating The JWT Used to Publish"},{"level":3,"text":"Web APIs","anchor":"web-apis","htmlText":"Web APIs"},{"level":3,"text":"Testing","anchor":"testing","htmlText":"Testing"},{"level":3,"text":"Debugging","anchor":"debugging","htmlText":"Debugging"}],"lineInfo":{"truncatedLoc":"654","truncatedSloc":"478"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"issueTemplate":null,"discussionTemplate":null,"language":"reStructuredText","languageID":419,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/symfony/symfony-docs/blob/5.1/mercure.rst","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/symfony/symfony-docs/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/symfony/symfony-docs/raw/refs/heads/5.1/mercure.rst","renderImageOrRaw":false,"richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cpre\u003e.. index::\n single: Mercure\n\n\u003c/pre\u003e\n\u003ca name=\"user-content-pushing-data-to-clients-using-the-mercure-protocol\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003ePushing Data to Clients Using the Mercure Protocol\u003c/h2\u003e\u003ca id=\"user-content-pushing-data-to-clients-using-the-mercure-protocol\" class=\"anchor\" aria-label=\"Permalink: Pushing Data to Clients Using the Mercure Protocol\" href=\"#pushing-data-to-clients-using-the-mercure-protocol\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eBeing able to broadcast data in real-time from servers to clients is a\nrequirement for many modern web and mobile applications.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eCreating a UI reacting in live to changes made by other users\n(e.g. a user changes the data currently browsed by several other users,\nall UIs are instantly updated),\nnotifying the user when \u003ca href=\"#id1\"\u003e\u003cspan id=\"user-content-id2\"\u003e:doc:`an asynchronous job \u0026lt;/messenger\u0026gt;`\u003c/span\u003e\u003c/a\u003e has been\ncompleted or creating chat applications are among the typical use cases\nrequiring \"push\" capabilities.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eSymfony provides a straightforward component, built on top of\n\u003ca href=\"https://mercure.rocks/spec\" rel=\"nofollow\"\u003ethe Mercure protocol\u003c/a\u003e, specifically designed for this class of use cases.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eMercure is an open protocol designed from the ground to publish updates from\nserver to clients. It is a modern and efficient alternative to timer-based\npolling and to WebSocket.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eBecause it is built on top \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events\" rel=\"nofollow\"\u003eServer-Sent Events (SSE)\u003c/a\u003e, Mercure is supported\nout of the box in most modern browsers (Edge and IE require \u003ca href=\"https://github.com/Yaffle/EventSource\"\u003ea polyfill\u003c/a\u003e) and\nhas \u003ca href=\"https://mercure.rocks/docs/ecosystem/awesome\" rel=\"nofollow\"\u003ehigh-level implementations\u003c/a\u003e in many programming languages.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eMercure comes with an authorization mechanism,\nautomatic re-connection in case of network issues\nwith retrieving of lost updates, a presence API,\n\"connection-less\" push for smartphones and auto-discoverability (a supported\nclient can automatically discover and subscribe to updates of a given resource\nthanks to a specific HTTP header).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAll these features are supported in the Symfony integration.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eUnlike WebSocket, which is only compatible with HTTP 1.x,\nMercure leverages the multiplexing capabilities provided by HTTP/2\nand HTTP/3 (but also supports older versions of HTTP).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca href=\"https://www.youtube.com/watch?v=UI1l0JOjLeI\" rel=\"nofollow\"\u003eIn this recording\u003c/a\u003e you can see how a Symfony web API leverages Mercure\nand API Platform to update in live a React app and a mobile app (React Native)\ngenerated using the API Platform client generator.\u003c/p\u003e\n\u003ca name=\"user-content-installation\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eInstallation\u003c/h3\u003e\u003ca id=\"user-content-installation\" class=\"anchor\" aria-label=\"Permalink: Installation\" href=\"#installation\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003ca name=\"user-content-installing-the-symfony-component\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eInstalling the Symfony Component\u003c/h4\u003e\u003ca id=\"user-content-installing-the-symfony-component\" class=\"anchor\" aria-label=\"Permalink: Installing the Symfony Component\" href=\"#installing-the-symfony-component\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIn applications using \u003ca href=\"#id3\"\u003e\u003cspan id=\"user-content-id4\"\u003e:ref:`Symfony Flex \u0026lt;symfony-flex\u0026gt;`\u003c/span\u003e\u003c/a\u003e, run this command to\ninstall the Mercure support before using it:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ composer require mercure\n\u003c/pre\u003e\n\u003ca name=\"user-content-running-a-mercure-hub\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eRunning a Mercure Hub\u003c/h4\u003e\u003ca id=\"user-content-running-a-mercure-hub\" class=\"anchor\" aria-label=\"Permalink: Running a Mercure Hub\" href=\"#running-a-mercure-hub\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eTo manage persistent connections, Mercure relies on a Hub: a dedicated server\nthat handles persistent SSE connections with the clients.\nThe Symfony app publishes the updates to the hub, that will broadcast them to\nclients.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/symfony/symfony-docs/blob/5.1/_images/mercure/schema.png\"\u003e\u003cimg alt=\"/_images/mercure/schema.png\" src=\"/symfony/symfony-docs/raw/5.1/_images/mercure/schema.png\" style=\"max-width: 100%; height: auto;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAn official and open source (AGPL) implementation of a Hub can be downloaded\nas a static binary from \u003ca href=\"https://mercure.rocks\" rel=\"nofollow\"\u003eMercure.rocks\u003c/a\u003e.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIf you use \u003ca href=\"https://github.com/dunglas/symfony-docker/\"\u003eSymfony Docker\u003c/a\u003e,\na Mercure Hub is already included and you can skip straight to the next section.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eOn Linux and Mac, run the following command to start it:\u003c/p\u003e\n\u003cpre\u003e.. rst-class:: command-linux\n\n $ SERVER_NAME=:3000 MERCURE_PUBLISHER_JWT_KEY=\"!ChangeMe!\" MERCURE_SUBSCRIBER_JWT_KEY=\"!ChangeMe!\" ./mercure run -config Caddyfile.dev\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eOn Windows run:\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAlternatively to the binary, a Docker image, a Helm chart for Kubernetes\nand a managed, High Availability Hub are also provided by Mercure.rocks.\u003c/p\u003e\n\u003c/div\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ca href=\"https://api-platform.com/docs/distribution/\" rel=\"nofollow\"\u003eAPI Platform distribution\u003c/a\u003e comes with a Docker Compose configuration\nas well as a Helm chart for Kubernetes that are 100% compatible with Symfony,\nand contain a Mercure hub.\nYou can copy them in your project, even if you don't use API Platform.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-configuration\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eConfiguration\u003c/h3\u003e\u003ca id=\"user-content-configuration\" class=\"anchor\" aria-label=\"Permalink: Configuration\" href=\"#configuration\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe preferred way to configure the MercureBundle is using\n\u003ca href=\"#id5\"\u003e\u003cspan id=\"user-content-id6\"\u003e:doc:`environment variables \u0026lt;/configuration\u0026gt;`\u003c/span\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eSet the URL of your hub as the value of the \u003ccode\u003eMERCURE_PUBLISH_URL\u003c/code\u003e env var.\nThe \u003ccode\u003e.env\u003c/code\u003e file of your project has been updated by the Flex recipe to\nprovide example values.\nSet it to the URL of the Mercure Hub (\u003ccode\u003ehttp://localhost:3000/.well-known/mercure\u003c/code\u003e by default).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIn addition, the Symfony application must bear a \u003ca href=\"https://tools.ietf.org/html/rfc7519\" rel=\"nofollow\"\u003eJSON Web Token\u003c/a\u003e (JWT)\nto the Mercure Hub to be authorized to publish updates.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThis JWT should be stored in the \u003ccode\u003eMERCURE_JWT_TOKEN\u003c/code\u003e environment variable.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe JWT must be signed with the same secret key as the one used by\nthe Hub to verify the JWT (\u003ccode\u003e!ChangeMe!\u003c/code\u003e in our example).\nIts payload must contain at least the following structure to be allowed to\npublish:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-json notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"{\n \u0026quot;mercure\u0026quot;: {\n \u0026quot;publish\u0026quot;: []\n }\n}\"\u003e\u003cpre\u003e{\n \u003cspan class=\"pl-ent\"\u003e\"mercure\"\u003c/span\u003e: {\n \u003cspan class=\"pl-ent\"\u003e\"publish\"\u003c/span\u003e: []\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eBecause the array is empty, the Symfony app will only be authorized to publish\npublic updates (see the \u003ca href=\"#authorization\"\u003eauthorization\u003c/a\u003e section for further information).\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe jwt.io website is a convenient way to create and sign JWTs.\nCheckout this \u003ca href=\"https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.iHLdpAEjX4BqCsHJEegxRmO-Y6sMxXwNATrQyRNt3GY\" rel=\"nofollow\"\u003eexample JWT\u003c/a\u003e, that grants publishing rights for all \u003cem\u003etopics\u003c/em\u003e\n(notice the star in the array).\nDon't forget to set your secret key properly in the bottom of the right panel of the form!\u003c/p\u003e\n\u003c/div\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eCaution!\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eDon't put the secret key in \u003ccode\u003eMERCURE_JWT_TOKEN\u003c/code\u003e, it will not work!\nThis environment variable must contain a JWT, signed with the secret key.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAlso, be sure to keep both the secret key and the JWTs... secrets!\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-basic-usage\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eBasic Usage\u003c/h3\u003e\u003ca id=\"user-content-basic-usage\" class=\"anchor\" aria-label=\"Permalink: Basic Usage\" href=\"#basic-usage\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003ca name=\"user-content-publishing\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003ePublishing\u003c/h4\u003e\u003ca id=\"user-content-publishing\" class=\"anchor\" aria-label=\"Permalink: Publishing\" href=\"#publishing\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe Mercure Component provides an \u003ccode\u003eUpdate\u003c/code\u003e value object representing\nthe update to publish. It also provides a \u003ccode\u003ePublisher\u003c/code\u003e service to dispatch\nupdates to the Hub.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003ePublisher\u003c/code\u003e service can be injected using the\n\u003ca href=\"#id7\"\u003e\u003cspan id=\"user-content-id8\"\u003e:doc:`autowiring \u0026lt;/service_container/autowiring\u0026gt;`\u003c/span\u003e\u003c/a\u003e in any other\nservice, including controllers:\u003c/p\u003e\n\u003cpre\u003e// src/Controller/PublishController.php\nnamespace App\\Controller;\n\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Mercure\\PublisherInterface;\nuse Symfony\\Component\\Mercure\\Update;\n\nclass PublishController\n{\n public function __invoke(PublisherInterface $publisher): Response\n {\n $update = new Update(\n 'http://example.com/books/1',\n json_encode(['status' =\u0026gt; 'OutOfStock'])\n );\n\n // The Publisher service is an invokable object\n $publisher($update);\n\n return new Response('published!');\n }\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThe first parameter to pass to the \u003ccode\u003eUpdate\u003c/code\u003e constructor is\nthe \u003cstrong\u003etopic\u003c/strong\u003e being updated. This topic should be an \u003ca href=\"https://tools.ietf.org/html/rfc3987\" rel=\"nofollow\"\u003eIRI\u003c/a\u003e\n(Internationalized Resource Identifier, RFC 3987): a unique identifier\nof the resource being dispatched.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eUsually, this parameter contains the original URL of the resource\ntransmitted to the client, but it can be any string or \u003ca href=\"https://tools.ietf.org/html/rfc3987\" rel=\"nofollow\"\u003eIRI\u003c/a\u003e,\nand it doesn't have to be a URL that exists (similarly to XML namespaces).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe second parameter of the constructor is the content of the update.\nIt can be anything, stored in any format.\nHowever, serializing the resource in a hypermedia format such as JSON-LD,\nAtom, HTML or XML is recommended.\u003c/p\u003e\n\u003ca name=\"user-content-subscribing\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSubscribing\u003c/h4\u003e\u003ca id=\"user-content-subscribing\" class=\"anchor\" aria-label=\"Permalink: Subscribing\" href=\"#subscribing\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSubscribing to updates in JavaScript is straightforward:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"const eventSource = new EventSource('http://localhost:3000/.well-known/mercure?topic=' + encodeURIComponent('http://example.com/books/1'));\neventSource.onmessage = event =\u0026gt; {\n // Will be called every time an update is published by the server\n console.log(JSON.parse(event.data));\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003econst\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eeventSource\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eEventSource\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'http://localhost:3000/.well-known/mercure?topic='\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e+\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eencodeURIComponent\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'http://example.com/books/1'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\u003cspan class=\"pl-s1\"\u003eeventSource\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eonmessage\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eevent\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"pl-kos\"\u003e{\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e// Will be called every time an update is published by the server\u003c/span\u003e\n \u003cspan class=\"pl-smi\"\u003econsole\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003elog\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eJSON\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eparse\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003eevent\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003edata\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\u003cspan class=\"pl-kos\"\u003e}\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eMercure also allows to subscribe to several topics,\nand to use URI Templates or the special value \u003ccode\u003e*\u003c/code\u003e (matched by all topics)\nas patterns:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"// URL is a built-in JavaScript class to manipulate URLs\nconst url = new URL('http://localhost:3000/.well-known/mercure');\nurl.se 8000 archParams.append('topic', 'http://example.com/books/1');\n// Subscribe to updates of several Book resources\nurl.searchParams.append('topic', 'http://example.com/books/2');\n// All Review resources will match this pattern\nurl.searchParams.append('topic', 'http://example.com/reviews/{id}');\n\nconst eventSource = new EventSource(url);\neventSource.onmessage = event =\u0026gt; {\n console.log(JSON.parse(event.data));\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c\"\u003e// URL is a built-in JavaScript class to manipulate URLs\u003c/span\u003e\n\u003cspan class=\"pl-k\"\u003econst\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eurl\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003eURL\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'http://localhost:3000/.well-known/mercure'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\u003cspan class=\"pl-s1\"\u003eurl\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003esearchParams\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eappend\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'topic'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e,\u003c/span\u003e \u003cspan class=\"pl-s\"\u003e'http://example.com/books/1'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e// Subscribe to updates of several Book resources\u003c/span\u003e\n\u003cspan class=\"pl-s1\"\u003eurl\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003esearchParams\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eappend\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'topic'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e,\u003c/span\u003e \u003cspan class=\"pl-s\"\u003e'http://example.com/books/2'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e// All Review resources will match this pattern\u003c/span\u003e\n\u003cspan class=\"pl-s1\"\u003eurl\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003esearchParams\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eappend\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'topic'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e,\u003c/span\u003e \u003cspan class=\"pl-s\"\u003e'http://example.com/reviews/{id}'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"pl-k\"\u003econst\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eeventSource\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eEventSource\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003eurl\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\u003cspan class=\"pl-s1\"\u003eeventSource\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eonmessage\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eevent\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"pl-kos\"\u003e{\u003c/span\u003e\n \u003cspan class=\"pl-smi\"\u003econsole\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003elog\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eJSON\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eparse\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003eevent\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003edata\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\u003cspan class=\"pl-kos\"\u003e}\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eGoogle Chrome DevTools natively integrate a \u003ca href=\"https://twitter.com/ChromeDevTools/status/562324683194785792\" rel=\"nofollow\"\u003epractical UI\u003c/a\u003e displaying in live\nthe received events:\u003c/p\u003e\n\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/symfony/symfony-docs/blob/5.1/_images/mercure/chrome.png\"\u003e\u003cimg alt=\"/_images/mercure/chrome.png\" src=\"/symfony/symfony-docs/raw/5.1/_images/mercure/chrome.png\" style=\"max-width: 100%; height: auto;\"\u003e\u003c/a\u003e\n\u003cp dir=\"auto\"\u003eTo use it:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eopen the DevTools\u003c/li\u003e\n\u003cli\u003eselect the \"Network\" tab\u003c/li\u003e\n\u003cli\u003eclick on the request to the Mercure hub\u003c/li\u003e\n\u003cli\u003eclick on the \"EventStream\" sub-tab.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/div\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eTest if a URI Template match a URL using \u003ca href=\"https://uri-template-tester.mercure.rocks\" rel=\"nofollow\"\u003ethe online debugger\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-async-dispatching\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAsync dispatching\u003c/h3\u003e\u003ca id=\"user-content-async-dispatching\" class=\"anchor\" aria-label=\"Permalink: Async dispatching\" href=\"#async-dispatching\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eInstead of calling the \u003ccode\u003ePublisher\u003c/code\u003e service directly, you can also let Symfony\ndispatching the updates asynchronously thanks to the provided integration with\nthe Messenger component.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eFirst, be sure \u003ca href=\"#id9\"\u003e\u003cspan id=\"user-content-id10\"\u003e:doc:`to install the Messenger component \u0026lt;/messenger\u0026gt;`\u003c/span\u003e\u003c/a\u003e\nand to configure properly a transport (if you don't, the handler will\nbe called synchronously).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThen, dispatch the Mercure \u003ccode\u003eUpdate\u003c/code\u003e to the Messenger's Message Bus,\nit will be handled automatically:\u003c/p\u003e\n\u003cpre\u003e// src/Controller/PublishController.php\nnamespace App\\Controller;\n\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Mercure\\Update;\nuse Symfony\\Component\\Messenger\\MessageBusInterface;\n\nclass PublishController\n{\n public function __invoke(MessageBusInterface $bus): Response\n {\n $update = new Update(\n 'http://example.com/books/1',\n json_encode(['status' =\u0026gt; 'OutOfStock'])\n );\n\n // Sync, or async (RabbitMQ, Kafka...)\n $bus-\u0026gt;dispatch($update);\n\n return new Response('published!');\n }\n}\n\u003c/pre\u003e\n\u003ca name=\"user-content-discovery\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eDiscovery\u003c/h3\u003e\u003ca id=\"user-content-discovery\" class=\"anchor\" aria-label=\"Permalink: Discovery\" href=\"#discovery\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe Mercure protocol comes with a discovery mechanism.\nTo leverage it, the Symfony application must expose the URL of the Mercure Hub\nin a \u003ccode\u003eLink\u003c/code\u003e HTTP header.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/symfony/symfony-docs/blob/5.1/_images/mercure/discovery.png\"\u003e\u003cimg alt=\"/_images/mercure/discovery.png\" src=\"/symfony/symfony-docs/raw/5.1/_images/mercure/discovery.png\" style=\"max-width: 100%; height: auto;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eYou can create \u003ccode\u003eLink\u003c/code\u003e headers with the \u003ca href=\"#id11\"\u003e\u003cspan id=\"user-content-id12\"\u003e:doc:`WebLink Component \u0026lt;/web_link\u0026gt;`\u003c/span\u003e\u003c/a\u003e,\nby using the \u003ccode\u003eAbstractController::addLink\u003c/code\u003e helper method:\u003c/p\u003e\n\u003cpre\u003e// src/Controller/DiscoverController.php\nnamespace App\\Controller;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\WebLink\\Link;\n\nclass DiscoverController extends AbstractController\n{\n public function __invoke(Request $request): JsonResponse\n {\n // This parameter is automatically created by the MercureBundle\n $hubUrl = $this-\u0026gt;getParameter('mercure.default_hub');\n\n // Link: \u0026lt;http://localhost:3000/.well-known/mercure\u0026gt;; rel=\"mercure\"\n $this-\u0026gt;addLink($request, new Link('mercure', $hubUrl));\n\n return $this-\u0026gt;json([\n '@id' =\u0026gt; '/books/1',\n 'availability' =\u0026gt; 'https://schema.org/InStock',\n ]);\n }\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThen, this header can be parsed client-side to find the URL of the Hub,\nand to subscribe to it:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"// Fetch the original resource served by the Symfony web API\nfetch('/books/1') // Has Link: \u0026lt;http://localhost:3000/.well-known/mercure\u0026gt;; rel=\u0026quot;mercure\u0026quot;\n .then(response =\u0026gt; {\n // Extract the hub URL from the Link header\n const hubUrl = response.headers.get('Link').match(/\u0026lt;([^\u0026gt;]+)\u0026gt;;\\s+rel=(?:mercure|\u0026quot;[^\u0026quot;]*mercure[^\u0026quot;]*\u0026quot;)/)[1];\n\n // Append the topic(s) to subscribe as query parameter\n const hub = new URL(hubUrl);\n hub.searchParams.append('topic', 'http://example.com/books/{id}');\n\n // Subscribe to updates\n const eventSource = new EventSource(hub);\n eventSource.onmessage = event =\u0026gt; console.log(event.data);\n });\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c\"\u003e// Fetch the original resource served by the Symfony web API\u003c/span\u003e\n\u003cspan class=\"pl-en\"\u003efetch\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'/books/1'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e \u003cspan class=\"pl-c\"\u003e// Has Link: \u0026lt;http://localhost:3000/.well-known/mercure\u0026gt;; rel=\"mercure\"\u003c/span\u003e\n \u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003ethen\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003eresponse\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"pl-kos\"\u003e{\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e// Extract the hub URL from the Link header\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003econst\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ehubUrl\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eresponse\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eheaders\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'Link'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003ematch\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-pds\"\u003e\u003cspan class=\"pl-c1\"\u003e/\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e[\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e^\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e]\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e+\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e;\u003c/span\u003e\u003cspan class=\"pl-cce\"\u003e\\s\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e+\u003c/span\u003e\u003cspan class=\"pl-s\"\u003er\u003c/span\u003e\u003cspan class=\"pl-s\"\u003ee\u003c/span\u003e\u003cspan class=\"pl-s\"\u003el\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e=\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(?:\u003c/span\u003e\u003cspan class=\"pl-s\"\u003em\u003c/span\u003e\u003cspan class=\"pl-s\"\u003ee\u003c/span\u003e\u003cspan class=\"pl-s\"\u003er\u003c/span\u003e\u003cspan class=\"pl-s\"\u003ec\u003c/span\u003e\u003cspan class=\"pl-s\"\u003eu\u003c/span\u003e\u003cspan class=\"pl-s\"\u003er\u003c/span\u003e\u003cspan class=\"pl-s\"\u003ee\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e|\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e\"\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e[\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e^\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e\"\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e]\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e*\u003c/span\u003e\u003cspan class=\"pl-s\"\u003em\u003c/span\u003e\u003cspan class=\"pl-s\"\u003ee\u003c/span\u003e\u003cspan class=\"pl-s\"\u003er\u003c/span\u003e\u003cspan class=\"pl-s\"\u003ec\u003c/span\u003e\u003cspan class=\"pl-s\"\u003eu\u003c/span\u003e\u003cspan class=\"pl-s\"\u003er\u003c/span\u003e\u003cspan class=\"pl-s\"\u003ee\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e[\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e^\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e\"\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e]\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e*\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e\"\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e/\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e[\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e]\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\n \u003cspan class=\"pl-c\"\u003e// Append the topic(s) to subscribe as query parameter\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003econst\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ehub\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003eURL\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003ehubUrl\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n \u003cspan class=\"pl-s1\"\u003ehub\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003esearchParams\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eappend\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s\"\u003e'topic'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e,\u003c/span\u003e \u003cspan class=\"pl-s\"\u003e'http://example.com/books/{id}'\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n\n \u003cspan class=\"pl-c\"\u003e// Subscribe to updates\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003econst\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eeventSource\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eEventSource\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003ehub\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n \u003cspan class=\"pl-s1\"\u003eeventSource\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003eonmessage\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eevent\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003econsole\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-en\"\u003elog\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003eevent\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e.\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003edata\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\n \u003cspan class=\"pl-kos\"\u003e}\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ca name=\"user-content-authorization\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAuthorization\u003c/h3\u003e\u003ca id=\"user-content-authorization\" class=\"anchor\" aria-label=\"Permalink: Authorization\" href=\"#authorization\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eMercure also allows to dispatch updates only to authorized clients.\nTo do so, mark the update as \u003cstrong\u003eprivate\u003c/strong\u003e by setting the third parameter\nof the \u003ccode\u003eUpdate\u003c/code\u003e constructor to \u003ccode\u003etrue\u003c/code\u003e:\u003c/p\u003e\n\u003cpre\u003e// src/Controller/Publish.php\nnamespace App\\Controller;\n\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Mercure\\PublisherInterface;\nuse Symfony\\Component\\Mercure\\Update;\n\nclass PublishController\n{\n public function __invoke(PublisherInterface $publisher): Response\n {\n $update = new Update(\n 'http://example.com/books/1',\n json_encode(['status' =\u0026gt; 'OutOfStock']),\n true // private\n );\n\n // Publisher's JWT must contain this topic, a URI template it matches or * in mercure.publish or you'll get a 401\n // Subscriber's JWT must contain this topic, a URI template it matches or * in mercure.subscribe to receive the update\n $publisher($update);\n\n return new Response('private update published!');\n }\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eTo subscribe to private updates, subscribers must provide to the Hub\na JWT containing a topic selector matching by the update's topic.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eTo provide this JWT, the subscriber can use a cookie,\nor a \u003ccode\u003eAuthorization\u003c/code\u003e HTTP header.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eCookies are automatically sent by the browsers when opening an \u003ccode\u003eEventSource\u003c/code\u003e\nconnection if the \u003ccode\u003ewithCredentials\u003c/code\u003e attribute is set to \u003ccode\u003etrue\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"const eventSource = new EventSource(hub, {\n withCredentials: true\n});\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003econst\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eeventSource\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eEventSource\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003ehub\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e,\u003c/span\u003e \u003cspan class=\"pl-kos\"\u003e{\u003c/span\u003e\n \u003cspan class=\"pl-c1\"\u003ewithCredentials\u003c/span\u003e: \u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e\n\u003cspan class=\"pl-kos\"\u003e}\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eUsing cookies is the most secure and preferred way when the client is a web\nbrowser. If the client is not a web browser, then using an authorization header\nis the way to go.\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe native implementation of EventSource doesn't allow specifying headers.\nFor example, authorization using Bearer token. In order to achieve that, use \u003ca href=\"https://github.com/Yaffle/EventSource\"\u003ea polyfill\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"const es = new EventSourcePolyfill(url, {\n headers: {\n 'Authorization': 'Bearer ' + token,\n }\n});\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003econst\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ees\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e=\u003c/span\u003e \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eEventSourcePolyfill\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e(\u003c/span\u003e\u003cspan class=\"pl-s1\"\u003eurl\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e,\u003c/span\u003e \u003cspan class=\"pl-kos\"\u003e{\u003c/span\u003e\n \u003cspan class=\"pl-c1\"\u003eheaders\u003c/span\u003e: \u003cspan class=\"pl-kos\"\u003e{\u003c/span\u003e\n \u003cspan class=\"pl-s\"\u003e'Authorization'\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e'Bearer '\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e+\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003etoken\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e,\u003c/span\u003e\n \u003cspan class=\"pl-kos\"\u003e}\u003c/span\u003e\n\u003cspan class=\"pl-kos\"\u003e}\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e)\u003c/span\u003e\u003cspan class=\"pl-kos\"\u003e;\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIn the following example controller,\nthe generated cookie contains a JWT, itself containing the appropriate topic selector.\nThis cookie will be automatically sent by the web browser when connecting to the Hub.\nThen, the Hub will verify the validity of the provided JWT, and extract the topic selectors\nfrom it.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eTo generate the JWT, we'll use the \u003ccode\u003elcobucci/jwt\u003c/code\u003e library. Install it:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ composer require lcobucci/jwt\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eAnd here is the controller:\u003c/p\u003e\n\u003cpre\u003e// src/Controller/DiscoverController.php\nnamespace App\\Controller;\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Component\\HttpFoundation\\Cookie;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\WebLink\\Link;\n\nclass DiscoverController extends AbstractController\n{\n public function __invoke(Request $request): Response\n {\n $hubUrl = $this-\u0026gt;getParameter('mercure.default_hub');\n $this-\u0026gt;addLink($request, new Link('mercure', $hubUrl));\n\n $key = Key\\InMemory::plainText('mercure_secret_key'); // don't forget to set this parameter! Test value: !ChangeMe!\n $configuration = Configuration::forSymmetricSigner(new Sha256(), $key);\n\n $token = $configuration-\u0026gt;builder()\n -\u0026gt;withClaim('mercure', ['subscribe' =\u0026gt; [\"http://example.com/books/1\"]]) // can also be a URI template, or *\n -\u0026gt;getToken($configuration-\u0026gt;signer(), $configuration-\u0026gt;signingKey())\n -\u0026gt;toString();\n\n $response = $this-\u0026gt;json(['@id' =\u0026gt; '/demo/books/1', 'availability' =\u0026gt; 'https://schema.org/InStock']);\n $cookie = Cookie::create('mercureAuthorization')\n -\u0026gt;withValue($token)\n -\u0026gt;withPath('/.well-known/mercure')\n -\u0026gt;withSecure(true)\n -\u0026gt;withHttpOnly(true)\n -\u0026gt;withSameSite('strict')\n ;\n $response-\u0026gt;headers-\u0026gt;setCookie($cookie);\n\n return $response;\n }\n}\n\u003c/pre\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eCaution!\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eTo use the cookie authentication method, the Symfony app and the Hub\nmust be served from the same domain (can be different sub-domains).\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-programmatically-generating-the-jwt-used-to-publish\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eProgrammatically Generating The JWT Used to Publish\u003c/h3\u003e\u003ca id=\"user-content-programmatically-generating-the-jwt-used-to-publish\" class=\"anchor\" aria-label=\"Permalink: Programmatically Generating The JWT Used to Publish\" href=\"#programmatically-generating-the-jwt-used-to-publish\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eInstead of directly storing a JWT in the configuration,\nyou can create a service that will return the token used by\nthe \u003ccode\u003ePublisher\u003c/code\u003e object:\u003c/p\u003e\n\u003cpre\u003e// src/Mercure/MyJwtProvider.php\nnamespace App\\Mercure;\n\nfinal class MyJwtProvider\n{\n public function __invoke(): string\n {\n return 'the-JWT';\n }\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThen, reference this service in the bundle configuration:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/mercure.yaml\n mercure:\n hubs:\n default:\n url: https://mercure-hub.example.com/.well-known/mercure\n jwt_provider: App\\Mercure\\MyJwtProvider\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/mercure.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;config\u0026gt;\n \u0026lt;hub\n name=\"default\"\n url=\"https://mercure-hub.example.com/.well-known/mercure\"\n jwt-provider=\"App\\Mercure\\MyJwtProvider\"\n /\u0026gt;\n \u0026lt;/config\u0026gt;\n\n .. code-block:: php\n\n // config/packages/mercure.php\n use App\\Mercure\\MyJwtProvider;\n\n $container-\u0026gt;loadFromExtension('mercure', [\n 'hubs' =\u0026gt; [\n 'default' =\u0026gt; [\n 'url' =\u0026gt; 'https://mercure-hub.example.com/.well-known/mercure',\n 'jwt_provider' =\u0026gt; MyJwtProvider::class,\n ],\n ],\n ]);\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThis method is especially convenient when using tokens having an expiration\ndate, that can be refreshed programmatically.\u003c/p\u003e\n\u003ca name=\"user-content-web-apis\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eWeb APIs\u003c/h3\u003e\u003ca id=\"user-content-web-apis\" class=\"anchor\" aria-label=\"Permalink: Web APIs\" href=\"#web-apis\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eWhen creating a web API, it's convenient to be able to 46CE instantly push\nnew versions of the resources to all connected devices, and to update\ntheir views.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAPI Platform can use the Mercure Component to dispatch updates automatically,\nevery time an API resource is created, modified or deleted.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eStart by installing the library using its official recipe:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ composer require api\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThen, creating the following entity is enough to get a fully-featured\nhypermedia API, and automatic update broadcasting through the Mercure hub:\u003c/p\u003e\n\u003cpre\u003e// src/Entity/Book.php\nnamespace App\\Entity;\n\nuse ApiPlatform\\Core\\Annotation\\ApiResource;\nuse Doctrine\\ORM\\Mapping as ORM;\n\n/**\n* @ApiResource(mercure=true)\n* @ORM\\Entity\n*/\nclass Book\n{\n /**\n * @ORM\\Id\n * @ORM\\Column\n */\n public $name;\n\n /**\n * @ORM\\Column\n */\n public $status;\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eAs showcased \u003ca href=\"https://www.youtube.com/watch?v=UI1l0JOjLeI\" rel=\"nofollow\"\u003ein this recording\u003c/a\u003e, the API Platform Client Generator also\nallows to scaffold complete React and React Native applications from this API.\nThese applications will render the content of Mercure updates in real-time.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eCheckout \u003ca href=\"https://api-platform.com/docs/core/mercure/\" rel=\"nofollow\"\u003ethe dedicated API Platform documentation\u003c/a\u003e to learn more about\nits Mercure support.\u003c/p\u003e\n\u003ca name=\"user-content-testing\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eTesting\u003c/h3\u003e\u003ca id=\"user-content-testing\" class=\"anchor\" aria-label=\"Permalink: Testing\" href=\"#testing\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eDuring functional testing there is no need to send updates to Mercure. They will\nbe handled by a stub publisher:\u003c/p\u003e\n\u003cpre\u003e// tests/Functional/Fixtures/PublisherStub.php\nnamespace App\\Tests\\Functional\\Fixtures;\n\nuse Symfony\\Component\\Mercure\\PublisherInterface;\nuse Symfony\\Component\\Mercure\\Update;\n\nclass PublisherStub implements PublisherInterface\n{\n public function __invoke(Update $update): string\n {\n return '';\n }\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003ePublisherStub decorates the default publisher service so no updates are actually\nsent. Here is the PublisherStub implementation:\u003c/p\u003e\n\u003cpre\u003e# config/services_test.yaml\nApp\\Tests\\Functional\\Fixtures\\PublisherStub:\n decorates: mercure.hub.default.publisher\n\u003c/pre\u003e\n\u003ca name=\"user-content-debugging\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eDebugging\u003c/h3\u003e\u003ca id=\"user-content-debugging\" class=\"anchor\" aria-label=\"Permalink: Debugging\" href=\"#debugging\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cpre\u003e.. versionadded:: 0.2\n\n The WebProfiler panel was introduced in MercureBundle 0.2.\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eEnable the panel in your configuration, as follows:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/mercure.yaml\n mercure:\n enable_profiler: '%kernel.debug%'\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/mercure.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;container xmlns=\"http://symfony.com/schema/dic/services\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\"\u0026gt;\n\n \u0026lt;mercure:config enable_profiler=\"%kernel.debug%\"/\u0026gt;\n\n \u0026lt;/container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/mercure.php\n $container-\u0026gt;loadFromExtension('mercure', [\n 'enable_profiler' =\u0026gt; '%kernel.debug%',\n ]);\n\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/symfony/symfony-docs/blob/5.1/_images/mercure/panel.png\"\u003e\u003cimg alt=\"/_images/mercure/panel.png\" src=\"/symfony/symfony-docs/raw/5.1/_images/mercure/panel.png\" style=\"max-width: 100%; height: auto;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\n\u003c/article\u003e","renderedFileInfo":null,"shortPath":null,"symbolsEnabled":true,"tabSize":8,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":"/symfony/.github/blob/6f2ca452c856184a28812bb364b4e34ed50309da/FUNDING.yml","showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null,"symbols":null},"copilotInfo":null,"copilotAccessAllowed":false,"modelsAccessAllowed":false,"modelsRepoIntegrationEnabled":false,"csrf_tokens":{"/symfony/symfony-docs/branches":{"post":"cn2xAIpcwf7RSGXvt4HHVMozl7O8rKtRrxxqLGeNS2rcyoZbv2BvyYVlJRqNm3sWv_hNyyWJDkgwZaqUn4DhHQ"},"/repos/preferences":{"post":"0NW0-WNaU8OhbmrdYOBWuH1DcKml6mRoXUS6oW3bsOaOk3r7rYdV82Lm01mrswa0kOvb8RkgtawCalh6LRaiNQ"}}},"title":"symfony-docs/mercure.rst at 5.1 · symfony/symfony-docs","appPayload":{"helpUrl":"https://docs.github.com","findFileWorkerPath":"/assets-cdn/worker/find-file-worker-263cab1760dd.js","findInFileWorkerPath":"/assets-cdn/worker/find-in-file-worker-b84e9496fc59.js","githubDevUrl":null,"enabled_features":{"code_nav_ui_events":false,"react_blob_overlay":false,"accessible_code_button":true}}}
0