ns/flash-bundle
ns/flash-bundle offers a lightweight flash messaging layer for PHP apps, with simple APIs for queuing, persisting, and rendering notifications across requests. It keeps user feedback consistent without extra boilerplate.
First, note the obvious: this is a Symfony bundle, not a Laravel package. There's no service provider, no config/app.php, no facade. If you're reaching for this in a Laravel codebase you have the wrong tool — session()->flash(...) plus a Blade partial covers the same ground with zero dependencies. The rest of this assessment is written for the Symfony developer who will actually use it.
Install and register
composer require ns/flash-bundle
With Symfony Flex the bundle is auto-registered via its composer.json extra.symfony block. On older/manual setups, add it to config/bundles.php:
return [
// ...
Ns\FlashBundle\NsFlashBundle::class => ['all' => true],
];
Check DependencyInjection/Configuration.php inside the vendor tree first — that's where the config tree (message type → CSS class, template path, auto-render toggle) lives, and the README doesn't always keep up with it.
First use case
The canonical flow is: set a flash in a controller action, redirect, render it in your base layout on the next request.
// src/Controller/PostController.php
public function create(Request $request, FlashService $flash): Response
{
// ... persist ...
$flash->add('success', 'Post saved.');
return $this->redirectToRoute('post_index');
}
{# templates/base.html.twig #}
<body>
{{ flash_render() }}
{% block body %}{% endblock %}
</body>
Where to look first
src/ — the flash service and the Twig extension are the whole package; read both completely, it's small.Resources/views/ — the default render template; copy it into templates/bundles/NsFlashBundle/ to override.DependencyInjection/Configuration.php — the real source of truth for options.Resources/config/services.yaml — the service ID, in case you need to inject it by string rather than by class.Typed messages drive the CSS. The package's value over raw Symfony flashes is the type→class mapping. Rather than addFlash('success', ...) and hand-wiring alert alert-success in Twig, you add a semantic type and the bundle renders the right markup. Keep type names aligned with whatever your config maps (success, error, warning, info by default) — inventing danger without updating config gives you an unclassed message.
Service injection over container access. Autowire the service into controllers and, more importantly, into domain services so a command or event listener can flash a message too:
public function __construct(private FlashService $flash) {}
If autowiring fails, fall back to the service ID from services.yaml — small bundles don't always alias the class name.
Render once, in the layout. Put flash_render() in a single base template. Scattering it across partials causes messages to render twice (Symfony's flash bag is consumed only when read — if the bundle buffers, you can get duplicates on re-inclusion).
Override the template for framework-specific markup. For Bootstrap/Tailwind/UiKit, copy the default view into templates/bundles/NsFlashBundle/ and edit. This is the lowest-friction extension point — you get the service's typing and the renderer's iteration for free while controlling the markup.
Combine with PRG and AJAX. Standard post/redirect/get works as expected because flashes live in the session. For AJAX responses you'll need to either return the rendered HTML as part of your JSON payload (render the template fragment server-side) or skip the bundle and let the client own toasts — the bundle is not designed to push messages down a JSON channel.
Coexistence with addFlash(). If you're migrating incrementally, be aware the bundle and Symfony's native FlashBagInterface may both be writing to the session under different keys. Read both in your layout during transition ({{ flash_render() }}{{ include('_native_flashes.html.twig') }}) or you'll silently drop the other side's messages.
Testing pattern. In WebTestCase, follow the redirect and assert on rendered output rather than the session array — the session key is an implementation detail the bundle can change:
$client->request('POST', '/posts', [...]);
$client->followRedirect();
$this->assertSelectorTextContains('.flash-success', 'Post saved.');
Read the composer.json before committing. Two stars and no dependents means this is a personal/utility bundle with no community maintenance. Pin to a specific version ("ns/flash-bundle": "1.*") rather than ^1.0, and be prepared to fork it — the code is small enough to adopt outright if the maintainer goes quiet.
Symfony and PHP version constraints are tight. Small bundles often declare Symfony 4/5 ranges or PHP 7.4 only. Check both composer.json and the CI matrix before adding it to a Symfony 6/7 project, or you'll spend an afternoon on a conflict you could have seen in thirty seconds.
Flash messages are session-scoped and single-read. If you don't render on the redirected request, the message is consumed or leaks into the next one — expect "ghost" toasts a page later if a redirect chain skips the layout. Symfony's own flash bag has the same behavior; it's not a bug in this bundle.
Escaping. The renderer almost certainly calls Twig's escape on the message body. If you intend to flash markup (a link inside the message), you must pass a Markup object or adjust the bundle's template with |raw — and then audit every call site, because that's an XSS footgun the moment user input reaches a flash message.
Config changes need a cache clear. After editing config/packages/ns_flash.yaml, run bin/console cache:clear (or cache:pool:clear cache.app) before assuming the setting is ignored. Standard Symfony friction, but it reads as a bundle bug on first encounter.
Auto-render conflicts. If the bundle offers an "auto-render into every response" option (some do, via a kernel response listener), it will fight with template-based rendering and produce duplicates or messages appearing outside your layout. Turn auto-render off and be explicit.
Debugging a message that never appears. Work outward: (1) does $session->getFlashBag()->all() show the raw key? (2) does the service ID resolve in bin/console debug:container ns_flash? (3) is flash_render() actually reached in the layout, or is a child template bypassing the base? (4) is output escaping swallowing it as empty markup? Most "broken" reports land on step 3.
Extension points are thin. Beyond template override and the config tree, there's not much surface. Don't expect custom message objects, priorities, or a renderer interface — if you need those, you're building a wrapper around FlashBagInterface anyway, at which point you may as well not depend on an unmaintained bundle for it.
How can I help you explore Laravel packages today?