Session::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 0
c 2
b 0
f 0
dl 0
loc 5
ccs 1
cts 1
cp 1
rs 10
cc 1
nc 1
nop 3
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Conia\Session;
6
7
use Conia\Session\OutOfBoundsException;
8
use Conia\Session\RuntimeException;
9
use SessionHandlerInterface;
10
11
/** @psalm-api */
12
class Session
13
{
14
    public const FLASH = 'conia_flash_messages';
15
    public const REMEMBER = 'conia_remembered_uri';
16
17 15
    public function __construct(
18
        protected readonly string $name = '',
19
        protected readonly array $options = [],
20
        protected readonly ?SessionHandlerInterface $handler = null,
21
    ) {
22 15
    }
23
24 9
    public function start(): void
25
    {
26 9
        if (session_status() === PHP_SESSION_NONE) {
27 9
            if (!headers_sent($file, $line)) {
28 9
                if ($this->name) {
29 2
                    session_name($this->name);
30
                }
31
32 9
                if ($this->handler) {
33 1
                    session_set_save_handler($this->handler, true);
34
                }
35
36 9
                session_cache_limiter('');
37
38 9
                if (!session_start($this->options)) {
39
                    // @codeCoverageIgnoreStart
40
                    throw new RuntimeException(__METHOD__ . 'session_start failed.');
41
                    // @codeCoverageIgnoreEnd
42
                }
43
            } else {
44
                // Cannot be provoked in the test suit
45
                // @codeCoverageIgnoreStart
46
                throw new RuntimeException(
47
                    __METHOD__ . 'Session started after headers sent. File: ' .
48
                        $file . ' line: ' . $line
49
                );
50
                // @codeCoverageIgnoreEnd
51
            }
52
        }
53
    }
54
55 9
    public function forget(): void
56
    {
57
        // Unset all of the session variables.
58 9
        session_unset(); // same as $_SESSION = [];
59
60
        // If it's desired to kill the session, also delete the session cookie.
61
        // Note: This will destroy the session, and not just the session data!
62 9
        if (ini_get('session.use_cookies')) {
63 9
            $params = session_get_cookie_params();
64 9
            setcookie(
65 9
                session_name(),
66 9
                '',
67 9
                time() - 42000,
68 9
                (string)$params['path'],
69 9
                (string)$params['domain'],
70 9
                (bool)$params['secure'],
71 9
                (bool)$params['httponly']
72 9
            );
73
        }
74
75
        // Finally, destroy the session.
76 9
        session_destroy();
77
    }
78
79 4
    public function name(): string
80
    {
81 4
        return session_name();
82
    }
83
84 1
    public function id(): string
85
    {
86 1
        return session_id();
87
    }
88
89
    /** @psalm-param non-empty-string $key */
90 7
    public function get(string $key, mixed $default = null): mixed
91
    {
92 7
        if ($this->has($key)) {
93 5
            return $_SESSION[$key];
94
        }
95 3
        if (func_num_args() > 1) {
96 2
            return $default;
97
        }
98
99 1
        throw new OutOfBoundsException(
100 1
            "The session key '{$key}' does not exist"
101 1
        );
102
    }
103
104
    /**
105
     * @psalm-suppress MixedAssignment
106
     *
107
     * @psalm-param non-empty-string $key
108
     * */
109 5
    public function set(string $key, mixed $value): void
110
    {
111 5
        if (!$this->active()) {
112 1
            throw new RuntimeException('Session not started');
113
        }
114
115 4
        $_SESSION[$key] = $value;
116
    }
117
118
    /** @psalm-param non-empty-string $key */
119 7
    public function has(string $key): bool
120
    {
121 7
        return isset($_SESSION[$key]);
122
    }
123
124
    /** @psalm-param non-empty-string $key */
125 1
    public function unset(string $key): void
126
    {
127 1
        unset($_SESSION[$key]);
128
    }
129
130 12
    public function active(): bool
131
    {
132 12
        return session_status() == PHP_SESSION_ACTIVE;
133
    }
134
135 1
    public function regenerate(): void
136
    {
137 1
        if ($this->active()) {
138 1
            session_regenerate_id(true);
139
        }
140
    }
141
142 3
    public function flash(
143
        string $message,
144
        string $queue = 'default',
145
    ): void {
146 3
        if (!$this->active()) {
147 1
            throw new RuntimeException('Session not started');
148
        }
149
150 2
        if (isset($_SESSION[self::FLASH]) && is_array($_SESSION[self::FLASH])) {
151 2
            $_SESSION[self::FLASH][] = [
152 2
                'message' => htmlspecialchars($message),
153 2
                'queue' => htmlspecialchars($queue),
154 2
            ];
155
156 2
            return;
157
        }
158
159 2
        $_SESSION[self::FLASH] = [[
160 2
            'message' => htmlspecialchars($message),
161 2
            'queue' => htmlspecialchars($queue),
162 2
        ]];
163
    }
164
165 2
    public function popFlashes(?string $queue = null): array
166
    {
167 2
        if ($queue === null) {
168 2
            $flashes = $_SESSION[self::FLASH];
169 2
            assert(is_array($flashes));
170 2
            $_SESSION[self::FLASH] = [];
171
        } else {
172 1
            $key = 0;
173 1
            $keys = [];
174 1
            $flashes = [];
175
176 1
            foreach ($_SESSION[self::FLASH] as $flash) {
177 1
                assert(isset($flash['queue']));
178 1
                assert(isset($flash['message']));
179
180 1
                if ($flash['queue'] === $queue) {
181 1
                    $flashes[] = $flash;
182 1
                    $keys[] = $key;
183
                }
184
185 1
                $key++;
186
            }
187
188 1
            foreach (array_reverse($keys) as $key) {
189
                if (
190 1
                    ($_SESSION[self::FLASH] ?? null)
191 1
                    && is_array($_SESSION[self::FLASH])
192
                ) {
193 1
                    if (isset($_SESSION[self::FLASH][$key])) {
194 1
                        unset($_SESSION[self::FLASH][$key]);
195
                    }
196
                }
197
            }
198
        }
199
200 2
        return $flashes;
201
    }
202
203 2
    public function hasFlashes(?string $queue = null): bool
204
    {
205
        /** @var array */
206 2
        $messages = $_SESSION[self::FLASH] ?? [];
207
208 2
        if ($queue) {
209 1
            return count(array_filter(
210 1
                $messages,
211 1
                fn (array $f) => $f['queue'] === $queue,
212 1
            )) > 0;
213
        }
214
215 2
        return count($messages) > 0;
216
    }
217
218 1
    public function rememberUri(
219
        string $uri,
220
        int $expires = 3600,
221
    ): void {
222 1
        $rememberedUri = [
223 1
            'uri' => $uri,
224 1
            'expires' => time() + $expires,
225 1
        ];
226 1
        $_SESSION[self::REMEMBER] = $rememberedUri;
227
    }
228
229 1
    public function rememberedUri(): string
230
    {
231
        /** @var null|array{uri: string, expires: int} */
232 1
        $rememberedUri = $_SESSION[self::REMEMBER] ?? null;
233
234 1
        if ($rememberedUri) {
235 1
            if ($rememberedUri['expires'] > time()) {
236 1
                $uri = $rememberedUri['uri'];
237 1
                unset($_SESSION[self::REMEMBER]);
238
239 1
                if (filter_var($uri, FILTER_VALIDATE_URL)) {
240 1
                    return $uri;
241
                }
242
            }
243
244 1
            unset($_SESSION[self::REMEMBER]);
245
        }
246
247 1
        return '/';
248
    }
249
}
250