Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.56% covered (success)
97.56%
40 / 41
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
CookieFlash
97.56% covered (success)
97.56%
40 / 41
87.50% covered (warning)
87.50%
7 / 8
15
0.00% covered (danger)
0.00%
0 / 1
 set
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getType
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 has
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clear
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 clearCache
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
6.05
 render
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * Cookie-based flash messages for stateless operation.
5 *
6 * Replaces session-based flash messages with cookie storage.
7 * Messages are stored as JSON in a cookie and cleared after reading.
8 *
9 * Usage:
10 *   CookieFlash::set('Your message', 'success')  // Set flash message
11 *   CookieFlash::get()                           // Get and clear message
12 *   CookieFlash::getType()                       // Get message type (info/success/error)
13 *   CookieFlash::has()                           // Check if message exists
14 */
15class CookieFlash
16{
17    private const COOKIE_NAME = 'flash_message';
18
19    /**
20     * @var array|null Cached flash data
21     */
22    private static ?array $cache = null;
23
24    /**
25     * Set a flash message.
26     *
27     * @param string $message The message to display
28     * @param string $type Message type: 'info', 'success', 'error', 'warning'
29     */
30    public static function set(string $message, string $type = 'info'): void
31    {
32        $data = [
33            'message' => $message,
34            'type' => $type,
35        ];
36
37        $json = json_encode($data);
38        $_COOKIE[self::COOKIE_NAME] = $json;
39        setcookie(self::COOKIE_NAME, $json, time() + 60, '/'); // 60 second expiry
40        self::$cache = $data;
41    }
42
43    /**
44     * Get and clear the flash message.
45     *
46     * @return string|null The message, or null if none exists
47     */
48    public static function get(): ?string
49    {
50        $data = self::load();
51        if (!$data)
52            return null;
53
54        // Clear the cookie after reading
55        self::clear();
56
57        return $data['message'];
58    }
59
60    /**
61     * Get the message type without clearing.
62     *
63     * @return string The type ('info', 'success', 'error', 'warning')
64     */
65    public static function getType(): string
66    {
67        $data = self::load();
68        return $data['type'] ?? 'info';
69    }
70
71    /**
72     * Check if a flash message exists.
73     *
74     * @return bool True if a message exists
75     */
76    public static function has(): bool
77    {
78        return self::load() !== null;
79    }
80
81    /**
82     * Clear the flash message.
83     */
84    public static function clear(): void
85    {
86        unset($_COOKIE[self::COOKIE_NAME]);
87        setcookie(self::COOKIE_NAME, '', time() - 3600, '/');
88        self::$cache = null;
89    }
90
91    /**
92     * Clear internal cache (for testing).
93     */
94    public static function clearCache(): void
95    {
96        self::$cache = null;
97    }
98
99    /**
100     * Load flash data from cookie.
101     *
102     * @return array|null The flash data array or null
103     */
104    private static function load(): ?array
105    {
106        if (self::$cache !== null)
107            return self::$cache;
108
109        if (!isset($_COOKIE[self::COOKIE_NAME]) || empty($_COOKIE[self::COOKIE_NAME]))
110            return null;
111
112        $decoded = json_decode($_COOKIE[self::COOKIE_NAME], true);
113        if (!is_array($decoded) || !isset($decoded['message']))
114            return null;
115
116        self::$cache = $decoded;
117        return self::$cache;
118    }
119
120    /**
121     * Render flash message as HTML (for use in views).
122     *
123     * Automatically gets type before clearing, then renders the message.
124     *
125     * @return string HTML output or empty string
126     */
127    public static function render(): string
128    {
129        if (!self::has())
130            return '';
131
132        $type = self::getType();  // Get type BEFORE get() clears it
133        $message = self::get();
134
135        $cssClass = match ($type)
136        {
137            'error' => 'alert-error',
138            'success' => 'alert-success',
139            'warning' => 'alert-warning',
140            default => 'alert-info',
141        };
142
143        return '<div class="alert ' . $cssClass . '">' . htmlspecialchars($message) . '</div>';
144    }
145}