Add Lib\Session: native session wrapper with flash data
Lib\Session (novaconium/lib/Session.php) is a thin, all-static wrapper around PHP's native session handling — get/set/has/remove plus CodeIgniter-style flash data (flash()/getFlash()): a value set now is readable on exactly the next request, then gone, for post/redirect/GET flows like the contact form's hand-rolled ?sent=1 (not refactored here — the original spec cites it as a motivating example, not a mandate). Lazy-start, same shape as the already-shipped Lib\Csrf, which the two classes can share a native session with in the same request without conflict. Flash data is a single per-request swap (snapshot last request's bucket, clear the stored one) rather than a sweep/expiry pass. Verified end-to-end across three separate HTTP requests sharing a cookie jar (not just in-process calls), confirming a flashed value survives exactly one subsequent request. Closes the "Session handling (with flash sessions)" backlog item in novaconium/ISSUES.md.
This commit is contained in:
@@ -191,6 +191,33 @@ tables of its own yet — if a future framework feature needs a shipped
|
||||
migration, extend this to the same App-over-novaconium two-root scan
|
||||
`Overlay.php` already does for pages/lib, don't invent a second mechanism.
|
||||
|
||||
`Lib\Session` (`novaconium/lib/Session.php`) is a thin wrapper around
|
||||
native PHP sessions (`session_start()`/`$_SESSION`, not a custom store),
|
||||
all-static and lazy-start like `Lib\Csrf` — nothing calls `session_start()`
|
||||
until the first real call to a `Session` method. Its `ensureSession()` is a
|
||||
**deliberate duplicate** of `Csrf::ensureSession()` (same cookie params,
|
||||
same `session_status()` guard) rather than a shared helper — keeps `Csrf`
|
||||
standalone with zero new dependencies on a class that didn't exist when it
|
||||
shipped, same tolerance for small duplication already established by the
|
||||
config-load block duplicated across `bootstrap.php`/`bin/clear-cache.php`/
|
||||
`Lib\Db::config()`. Both classes touching the same native session in the
|
||||
same request is safe either way, since `session_start()` silently no-ops
|
||||
if a session is already active — there's no ordering requirement between
|
||||
`Csrf::token()`/`::verify()` and any `Session` method.
|
||||
|
||||
Flash data (`Session::flash()`/`::getFlash()`) is one swap, not a
|
||||
sweep/expiry pass: the first `Session` method call in a request snapshots
|
||||
whatever was flashed on the *previous* request into an in-memory static
|
||||
(`self::$currentFlash`) for that request's `getFlash()` reads, then
|
||||
immediately empties the stored flash bucket so `flash()` calls made
|
||||
*during* the current request start filling a fresh bucket for the request
|
||||
after this one. This relies on static properties not persisting across
|
||||
requests (true under `php -S`, mod_php, and PHP-FPM alike — each request
|
||||
gets fresh PHP state regardless of worker-process reuse) — don't add any
|
||||
caching/memoization to `Session` that assumes static state survives
|
||||
between requests, since none of it does. See `/admin/docs/session` for a
|
||||
worked flash example.
|
||||
|
||||
## Running it
|
||||
|
||||
```
|
||||
|
||||
@@ -17,6 +17,7 @@ A tiny, Hugo-flavored PHP micro-framework. Routes are directories on disk, pages
|
||||
- **Self-hosted spam prevention & form validation** — `Lib\SpamGuard`, a reusable class for any form: a CSS-hidden honeypot field plus a submission-timing check, no external CAPTCHA service, site key, or outbound API call. Pairs with `Lib\FormValidator` (accumulating required-field/email/length checks) and `Lib\Validate` (the underlying validation primitives — email, length, phone, postal/zip, spam-word checks). All three ship in `novaconium/lib/`, demonstrated on the contact form.
|
||||
- **Form security by default** — `Lib\Input`, a cleaning accessor for `$_POST`/`$_GET` (defense-in-depth against HTML/script injection, not a substitute for parameterized queries), and `Lib\Csrf`, standalone session-token CSRF protection called directly from a sidecar. Both ship in `novaconium/lib/`, wired into the contact form, `/admin/clear-cache`, and `/admin/password-hash`.
|
||||
- **SQLite/MySQL database, zero setup** — `Lib\Db`, a thin PDO wrapper (no ORM) supporting multiple named connections open at once — e.g. this site's own SQLite data plus a MySQL connection to a legacy database, usable in the same sidecar — each with its own plain-SQL migration convention, applied automatically on first use or via `php novaconium/bin/migrate.php`. SQLite data lives in a project-owned top-level `data/` directory, outside both `public/` and `novaconium/`. See `/admin/docs/database`.
|
||||
- **Sessions with flash data** — `Lib\Session`, a thin wrapper around native PHP sessions with CodeIgniter-style flash values (set now, readable on exactly the next request) for post/redirect/GET flows without a query-string flag. Lazy-start, same mechanism `Lib\Csrf` already uses. See `/admin/docs/session`.
|
||||
- **No build step, no Composer** — clone it, point Apache (or `php -S`) at `public/`, and it runs. Twig is vendored as source; see `/admin/docs/upgrading-twig` for upgrading it.
|
||||
|
||||
## Getting started
|
||||
@@ -77,12 +78,14 @@ php novaconium/bin/create-static-page.php blog/my-new-post
|
||||
|
||||
## Documentation
|
||||
|
||||
The full framework documentation — routing, sidecars, libraries, layouts, static caching, SEO, Matomo analytics, admin authentication, styling, project layout, and third-party notices — lives at `/admin/docs` on any running instance (so it travels with the code, no internet connection needed). Highlights:
|
||||
The full framework documentation — routing, sidecars, libraries, database, session, layouts, static caching, SEO, Matomo analytics, admin authentication, styling, project layout, and third-party notices — lives at `/admin/docs` on any running instance (so it travels with the code, no internet connection needed). Highlights:
|
||||
|
||||
- [Getting started](http://127.0.0.1:8000/admin/docs/getting-started)
|
||||
- [Routing](http://127.0.0.1:8000/admin/docs/routing)
|
||||
- [Sidecars](http://127.0.0.1:8000/admin/docs/sidecars)
|
||||
- [Libraries](http://127.0.0.1:8000/admin/docs/libraries)
|
||||
- [Database](http://127.0.0.1:8000/admin/docs/database)
|
||||
- [Session](http://127.0.0.1:8000/admin/docs/session)
|
||||
- [Layouts](http://127.0.0.1:8000/admin/docs/layouts)
|
||||
- [Static caching](http://127.0.0.1:8000/admin/docs/caching)
|
||||
- [SEO](http://127.0.0.1:8000/admin/docs/seo)
|
||||
|
||||
+48
-36
@@ -54,55 +54,37 @@ expected vs. actual behavior. For features, include the motivating use case.>
|
||||
Suggested build order (foundations first, since admin login builds on two
|
||||
of the others):
|
||||
|
||||
1. **Session handling** — no dependencies.
|
||||
2. **Blog tags/categories** — no dependencies, but now needs a metadata
|
||||
1. **Blog tags/categories** — no dependencies, but now needs a metadata
|
||||
source design decision first (`PostRepository` was removed when
|
||||
`hello-world`/`second-post` became plain Twig pages — see the entry
|
||||
below), so worth doing first or together with Blog RSS feed.
|
||||
3. **Blog RSS feed** — no hard dependency, but a per-tag feed is easiest
|
||||
2. **Blog RSS feed** — no hard dependency, but a per-tag feed is easiest
|
||||
once tags/categories exist.
|
||||
4. **Internal search** — SQLite groundwork it needed is done; ready to
|
||||
3. **Internal search** — SQLite groundwork it needed is done; ready to
|
||||
build.
|
||||
5. **XML sitemap** — no hard dependency, but shares crawling logic with
|
||||
4. **XML sitemap** — no hard dependency, but shares crawling logic with
|
||||
Internal search, so easiest right after (or alongside) it.
|
||||
6. **Media/file manager** — no hard dependency; usable standalone, though
|
||||
5. **Media/file manager** — no hard dependency; usable standalone, though
|
||||
best gated behind admin login once that exists.
|
||||
7. **Draft pages (admin-only preview)** — no hard dependency; the admin
|
||||
6. **Draft pages (admin-only preview)** — no hard dependency; the admin
|
||||
authentication it reuses already shipped (see `/admin/docs/admin-auth`).
|
||||
8. **Syntax highlighting on code blocks** — no hard dependency on the
|
||||
7. **Syntax highlighting on code blocks** — no hard dependency on the
|
||||
copy button (see Done — shipped 2026-07-14), but touches the same
|
||||
`<pre><code>` markup it did.
|
||||
9. **Admin login & user management** — SQLite groundwork it needed is
|
||||
done; still needs session handling (logged-in state).
|
||||
10. **Ecommerce functionality** — needs session handling (cart) and admin
|
||||
login & user management (order/product admin, and customer accounts);
|
||||
SQLite groundwork it needed is done.
|
||||
11. **Paywall functionality** — needs everything Ecommerce needs, plus
|
||||
8. **Admin login & user management** — SQLite groundwork and session
|
||||
handling it needed are both done; ready to build.
|
||||
9. **Ecommerce functionality** — needs admin login & user management
|
||||
(order/product admin, and customer accounts); SQLite groundwork and
|
||||
session handling (cart) it needed are both done.
|
||||
10. **Paywall functionality** — needs everything Ecommerce needs, plus
|
||||
Ecommerce itself for the recurring-billing/payment-gateway plumbing;
|
||||
build after it rather than in parallel.
|
||||
|
||||
MySQL support shipped 2026-07-14 — see Done.
|
||||
MySQL support and Session handling (with flash sessions) shipped
|
||||
2026-07-14 — see Done.
|
||||
|
||||
See **Won't Do** below for 404 tracking, dropped in favor of Matomo.
|
||||
|
||||
### Session handling (with flash sessions)
|
||||
|
||||
- **Type:** Feature
|
||||
- **Status:** Backlog
|
||||
- **Priority:** High
|
||||
- **Added:** 2026-07-12
|
||||
|
||||
A `Lib\`/framework-core wrapper around PHP's native session handling
|
||||
(`session_start()` etc., not a custom session store) so sidecars and the
|
||||
future admin login have a consistent way to read/write session data
|
||||
instead of touching `$_SESSION` directly. Include CodeIgniter-style flash
|
||||
data — a value set now that survives exactly one subsequent request (e.g.
|
||||
`$session->flash('message', 'Saved.')` readable on the next request only,
|
||||
via a set-now/expire-after-read scheme) — for post/redirect/GET flows like
|
||||
`App/pages/contact/index.php` already does manually with `?sent=1`.
|
||||
Foundational alongside SQLite groundwork — no dependencies of its own, but
|
||||
admin login depends on it.
|
||||
|
||||
### Blog tags/categories
|
||||
|
||||
- **Type:** Feature
|
||||
@@ -287,7 +269,7 @@ full of stray markup.
|
||||
- **Type:** Feature
|
||||
- **Status:** Backlog
|
||||
- **Priority:** Medium
|
||||
- **Depends on:** SQLite groundwork (Done), Session handling (with flash sessions)
|
||||
- **Depends on:** SQLite groundwork (Done), Session handling (with flash sessions) (Done)
|
||||
- **Added:** 2026-07-12
|
||||
|
||||
A single-user HTTP Basic Auth stopgap now gates `/admin/*`
|
||||
@@ -306,7 +288,7 @@ with the new mechanism, not layering on top of it.
|
||||
- **Type:** Feature
|
||||
- **Status:** Backlog
|
||||
- **Priority:** Low
|
||||
- **Depends on:** SQLite groundwork (Done), Session handling (with flash sessions), Admin login & user management
|
||||
- **Depends on:** SQLite groundwork (Done), Session handling (with flash sessions) (Done), Admin login & user management
|
||||
- **Added:** 2026-07-12
|
||||
|
||||
Product catalog, cart, checkout, and order storage — a `products` /
|
||||
@@ -326,7 +308,7 @@ planning it all up front here.
|
||||
- **Type:** Feature
|
||||
- **Status:** Backlog
|
||||
- **Priority:** Low
|
||||
- **Depends on:** Ecommerce functionality (recurring billing/payment plumbing), SQLite groundwork (Done), Session handling (with flash sessions), Admin login & user management
|
||||
- **Depends on:** Ecommerce functionality (recurring billing/payment plumbing), SQLite groundwork (Done), Session handling (with flash sessions) (Done), Admin login & user management
|
||||
- **Added:** 2026-07-12
|
||||
|
||||
Subscription/membership content gating, similar to OnlyFans/Patreon:
|
||||
@@ -349,6 +331,36 @@ _Nothing yet._
|
||||
|
||||
## Done
|
||||
|
||||
### Session handling (with flash sessions)
|
||||
|
||||
- **Type:** Feature
|
||||
- **Status:** Done
|
||||
- **Priority:** High
|
||||
- **Added:** 2026-07-12
|
||||
- **Shipped:** 2026-07-14
|
||||
|
||||
A `Lib\` wrapper around PHP's native session handling (`session_start()`,
|
||||
`$_SESSION`, not a custom session store) — `Lib\Session`
|
||||
(`novaconium/lib/Session.php`), all-static and lazy-start, same shape as
|
||||
the already-shipped `Lib\Csrf` (which also touches the native session; the
|
||||
two coexist in the same request without conflict). Ships
|
||||
`get()`/`set()`/`has()`/`remove()` plus CodeIgniter-style flash data
|
||||
(`flash()`/`getFlash()`) — a value set now that's readable on exactly the
|
||||
next request, then gone, for post/redirect/GET flows like
|
||||
`App/pages/contact/index.php`'s hand-rolled `?sent=1` (not refactored to
|
||||
use it in this change — cited in the original spec as the motivating
|
||||
example, not a mandate to touch working demo code). The flash mechanism is
|
||||
a single per-request swap (snapshot last request's flash bucket into an
|
||||
in-memory static on first touch, then clear the stored bucket so this
|
||||
request's `flash()` calls fill a fresh one for the request after), not a
|
||||
separate expiry/sweep pass — verified end-to-end across three real,
|
||||
separate HTTP requests sharing a cookie jar (not three calls in one PHP
|
||||
process), confirming a flashed value appears on exactly the next request
|
||||
and is gone on the one after. Foundational alongside SQLite groundwork —
|
||||
admin login (next up) depends on it for logged-in state. Documented at
|
||||
`/admin/docs/session` and in `AGENTS.md` next to the `Lib\Csrf`/`Lib\Db`
|
||||
sections.
|
||||
|
||||
### MySQL support
|
||||
|
||||
- **Type:** Feature
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Lib;
|
||||
|
||||
/**
|
||||
* A thin wrapper around PHP's native session handling — session_start()
|
||||
* etc., not a custom session store — so sidecars have a consistent
|
||||
* get/set/flash API instead of touching $_SESSION directly. All-static,
|
||||
* lazy-start like Lib\Csrf: nothing calls session_start() until the first
|
||||
* real call to any method here, so a page that never touches Session (or
|
||||
* Csrf, which starts a session the same way) never gets a session cookie.
|
||||
*
|
||||
* ensureSession()'s body is deliberately duplicated from Csrf::ensureSession()
|
||||
* rather than extracted into a shared helper — keeps Csrf standalone with
|
||||
* zero new dependencies rather than coupling it to a class that didn't
|
||||
* exist when it shipped, consistent with this project's tolerance for
|
||||
* small duplication over premature coupling (see the config-load block
|
||||
* duplicated across bootstrap.php/bin/clear-cache.php/Lib\Db::config()).
|
||||
* Both classes touching the same native session in the same request is
|
||||
* safe either way — session_status() guards against a double session_start().
|
||||
*
|
||||
* Flash data: a value set now via flash() is readable via getFlash() on
|
||||
* exactly the next request, then gone — for post/redirect/GET flows like
|
||||
* "message sent" banners, without a query-string flag. See
|
||||
* /admin/docs/session for the mechanism and a worked example.
|
||||
*/
|
||||
final class Session
|
||||
{
|
||||
private const FLASH_KEY = '_flash';
|
||||
|
||||
private static bool $flashLoaded = false;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
private static array $currentFlash = [];
|
||||
|
||||
public static function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
self::ensureSession();
|
||||
|
||||
return $_SESSION[$key] ?? $default;
|
||||
}
|
||||
|
||||
public static function set(string $key, mixed $value): void
|
||||
{
|
||||
self::ensureSession();
|
||||
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
public static function has(string $key): bool
|
||||
{
|
||||
self::ensureSession();
|
||||
|
||||
return isset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
public static function remove(string $key): void
|
||||
{
|
||||
self::ensureSession();
|
||||
|
||||
unset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores $value so it's readable via getFlash($key) on the next
|
||||
* request only, then gone — regardless of whether getFlash() was
|
||||
* actually called on that next request.
|
||||
*/
|
||||
public static function flash(string $key, mixed $value): void
|
||||
{
|
||||
self::ensureSession();
|
||||
|
||||
$_SESSION[self::FLASH_KEY][$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a value flashed on the previous request. Never reflects a
|
||||
* value flashed during this same request — that value will be
|
||||
* readable on the next request instead.
|
||||
*/
|
||||
public static function getFlash(string $key, mixed $default = null): mixed
|
||||
{
|
||||
self::ensureSession();
|
||||
|
||||
return self::$currentFlash[$key] ?? $default;
|
||||
}
|
||||
|
||||
private static function ensureSession(): void
|
||||
{
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
// Must be called before session_start() — after is a silent no-op.
|
||||
session_set_cookie_params([
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
]);
|
||||
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Runs once per request, on whichever Session method is called
|
||||
// first: snapshot last request's flash bucket for this request's
|
||||
// getFlash() reads, then immediately reset the session's bucket so
|
||||
// flash() calls made during this request go to a fresh bucket —
|
||||
// the one the *next* request will snapshot. This single swap is
|
||||
// the entire flash mechanism; no separate expiry/sweep step needed,
|
||||
// since static properties don't persist across requests.
|
||||
if (!self::$flashLoaded) {
|
||||
self::$currentFlash = $_SESSION[self::FLASH_KEY] ?? [];
|
||||
$_SESSION[self::FLASH_KEY] = [];
|
||||
self::$flashLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
<li><a class="icon-link" href="/admin/docs/libraries">{{ icons.book() }}Libraries</a></li>
|
||||
<li><a class="icon-link" href="/admin/docs/config">{{ icons.book() }}Configuration</a></li>
|
||||
<li><a class="icon-link" href="/admin/docs/database">{{ icons.book() }}Database</a></li>
|
||||
<li><a class="icon-link" href="/admin/docs/session">{{ icons.book() }}Session</a></li>
|
||||
<li><a class="icon-link" href="/admin/docs/admin-auth">{{ icons.lock() }}Admin authentication</a></li>
|
||||
<li><a class="icon-link" href="/admin/docs/layouts">{{ icons.book() }}Layouts</a></li>
|
||||
<li><a class="icon-link" href="/admin/docs/caching">{{ icons.book() }}Static caching</a></li>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block title %}Docs{% endblock %}
|
||||
|
||||
{% block description %}Framework documentation: routing, sidecars, forms, libraries, database, layouts, caching, styling.{% endblock %}
|
||||
{% block description %}Framework documentation: routing, sidecars, forms, libraries, database, session, layouts, caching, styling.{% endblock %}
|
||||
|
||||
{% block robots %}noindex, nofollow{% endblock %}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
<li><a class="icon-link" href="/admin/docs/forms">{{ icons.email() }}Forms</a> — building a custom form with a sidecar, using the novaconium validation/spam libraries.</li>
|
||||
<li><a class="icon-link" href="/admin/docs/libraries">{{ icons.book() }}Libraries</a> — plain PHP classes under <code>Lib\</code>.</li>
|
||||
<li><a class="icon-link" href="/admin/docs/config">{{ icons.book() }}Configuration</a> — override framework settings from <code>App/config.php</code>.</li>
|
||||
<li><a class="icon-link" href="/admin/docs/database">{{ icons.book() }}Database</a> — <code>Lib\Db</code>, a thin PDO/SQLite wrapper with a plain-SQL migration convention.</li>
|
||||
<li><a class="icon-link" href="/admin/docs/database">{{ icons.book() }}Database</a> — <code>Lib\Db</code>, a thin PDO wrapper (SQLite and MySQL) with named, simultaneous connections and a plain-SQL migration convention.</li>
|
||||
<li><a class="icon-link" href="/admin/docs/session">{{ icons.book() }}Session</a> — <code>Lib\Session</code>, a thin wrapper around native PHP sessions, with CodeIgniter-style flash data.</li>
|
||||
<li><a class="icon-link" href="/admin/docs/admin-auth">{{ icons.lock() }}Admin authentication</a> — gate <code>/admin/*</code> behind HTTP Basic Auth, reusable for any future admin page.</li>
|
||||
<li><a class="icon-link" href="/admin/docs/layouts">{{ icons.book() }}Layouts</a> — pages and layouts are overridable, just like <code>Lib\</code>.</li>
|
||||
<li><a class="icon-link" href="/admin/docs/caching">{{ icons.book() }}Static caching</a> — how sidecar-less pages get served as static HTML.</li>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{% extends 'admin/docs/_layout/layout.twig' %}
|
||||
|
||||
{% import '_layout/icons.twig' as icons %}
|
||||
|
||||
{% block title %}Session{% endblock %}
|
||||
|
||||
{% block description %}Lib\Session — a thin wrapper around native PHP sessions, with CodeIgniter-style flash data.{% endblock %}
|
||||
|
||||
{% block robots %}noindex, nofollow{% endblock %}
|
||||
|
||||
{% block docs_content %}
|
||||
<h1>Session</h1>
|
||||
|
||||
<p><code>Lib\Session</code> (<code>novaconium/lib/Session.php</code>) is a thin wrapper around PHP's native session handling — plain <code>session_start()</code>/<code>$_SESSION</code>, not a custom session store — so sidecars have a consistent get/set API instead of touching <code>$_SESSION</code> directly. It's a <a class="icon-link" href="/admin/docs/libraries">{{ icons.book() }}Lib\</a> class like <code>Input</code>/<code>Csrf</code>/<code>Mailer</code>, so a project can override it entirely by dropping its own <code>App/lib/Session.php</code>.</p>
|
||||
|
||||
<h2>Using it</h2>
|
||||
|
||||
<pre><code>use Lib\Session;
|
||||
|
||||
Session::set('user_id', 42);
|
||||
$userId = Session::get('user_id'); // 42
|
||||
$loggedIn = Session::has('user_id'); // true
|
||||
Session::remove('user_id');</code></pre>
|
||||
|
||||
<p>The session is started lazily — nothing calls <code>session_start()</code> until the first real call to any <code>Session</code> method, so a page that never touches <code>Session</code> never gets a session cookie. <a class="icon-link" href="/admin/docs/libraries">{{ icons.book() }}Lib\Csrf</a> uses the exact same lazy-start mechanism to run its own session-token CSRF protection — both classes can touch the same native session in the same request without conflict, since <code>session_start()</code> is only ever actually called once (PHP no-ops a second call).</p>
|
||||
|
||||
<h2>Flash data</h2>
|
||||
|
||||
<p>A flashed value is readable on exactly the next request, then gone — useful for post/redirect/GET flows (a "message sent" banner after a redirect) without a query-string flag like <code>?sent=1</code>:</p>
|
||||
|
||||
<pre><code>use Lib\Session;
|
||||
use App\Response;
|
||||
|
||||
// In the sidecar handling the POST:
|
||||
Session::flash('message', 'Sent! We\'ll be in touch soon.');
|
||||
return Response::redirect('/contact');
|
||||
|
||||
// In the sidecar handling the following GET (the redirect target):
|
||||
return [
|
||||
'flashMessage' => Session::getFlash('message'),
|
||||
];</code></pre>
|
||||
|
||||
<p><code>Session::getFlash($key, $default = null)</code> returns the value on the request immediately after <code>flash()</code> was called, and the default on every request after that — regardless of whether <code>getFlash()</code> was actually called on that one request in between. A value flashed during the current request is never visible to <code>getFlash()</code> during that same request; it becomes visible on the next one.</p>
|
||||
|
||||
<p>Mechanically, this is a single swap rather than a separate expiry/sweep step: the first time any <code>Session</code> method runs in a request, it snapshots whatever was flashed on the previous request into an in-memory value for that request's <code>getFlash()</code> calls, then immediately clears the stored flash bucket so <code>flash()</code> calls made during the current request start filling a fresh bucket — the one the next request will snapshot in turn.</p>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user