fix(flash): make flash messages one-shot per request

This commit is contained in:
2026-04-15 18:45:45 +02:00
parent 63be2def76
commit 57b7920098
7 changed files with 149 additions and 20 deletions

View File

@@ -8,7 +8,6 @@ use MintyPHP\Session;
class Flash
{
private const SESSION_KEY = 'flash_messages';
private const KEEP_KEY = 'flash_keep';
private static function ensureSession()
{
@@ -74,10 +73,50 @@ class Flash
}));
}
public static function has(): bool
/**
* Consume (read + remove) all messages that match any of the provided scopes.
* Global messages (scope=null) are included for every scope set.
*
* @param array<int, string> $scopes
* @return array<int, array<string, mixed>>
*/
public static function consumeForScopes(array $scopes): array
{
self::ensureSession();
return !empty($_SESSION[self::SESSION_KEY]);
$messages = $_SESSION[self::SESSION_KEY] ?? [];
if ($messages === []) {
return [];
}
$scopeSet = [];
foreach ($scopes as $scope) {
$scopeSet[$scope] = true;
}
if ($scopeSet === []) {
return [];
}
$consumed = [];
$remaining = [];
foreach ($messages as $message) {
$messageScope = $message['scope'] ?? null;
$matchesScope = $messageScope === null || isset($scopeSet[(string) $messageScope]);
if ($matchesScope) {
$consumed[] = $message;
continue;
}
$remaining[] = $message;
}
if ($remaining !== []) {
$_SESSION[self::SESSION_KEY] = $remaining;
} else {
unset($_SESSION[self::SESSION_KEY]);
}
return $consumed;
}
public static function dismiss(string $id): void
@@ -118,13 +157,4 @@ class Flash
unset($_SESSION[self::SESSION_KEY]);
}
}
// Prevents messages from being cleared on the next render — useful when a redirect chain crosses multiple pages.
public static function keep(int $times = 1)
{
self::ensureSession();
$times = max(1, $times);
$current = (int) ($_SESSION[self::KEEP_KEY] ?? 0);
$_SESSION[self::KEEP_KEY] = max($current, $times);
}
}

View File

@@ -38,6 +38,17 @@ function flashFormErrors(FormErrors $errors, ?string $scope = null, string $keyP
}
}
/**
* Consume and remove flash messages matching any provided scope.
*
* @param array<int, string> $scopes
* @return array<int, array<string, mixed>>
*/
function flashConsumeForScopes(array $scopes): array
{
return Flash::consumeForScopes($scopes);
}
function requestExtractNestedReturnValue(mixed $value): string
{
if (is_string($value)) {