php-flasher/flasher-symfony
Symfony integration for PHPFlasher that lets you show flash messages and toast notifications through a simple, framework-native API. Publish notifications from controllers or services and render them in your Twig templates with minimal setup.
composer require php-flasher/flasher-symfony
With Symfony Flex the recipe registers Flasher\Symfony\FlasherBundle in config/bundles.php and drops config/packages/flasher.yaml. Without Flex, add the bundle manually and create that config file yourself — bin/console debug:config flasher is the fastest way to confirm it loaded.
Where to look first, in order:
config/packages/flasher.yaml — the default adapter and its options block. This is where 90% of "why does it look wrong" problems live.{{ flasher_render() }} — the Twig function that emits the adapter CSS/JS plus the container the JS reads on page load. Put it once, in your base layout, just before </body> (styles are inlined, so head placement is optional).vendor/php-flasher/flasher-symfony/src/ — small surface area: the bundle class, the DI extension, the Twig extension, and a response listener. Read it in ten minutes.First use case — the canonical one, and the reason you install this at all:
// Controller
public function edit(Request $request): Response
{
// ... handle form
$this->addFlash('success', 'Saved.'); // plain Symfony flash, not this package
return $this->redirectToRoute('home');
}
becomes:
public function edit(Request $request, FlasherInterface $flasher): Response
{
// ... handle form
$flasher->addSuccess('Saved.'); // queued, survives the redirect
return $this->redirectToRoute('home');
}
The layout renders {{ flasher_render() }}, and the message appears on the page after the redirect.
Inject the interface, not the concrete class. Flasher\Prime\FlasherInterface is autowired. Prefer constructor injection in services and method injection in controllers.
Two distinct flows — know which you want:
addSuccess(), addError(), addWarning(), addInfo() — render on the current response. Use after a non-redirecting action, or when you're about to return a template directly.flash() / flashSuccess() — pushes into the session flash bag, delivered on the next request. Use with redirectToRoute(), which is the normal POST-redirect-GET shape.Mixing them is the most common source of "nothing shows up" — queuing with flash() and then rendering a template without a redirect is fine, but queuing and never issuing a subsequent request is not.
Per-call adapter switching when one area of the app needs a different look:
$flasher->adapter('sweetalert')->addSuccess('Account deleted.');
$flasher->adapter('toastr')->addInfo('Draft autosaved.');
Install the matching frontend package (php-flasher/flasher-toastr, -sweetalert, -notyf, -noty, -pnotify) and set default: in YAML for the app-wide choice. Keep the version constraint identical across all flasher-* packages.
Form + validation. Flash inside the isValid() branch and redirect; flash the error summary in the !isValid() branch without a redirect so the user sees it alongside the field errors. Don't notify on every individual constraint violation — the form renderer already does that.
Live/partial updates (Turbo, HTMX, fetch). The server-side listener only injects into a normal full-page response. For fragments, either include {{ flasher_render() }} in the partial or call the JS global directly (flasher.success('...')) from your Stimulus controller. Do not render the helper in both the layout and the fragment — you'll double-fire every queued message.
Event-driven notifications. Symfony's event dispatcher is a clean place to attach notifications to things like LoginSuccessEvent — inject the flasher, queue in the listener, let the redirect deliver it. Watch that the listener fires before the response is sent.
Layout strategy. One flasher_render() in base.html.twig, and have every layout extend it. Apps that maintain several independent layouts (admin vs public) usually want a different adapter in each and a separate {{ flasher_render() }} call per layout.
php-flasher/php-flasher monorepo. Bugs you file against this repo may sit untouched indefinitely; the low star count reflects the split, not necessarily adoption of the parent project.flasher.yaml, run bin/console cache:clear (or cache:warmup in CI). Stale compiled config is a frequent phantom bug in dev.flasher_render() must actually be on the page, and the adapter package must be installed. If you use Webpack Encore or AssetMapper instead of the injected CDN/script tags, set root_script (and scripts/styles as needed) explicitly and verify with the browser network tab.flasher_render() twice on one page (e.g. layout + a reusable component) and the second read gets nothing — or, worse, the JS gets an empty payload and the first batch silently vanishes. Render once.flash() against a stateless firewall or an API route has nowhere to persist. Use immediate addSuccess() there, and accept that JSON API clients won't see anything anyway.StreamedResponse, binary downloads (CSV/PDF exports), and 204/302 bodies are not touched — don't queue a "Download ready" toast on the export request and expect it on the download.options are serialized to JSON and handed to the frontend library. JS function callbacks (onclick, beforeOpen, etc.) cannot survive that serialization — set them in your own JS init, not in YAML.translate is enabled, messages go through the Symfony translator. Set the correct domain and make sure the locale is resolved at render time, not at queue time, or you'll render the previous request's language.debug:container flasher for the service, debug:config flasher for merged config, {{ flasher_render() }} output in the page source for the payload, and the JS console for adapter load errors.flasher.yaml. Run bin/console debug:container --tag to find the exact tag name in your installed version rather than trusting docs from an older release.php-flasher/* packages should share a major version; drift between core and an adapter produces confusing "option ignored" behaviour rather than a hard error.How can I help you explore Laravel packages today?