Test Failed
Push — main ( b20cf2...6af967 )
by Thomas
12:34
created

Session::id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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