Session   F
last analyzed

Complexity

Total Complexity 138

Size/Duplication

Total Lines 998
Duplicated Lines 0 %

Test Coverage

Coverage 59.06%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 266
dl 0
loc 998
ccs 176
cts 298
cp 0.5906
rs 2
c 2
b 0
f 0
wmc 138

59 Methods

Rating   Name   Duplication   Size   Complexity  
A getCount() 0 4 1
A getCacheLimiter() 0 3 1
A setCacheLimiter() 0 5 1
A getUseCustomStorage() 0 3 1
A init() 0 7 2
A setSavePath() 0 7 2
A getCookieParams() 0 3 1
A getName() 0 3 1
A setCookieParams() 0 3 1
A setHasSessionId() 0 3 1
A regenerateID() 0 9 4
A getSavePath() 0 3 1
A destroy() 0 10 2
A setId() 0 3 1
A getIsActive() 0 3 1
B open() 0 26 8
A getId() 0 3 1
A setCookieParamsInternal() 0 14 4
A close() 0 7 3
A setName() 0 5 1
B registerSessionHandler() 0 30 11
A removeAll() 0 5 2
A removeFlash() 0 8 2
A set() 0 4 1
A getGCProbability() 0 3 1
A offsetUnset() 0 5 1
A openSession() 0 3 1
A getUseCookies() 0 9 4
A addFlash() 0 11 4
A freeze() 0 8 3
A offsetGet() 0 6 2
B getHasSessionId() 0 15 7
A closeSession() 0 3 1
A readSession() 0 3 1
A offsetSet() 0 5 1
A has() 0 4 1
A getUseTransparentSessionID() 0 6 2
A setTimeout() 0 5 1
A setUseStrictMode() 0 12 5
A gcSession() 0 3 1
A setUseCookies() 0 20 6
A getTimeout() 0 3 1
A getUseStrictMode() 0 7 2
A setGCProbability() 0 11 3
A getFlash() 0 17 4
A setFlash() 0 6 2
A destroySession() 0 3 1
A remove() 0 11 2
A removeAllFlashes() 0 7 2
A unfreeze() 0 15 5
A updateFlashCounters() 0 15 5
A offsetExists() 0 6 1
A writeSession() 0 3 1
A count() 0 4 1
A get() 0 4 2
A hasFlash() 0 3 1
A setUseTransparentSessionID() 0 7 3
A getIterator() 0 5 1
A getAllFlashes() 0 21 5

How to fix   Complexity   

Complex Class

Complex classes like Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Session, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\Component;
12
use yii\base\InvalidArgumentException;
13
use yii\base\InvalidConfigException;
14
15
/**
16
 * Session provides session data management and the related configurations.
17
 *
18
 * Session is a Web application component that can be accessed via `Yii::$app->session`.
19
 *
20
 * To start the session, call [[open()]]; To complete and send out session data, call [[close()]];
21
 * To destroy the session, call [[destroy()]].
22
 *
23
 * Session can be used like an array to set and get session data. For example,
24
 *
25
 * ```php
26
 * $session = new Session;
27
 * $session->open();
28
 * $value1 = $session['name1'];  // get session variable 'name1'
29
 * $value2 = $session['name2'];  // get session variable 'name2'
30
 * foreach ($session as $name => $value) // traverse all session variables
31
 * $session['name3'] = $value3;  // set session variable 'name3'
32
 * ```
33
 *
34
 * Session can be extended to support customized session storage.
35
 * To do so, override [[useCustomStorage]] so that it returns true, and
36
 * override these methods with the actual logic about using custom storage:
37
 * [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]],
38
 * [[destroySession()]] and [[gcSession()]].
39
 *
40
 * Session also supports a special type of session data, called *flash messages*.
41
 * A flash message is available only in the current request and the next request.
42
 * After that, it will be deleted automatically. Flash messages are particularly
43
 * useful for displaying confirmation messages. To use flash messages, simply
44
 * call methods such as [[setFlash()]], [[getFlash()]].
45
 *
46
 * For more details and usage information on Session, see the [guide article on sessions](guide:runtime-sessions-cookies).
47
 *
48
 * @property-read array $allFlashes Flash messages (key => message or key => [message1, message2]).
49
 * @property-read string $cacheLimiter Current cache limiter.
50
 * @property-read array $cookieParams The session cookie parameters.
51
 * @property-read int $count The number of session variables.
52
 * @property-write string $flash The key identifying the flash message. Note that flash messages and normal
53
 * session variables share the same name space. If you have a normal session variable using the same name, its
54
 * value will be overwritten by this method.
55
 * @property float $gCProbability The probability (percentage) that the GC (garbage collection) process is
56
 * started on every session initialization.
57
 * @property bool $hasSessionId Whether the current request has sent the session ID.
58
 * @property string $id The current session ID.
59
 * @property-read bool $isActive Whether the session has started.
60
 * @property-read SessionIterator $iterator An iterator for traversing the session variables.
61
 * @property string $name The current session name.
62
 * @property string $savePath The current session save path, defaults to '/tmp'.
63
 * @property int $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up. The
64
 * default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
65
 * @property bool|null $useCookies The value indicating whether cookies should be used to store session IDs.
66
 * @property-read bool $useCustomStorage Whether to use custom storage.
67
 * @property bool $useStrictMode Whether strict mode is enabled or not.
68
 * @property bool $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to
69
 * false.
70
 *
71
 * @author Qiang Xue <[email protected]>
72
 * @since 2.0
73
 */
74
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
75
{
76
    /**
77
     * @var string|null Holds the original session module (before a custom handler is registered) so that it can be
78
     * restored when a Session component without custom handler is used after one that has.
79
     */
80
    protected static $_originalSessionModule = null;
81
    /**
82
     * Polyfill for ini directive session.use-strict-mode for PHP < 5.5.2.
83
     */
84
    private static $_useStrictModePolyfill = false;
85
    /**
86
     * @var string the name of the session variable that stores the flash message data.
87
     */
88
    public $flashParam = '__flash';
89
    /**
90
     * @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods.
91
     */
92
    public $handler;
93
94
    /**
95
     * @var string|null Holds the session id in case useStrictMode is enabled and the session id needs to be regenerated
96
     */
97
    protected $_forceRegenerateId = null;
98
99
    /**
100
     * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
101
     * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly'
102
     * @see https://www.php.net/manual/en/function.session-set-cookie-params.php
103
     */
104
    private $_cookieParams = ['httponly' => true];
105
    /**
106
     * @var array|null is used for saving session between recreations due to session parameters update.
107
     */
108
    private $_frozenSessionData;
109
110
111
    /**
112
     * Initializes the application component.
113
     * This method is required by IApplicationComponent and is invoked by application.
114
     */
115 98
    public function init()
116
    {
117 98
        parent::init();
118 98
        register_shutdown_function([$this, 'close']);
119 98
        if ($this->getIsActive()) {
120 5
            Yii::warning('Session is already started', __METHOD__);
121 5
            $this->updateFlashCounters();
122
        }
123
    }
124
125
    /**
126
     * Returns a value indicating whether to use custom session storage.
127
     * This method should be overridden to return true by child classes that implement custom session storage.
128
     * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
129
     * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
130
     * @return bool whether to use custom storage.
131
     */
132 69
    public function getUseCustomStorage()
133
    {
134 69
        return false;
135
    }
136
137
    /**
138
     * Starts the session.
139
     */
140 72
    public function open()
141
    {
142 72
        if ($this->getIsActive()) {
143 72
            return;
144
        }
145
146 71
        $this->registerSessionHandler();
147
148 71
        if ($this->getUseCookies() !== false) {
149 71
            $this->setCookieParamsInternal();
150
        }
151
152 71
        YII_DEBUG ? session_start() : @session_start();
153
154 71
        if ($this->getUseStrictMode() && $this->_forceRegenerateId) {
155 1
            $this->regenerateID();
156 1
            $this->_forceRegenerateId = null;
157
        }
158
159 71
        if ($this->getIsActive()) {
160 71
            Yii::info('Session started', __METHOD__);
161 71
            $this->updateFlashCounters();
162
        } else {
163
            $error = error_get_last();
164
            $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
165
            Yii::error($message, __METHOD__);
166
        }
167
    }
168
169
    /**
170
     * Registers session handler.
171
     * @throws \yii\base\InvalidConfigException
172
     */
173 71
    protected function registerSessionHandler()
174
    {
175 71
        $sessionModuleName = session_module_name();
176 71
        if (static::$_originalSessionModule === null) {
177 1
            static::$_originalSessionModule = $sessionModuleName;
178
        }
179
180 71
        if ($this->handler === null && $this->getUseCustomStorage()) {
181 2
            $this->handler = Yii::createObject(
182 2
                [
183 2
                    '__class' => SessionHandler::class,
184 2
                    '__construct()' => [$this],
185 2
                ]
186 2
            );
187
        }
188
189 71
        if ($this->handler !== null) {
190 2
            if (is_array($this->handler)) {
191
                $this->handler = Yii::createObject($this->handler);
192
            }
193 2
            if (!$this->handler instanceof \SessionHandlerInterface) {
194
                throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
195
            }
196 2
            YII_DEBUG ? session_set_save_handler($this->handler, false) : @session_set_save_handler($this->handler, false);
197
        } elseif (
198 69
            $sessionModuleName !== static::$_originalSessionModule
199 69
            && static::$_originalSessionModule !== null
200 69
            && static::$_originalSessionModule !== 'user'
201
        ) {
202 1
            session_module_name(static::$_originalSessionModule);
203
        }
204
    }
205
206
    /**
207
     * Ends the current session and store session data.
208
     */
209 94
    public function close()
210
    {
211 94
        if ($this->getIsActive()) {
212 71
            YII_DEBUG ? session_write_close() : @session_write_close();
213
        }
214
215 94
        $this->_forceRegenerateId = null;
216
    }
217
218
    /**
219
     * Frees all session variables and destroys all data registered to a session.
220
     *
221
     * This method has no effect when session is not [[getIsActive()|active]].
222
     * Make sure to call [[open()]] before calling it.
223
     * @see open()
224
     * @see isActive
225
     */
226 7
    public function destroy()
227
    {
228 7
        if ($this->getIsActive()) {
229 7
            $sessionId = session_id();
230 7
            $this->close();
231 7
            $this->setId($sessionId);
232 7
            $this->open();
233 7
            session_unset();
234 7
            session_destroy();
235 7
            $this->setId($sessionId);
236
        }
237
    }
238
239
    /**
240
     * @return bool whether the session has started
241
     */
242 98
    public function getIsActive()
243
    {
244 98
        return session_status() === PHP_SESSION_ACTIVE;
245
    }
246
247
    private $_hasSessionId;
248
249
    /**
250
     * Returns a value indicating whether the current request has sent the session ID.
251
     * The default implementation will check cookie and $_GET using the session name.
252
     * If you send session ID via other ways, you may need to override this method
253
     * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
254
     * @return bool whether the current request has sent the session ID.
255
     */
256 37
    public function getHasSessionId()
257
    {
258 37
        if ($this->_hasSessionId === null) {
259 37
            $name = $this->getName();
260 37
            $request = Yii::$app->getRequest();
261 37
            if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) {
262
                $this->_hasSessionId = true;
263 37
            } elseif (PHP_VERSION_ID < 80400 && !ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) {
264
                $this->_hasSessionId = $request->get($name) != '';
0 ignored issues
show
Bug introduced by
The method get() does not exist on yii\console\Request. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

264
                $this->_hasSessionId = $request->/** @scrutinizer ignore-call */ get($name) != '';
Loading history...
265
            } else {
266 37
                $this->_hasSessionId = false;
267
            }
268
        }
269
270 37
        return $this->_hasSessionId;
271
    }
272
273
    /**
274
     * Sets the value indicating whether the current request has sent the session ID.
275
     * This method is provided so that you can override the default way of determining
276
     * whether the session ID is sent.
277
     * @param bool $value whether the current request has sent the session ID.
278
     */
279
    public function setHasSessionId($value)
280
    {
281
        $this->_hasSessionId = $value;
282
    }
283
284
    /**
285
     * Gets the session ID.
286
     * This is a wrapper for [PHP session_id()](https://www.php.net/manual/en/function.session-id.php).
287
     * @return string the current session ID
288
     */
289 15
    public function getId()
290
    {
291 15
        return session_id();
292
    }
293
294
    /**
295
     * Sets the session ID.
296
     * This is a wrapper for [PHP session_id()](https://www.php.net/manual/en/function.session-id.php).
297
     * @param string $value the session ID for the current session
298
     */
299 9
    public function setId($value)
300
    {
301 9
        session_id($value);
302
    }
303
304
    /**
305
     * Updates the current session ID with a newly generated one.
306
     *
307
     * Please refer to <https://www.php.net/session_regenerate_id> for more details.
308
     *
309
     * This method has no effect when session is not [[getIsActive()|active]].
310
     * Make sure to call [[open()]] before calling it.
311
     *
312
     * @param bool $deleteOldSession Whether to delete the old associated session file or not.
313
     * @see open()
314
     * @see isActive
315
     */
316 46
    public function regenerateID($deleteOldSession = false)
317
    {
318 46
        if ($this->getIsActive()) {
319
            // add @ to inhibit possible warning due to race condition
320
            // https://github.com/yiisoft/yii2/pull/1812
321 33
            if (YII_DEBUG && !headers_sent()) {
322 33
                session_regenerate_id($deleteOldSession);
323
            } else {
324
                @session_regenerate_id($deleteOldSession);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_regenerate_id(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

324
                /** @scrutinizer ignore-unhandled */ @session_regenerate_id($deleteOldSession);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
325
            }
326
        }
327
    }
328
329
    /**
330
     * Gets the name of the current session.
331
     * This is a wrapper for [PHP session_name()](https://www.php.net/manual/en/function.session-name.php).
332
     * @return string the current session name
333
     */
334 38
    public function getName()
335
    {
336 38
        return session_name();
337
    }
338
339
    /**
340
     * Sets the name for the current session.
341
     * This is a wrapper for [PHP session_name()](https://www.php.net/manual/en/function.session-name.php).
342
     * @param string $value the session name for the current session, must be an alphanumeric string.
343
     * It defaults to "PHPSESSID".
344
     */
345 1
    public function setName($value)
346
    {
347 1
        $this->freeze();
348 1
        session_name($value);
349 1
        $this->unfreeze();
350
    }
351
352
    /**
353
     * Gets the current session save path.
354
     * This is a wrapper for [PHP session_save_path()](https://www.php.net/manual/en/function.session-save-path.php).
355
     * @return string the current session save path, defaults to '/tmp'.
356
     */
357
    public function getSavePath()
358
    {
359
        return session_save_path();
360
    }
361
362
    /**
363
     * Sets the current session save path.
364
     * This is a wrapper for [PHP session_save_path()](https://www.php.net/manual/en/function.session-save-path.php).
365
     * @param string $value the current session save path. This can be either a directory name or a [path alias](guide:concept-aliases).
366
     * @throws InvalidArgumentException if the path is not a valid directory
367
     */
368
    public function setSavePath($value)
369
    {
370
        $path = Yii::getAlias($value);
371
        if (is_dir($path)) {
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type false; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

371
        if (is_dir(/** @scrutinizer ignore-type */ $path)) {
Loading history...
372
            session_save_path($path);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type false; however, parameter $path of session_save_path() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

372
            session_save_path(/** @scrutinizer ignore-type */ $path);
Loading history...
373
        } else {
374
            throw new InvalidArgumentException("Session save path is not a valid directory: $value");
375
        }
376
    }
377
378
    /**
379
     * @return array the session cookie parameters.
380
     * @see https://www.php.net/manual/en/function.session-get-cookie-params.php
381
     */
382 71
    public function getCookieParams()
383
    {
384 71
        return array_merge(session_get_cookie_params(), array_change_key_case($this->_cookieParams));
385
    }
386
387
    /**
388
     * Sets the session cookie parameters.
389
     * The cookie parameters passed to this method will be merged with the result
390
     * of `session_get_cookie_params()`.
391
     * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httponly`.
392
     * Starting with Yii 2.0.21 `sameSite` is also supported. It requires PHP version 7.3.0 or higher.
393
     * For security, an exception will be thrown if `sameSite` is set while using an unsupported version of PHP.
394
     * To use this feature across different PHP versions check the version first. E.g.
395
     * ```php
396
     * [
397
     *     'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null,
398
     * ]
399
     * ```
400
     * See https://owasp.org/www-community/SameSite for more information about `sameSite`.
401
     *
402
     * @throws InvalidArgumentException if the parameters are incomplete.
403
     * @see https://www.php.net/manual/en/function.session-set-cookie-params.php
404
     */
405
    public function setCookieParams(array $value)
406
    {
407
        $this->_cookieParams = $value;
408
    }
409
410
    /**
411
     * Sets the session cookie parameters.
412
     * This method is called by [[open()]] when it is about to open the session.
413
     * @throws InvalidArgumentException if the parameters are incomplete.
414
     * @see https://www.php.net/manual/en/function.session-set-cookie-params.php
415
     */
416 71
    private function setCookieParamsInternal()
417
    {
418 71
        $data = $this->getCookieParams();
419 71
        if (isset($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly'])) {
420 71
            if (PHP_VERSION_ID >= 70300) {
421 71
                session_set_cookie_params($data);
422
            } else {
423
                if (!empty($data['samesite'])) {
424
                    $data['path'] .= '; samesite=' . $data['samesite'];
425
                }
426 71
                session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']);
427
            }
428
        } else {
429
            throw new InvalidArgumentException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.');
430
        }
431
    }
432
433
    /**
434
     * Returns the value indicating whether cookies should be used to store session IDs.
435
     * @return bool|null the value indicating whether cookies should be used to store session IDs.
436
     * @see setUseCookies()
437
     */
438 71
    public function getUseCookies()
439
    {
440 71
        if (ini_get('session.use_cookies') === '0') {
441 1
            return false;
442 71
        } elseif (PHP_VERSION_ID >= 80400 || ini_get('session.use_only_cookies') === '1') {
443 71
            return true;
444
        }
445
446
        return null;
447
    }
448
449
    /**
450
     * Sets the value indicating whether cookies should be used to store session IDs.
451
     *
452
     * Three states are possible:
453
     *
454
     * - true: cookies and only cookies will be used to store session IDs.
455
     * - false: cookies will not be used to store session IDs.
456
     * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
457
     *
458
     * @param bool|null $value the value indicating whether cookies should be used to store session IDs.
459
     */
460 1
    public function setUseCookies($value)
461
    {
462 1
        $this->freeze();
463 1
        if ($value === false) {
464 1
            ini_set('session.use_cookies', '0');
465 1
            if (PHP_VERSION_ID < 80400) {
466 1
                ini_set('session.use_only_cookies', '0');
467
            }
468 1
        } elseif ($value === true) {
0 ignored issues
show
introduced by
The condition $value === true is always true.
Loading history...
469 1
            ini_set('session.use_cookies', '1');
470 1
            if (PHP_VERSION_ID < 80400) {
471 1
                ini_set('session.use_only_cookies', '1');
472
            }
473
        } else {
474
            ini_set('session.use_cookies', '1');
475
            if (PHP_VERSION_ID < 80400) {
476
                ini_set('session.use_only_cookies', '0');
477
            }
478
        }
479 1
        $this->unfreeze();
480
    }
481
482
    /**
483
     * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
484
     */
485 1
    public function getGCProbability()
486
    {
487 1
        return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
488
    }
489
490
    /**
491
     * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
492
     * @throws InvalidArgumentException if the value is not between 0 and 100.
493
     */
494 1
    public function setGCProbability($value)
495
    {
496 1
        $this->freeze();
497 1
        if ($value >= 0 && $value <= 100) {
498
            // percent * 21474837 / 2147483647 ≈ percent * 0.01
499 1
            ini_set('session.gc_probability', floor($value * 21474836.47));
500 1
            ini_set('session.gc_divisor', 2147483647);
501
        } else {
502
            throw new InvalidArgumentException('GCProbability must be a value between 0 and 100.');
503
        }
504 1
        $this->unfreeze();
505
    }
506
507
    /**
508
     * @return bool whether transparent sid support is enabled or not, defaults to false.
509
     */
510 1
    public function getUseTransparentSessionID()
511
    {
512 1
        if (PHP_VERSION_ID < 80400) {
513 1
            return ini_get('session.use_trans_sid') == 1;
514
        }
515
        return false;
516
    }
517
518
    /**
519
     * @param bool $value whether transparent sid support is enabled or not.
520
     */
521 1
    public function setUseTransparentSessionID($value)
522
    {
523 1
        $this->freeze();
524 1
        if (PHP_VERSION_ID < 80400) {
525 1
            ini_set('session.use_trans_sid', $value ? '1' : '0');
526
        }
527 1
        $this->unfreeze();
528
    }
529
530
    /**
531
     * @return int the number of seconds after which data will be seen as 'garbage' and cleaned up.
532
     * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
533
     */
534 4
    public function getTimeout()
535
    {
536 4
        return (int) ini_get('session.gc_maxlifetime');
537
    }
538
539
    /**
540
     * @param int $value the number of seconds after which data will be seen as 'garbage' and cleaned up
541
     */
542 1
    public function setTimeout($value)
543
    {
544 1
        $this->freeze();
545 1
        ini_set('session.gc_maxlifetime', $value);
546 1
        $this->unfreeze();
547
    }
548
549
    /**
550
     * @param bool $value Whether strict mode is enabled or not.
551
     * When `true` this setting prevents the session component to use an uninitialized session ID.
552
     * Note: Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.
553
     * Warning! Although enabling strict mode is mandatory for secure sessions, the default value of 'session.use-strict-mode' is `0`.
554
     * @see https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode
555
     * @since 2.0.38
556
     */
557 4
    public function setUseStrictMode($value)
558
    {
559 4
        if (PHP_VERSION_ID < 50502) {
560
            if ($this->getUseCustomStorage() || !$value) {
561
                self::$_useStrictModePolyfill = $value;
562
            } else {
563
                throw new InvalidConfigException('Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.');
564
            }
565
        } else {
566 4
            $this->freeze();
567 4
            ini_set('session.use_strict_mode', $value ? '1' : '0');
568 4
            $this->unfreeze();
569
        }
570
    }
571
572
    /**
573
     * @return bool Whether strict mode is enabled or not.
574
     * @see setUseStrictMode()
575
     * @since 2.0.38
576
     */
577 74
    public function getUseStrictMode()
578
    {
579 74
        if (PHP_VERSION_ID < 50502) {
580
            return self::$_useStrictModePolyfill;
581
        }
582
583 74
        return (bool)ini_get('session.use_strict_mode');
584
    }
585
586
    /**
587
     * Session open handler.
588
     * This method should be overridden if [[useCustomStorage]] returns true.
589
     * @internal Do not call this method directly.
590
     * @param string $savePath session save path
591
     * @param string $sessionName session name
592
     * @return bool whether session is opened successfully
593
     */
594 3
    public function openSession($savePath, $sessionName)
0 ignored issues
show
Unused Code introduced by
The parameter $savePath is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

594
    public function openSession(/** @scrutinizer ignore-unused */ $savePath, $sessionName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $sessionName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

594
    public function openSession($savePath, /** @scrutinizer ignore-unused */ $sessionName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
595
    {
596 3
        return true;
597
    }
598
599
    /**
600
     * Session close handler.
601
     * This method should be overridden if [[useCustomStorage]] returns true.
602
     * @internal Do not call this method directly.
603
     * @return bool whether session is closed successfully
604
     */
605 2
    public function closeSession()
606
    {
607 2
        return true;
608
    }
609
610
    /**
611
     * Session read handler.
612
     * This method should be overridden if [[useCustomStorage]] returns true.
613
     * @internal Do not call this method directly.
614
     * @param string $id session ID
615
     * @return string|false the session data, or false on failure
616
     */
617
    public function readSession($id)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

617
    public function readSession(/** @scrutinizer ignore-unused */ $id)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
618
    {
619
        return '';
620
    }
621
622
    /**
623
     * Session write handler.
624
     * This method should be overridden if [[useCustomStorage]] returns true.
625
     * @internal Do not call this method directly.
626
     * @param string $id session ID
627
     * @param string $data session data
628
     * @return bool whether session write is successful
629
     */
630
    public function writeSession($id, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

630
    public function writeSession(/** @scrutinizer ignore-unused */ $id, $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $data is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

630
    public function writeSession($id, /** @scrutinizer ignore-unused */ $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
631
    {
632
        return true;
633
    }
634
635
    /**
636
     * Session destroy handler.
637
     * This method should be overridden if [[useCustomStorage]] returns true.
638
     * @internal Do not call this method directly.
639
     * @param string $id session ID
640
     * @return bool whether session is destroyed successfully
641
     */
642 1
    public function destroySession($id)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

642
    public function destroySession(/** @scrutinizer ignore-unused */ $id)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
643
    {
644 1
        return true;
645
    }
646
647
    /**
648
     * Session GC (garbage collection) handler.
649
     * This method should be overridden if [[useCustomStorage]] returns true.
650
     * @internal Do not call this method directly.
651
     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
652
     * @return int|false the number of deleted sessions on success, or false on failure
653
     */
654
    public function gcSession($maxLifetime)
0 ignored issues
show
Unused Code introduced by
The parameter $maxLifetime is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

654
    public function gcSession(/** @scrutinizer ignore-unused */ $maxLifetime)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
655
    {
656
        return 0;
657
    }
658
659
    /**
660
     * Returns an iterator for traversing the session variables.
661
     * This method is required by the interface [[\IteratorAggregate]].
662
     * @return SessionIterator an iterator for traversing the session variables.
663
     */
664
    #[\ReturnTypeWillChange]
665
    public function getIterator()
666
    {
667
        $this->open();
668
        return new SessionIterator();
669
    }
670
671
    /**
672
     * Returns the number of items in the session.
673
     * @return int the number of session variables
674
     */
675
    public function getCount()
676
    {
677
        $this->open();
678
        return count($_SESSION);
679
    }
680
681
    /**
682
     * Returns the number of items in the session.
683
     * This method is required by [[\Countable]] interface.
684
     * @return int number of items in the session.
685
     */
686
    #[\ReturnTypeWillChange]
687
    public function count()
688
    {
689
        return $this->getCount();
690
    }
691
692
    /**
693
     * Returns the session variable value with the session variable name.
694
     * If the session variable does not exist, the `$defaultValue` will be returned.
695
     * @param string $key the session variable name
696
     * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
697
     * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
698
     */
699 72
    public function get($key, $defaultValue = null)
700
    {
701 72
        $this->open();
702 72
        return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
703
    }
704
705
    /**
706
     * Adds a session variable.
707
     * If the specified name already exists, the old value will be overwritten.
708
     * @param string $key session variable name
709
     * @param mixed $value session variable value
710
     */
711 54
    public function set($key, $value)
712
    {
713 54
        $this->open();
714 54
        $_SESSION[$key] = $value;
715
    }
716
717
    /**
718
     * Removes a session variable.
719
     * @param string $key the name of the session variable to be removed
720
     * @return mixed the removed value, null if no such session variable.
721
     */
722 45
    public function remove($key)
723
    {
724 45
        $this->open();
725 45
        if (isset($_SESSION[$key])) {
726 37
            $value = $_SESSION[$key];
727 37
            unset($_SESSION[$key]);
728
729 37
            return $value;
730
        }
731
732 45
        return null;
733
    }
734
735
    /**
736
     * Removes all session variables.
737
     */
738 22
    public function removeAll()
739
    {
740 22
        $this->open();
741 22
        foreach (array_keys($_SESSION) as $key) {
742 22
            unset($_SESSION[$key]);
743
        }
744
    }
745
746
    /**
747
     * @param mixed $key session variable name
748
     * @return bool whether there is the named session variable
749
     */
750
    public function has($key)
751
    {
752
        $this->open();
753
        return isset($_SESSION[$key]);
754
    }
755
756
    /**
757
     * Updates the counters for flash messages and removes outdated flash messages.
758
     * This method should only be called once in [[init()]].
759
     */
760 72
    protected function updateFlashCounters()
761
    {
762 72
        $counters = $this->get($this->flashParam, []);
763 72
        if (is_array($counters)) {
764 72
            foreach ($counters as $key => $count) {
765
                if ($count > 0) {
766
                    unset($counters[$key], $_SESSION[$key]);
767
                } elseif ($count == 0) {
768
                    $counters[$key]++;
769
                }
770
            }
771 72
            $_SESSION[$this->flashParam] = $counters;
772
        } else {
773
            // fix the unexpected problem that flashParam doesn't return an array
774
            unset($_SESSION[$this->flashParam]);
775
        }
776
    }
777
778
    /**
779
     * Returns a flash message.
780
     * @param string $key the key identifying the flash message
781
     * @param mixed $defaultValue value to be returned if the flash message does not exist.
782
     * @param bool $delete whether to delete this flash message right after this method is called.
783
     * If false, the flash message will be automatically deleted in the next request.
784
     * @return mixed the flash message or an array of messages if addFlash was used
785
     * @see setFlash()
786
     * @see addFlash()
787
     * @see hasFlash()
788
     * @see getAllFlashes()
789
     * @see removeFlash()
790
     */
791
    public function getFlash($key, $defaultValue = null, $delete = false)
792
    {
793
        $counters = $this->get($this->flashParam, []);
794
        if (isset($counters[$key])) {
795
            $value = $this->get($key, $defaultValue);
796
            if ($delete) {
797
                $this->removeFlash($key);
798
            } elseif ($counters[$key] < 0) {
799
                // mark for deletion in the next request
800
                $counters[$key] = 1;
801
                $_SESSION[$this->flashParam] = $counters;
802
            }
803
804
            return $value;
805
        }
806
807
        return $defaultValue;
808
    }
809
810
    /**
811
     * Returns all flash messages.
812
     *
813
     * You may use this method to display all the flash messages in a view file:
814
     *
815
     * ```php
816
     * <?php
817
     * foreach (Yii::$app->session->getAllFlashes() as $key => $message) {
818
     *     echo '<div class="alert alert-' . $key . '">' . $message . '</div>';
819
     * } ?>
820
     * ```
821
     *
822
     * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger`
823
     * as the flash message key to influence the color of the div.
824
     *
825
     * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code.
826
     *
827
     * [bootstrap alert]: https://getbootstrap.com/docs/3.4/components/#alerts
828
     *
829
     * @param bool $delete whether to delete the flash messages right after this method is called.
830
     * If false, the flash messages will be automatically deleted in the next request.
831
     * @return array flash messages (key => message or key => [message1, message2]).
832
     * @see setFlash()
833
     * @see addFlash()
834
     * @see getFlash()
835
     * @see hasFlash()
836
     * @see removeFlash()
837
     */
838
    public function getAllFlashes($delete = false)
839
    {
840
        $counters = $this->get($this->flashParam, []);
841
        $flashes = [];
842
        foreach (array_keys($counters) as $key) {
843
            if (array_key_exists($key, $_SESSION)) {
844
                $flashes[$key] = $_SESSION[$key];
845
                if ($delete) {
846
                    unset($counters[$key], $_SESSION[$key]);
847
                } elseif ($counters[$key] < 0) {
848
                    // mark for deletion in the next request
849
                    $counters[$key] = 1;
850
                }
851
            } else {
852
                unset($counters[$key]);
853
            }
854
        }
855
856
        $_SESSION[$this->flashParam] = $counters;
857
858
        return $flashes;
859
    }
860
861
    /**
862
     * Sets a flash message.
863
     * A flash message will be automatically deleted after it is accessed in a request and the deletion will happen
864
     * in the next request.
865
     * If there is already an existing flash message with the same key, it will be overwritten by the new one.
866
     * @param string $key the key identifying the flash message. Note that flash messages
867
     * and normal session variables share the same name space. If you have a normal
868
     * session variable using the same name, its value will be overwritten by this method.
869
     * @param mixed $value flash message
870
     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
871
     * it is accessed. If false, the flash message will be automatically removed after the next request,
872
     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
873
     * it is accessed.
874
     * @see getFlash()
875
     * @see addFlash()
876
     * @see removeFlash()
877
     */
878
    public function setFlash($key, $value = true, $removeAfterAccess = true)
879
    {
880
        $counters = $this->get($this->flashParam, []);
881
        $counters[$key] = $removeAfterAccess ? -1 : 0;
882
        $_SESSION[$key] = $value;
883
        $_SESSION[$this->flashParam] = $counters;
884
    }
885
886
    /**
887
     * Adds a flash message.
888
     * If there are existing flash messages with the same key, the new one will be appended to the existing message array.
889
     * @param string $key the key identifying the flash message.
890
     * @param mixed $value flash message
891
     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
892
     * it is accessed. If false, the flash message will be automatically removed after the next request,
893
     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
894
     * it is accessed.
895
     * @see getFlash()
896
     * @see setFlash()
897
     * @see removeFlash()
898
     */
899
    public function addFlash($key, $value = true, $removeAfterAccess = true)
900
    {
901
        $counters = $this->get($this->flashParam, []);
902
        $counters[$key] = $removeAfterAccess ? -1 : 0;
903
        $_SESSION[$this->flashParam] = $counters;
904
        if (empty($_SESSION[$key])) {
905
            $_SESSION[$key] = [$value];
906
        } elseif (is_array($_SESSION[$key])) {
907
            $_SESSION[$key][] = $value;
908
        } else {
909
            $_SESSION[$key] = [$_SESSION[$key], $value];
910
        }
911
    }
912
913
    /**
914
     * Removes a flash message.
915
     * @param string $key the key identifying the flash message. Note that flash messages
916
     * and normal session variables share the same name space.  If you have a normal
917
     * session variable using the same name, it will be removed by this method.
918
     * @return mixed the removed flash message. Null if the flash message does not exist.
919
     * @see getFlash()
920
     * @see setFlash()
921
     * @see addFlash()
922
     * @see removeAllFlashes()
923
     */
924
    public function removeFlash($key)
925
    {
926
        $counters = $this->get($this->flashParam, []);
927
        $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
928
        unset($counters[$key], $_SESSION[$key]);
929
        $_SESSION[$this->flashParam] = $counters;
930
931
        return $value;
932
    }
933
934
    /**
935
     * Removes all flash messages.
936
     * Note that flash messages and normal session variables share the same name space.
937
     * If you have a normal session variable using the same name, it will be removed
938
     * by this method.
939
     * @see getFlash()
940
     * @see setFlash()
941
     * @see addFlash()
942
     * @see removeFlash()
943
     */
944
    public function removeAllFlashes()
945
    {
946
        $counters = $this->get($this->flashParam, []);
947
        foreach (array_keys($counters) as $key) {
948
            unset($_SESSION[$key]);
949
        }
950
        unset($_SESSION[$this->flashParam]);
951
    }
952
953
    /**
954
     * Returns a value indicating whether there are flash messages associated with the specified key.
955
     * @param string $key key identifying the flash message type
956
     * @return bool whether any flash messages exist under specified key
957
     */
958
    public function hasFlash($key)
959
    {
960
        return $this->getFlash($key) !== null;
961
    }
962
963
    /**
964
     * This method is required by the interface [[\ArrayAccess]].
965
     * @param int|string $offset the offset to check on
966
     * @return bool
967
     */
968
    #[\ReturnTypeWillChange]
969
    public function offsetExists($offset)
970
    {
971
        $this->open();
972
973
        return isset($_SESSION[$offset]);
974
    }
975
976
    /**
977
     * This method is required by the interface [[\ArrayAccess]].
978
     * @param int|string $offset the offset to retrieve element.
979
     * @return mixed the element at the offset, null if no element is found at the offset
980
     */
981
    #[\ReturnTypeWillChange]
982
    public function offsetGet($offset)
983
    {
984
        $this->open();
985
986
        return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
987
    }
988
989
    /**
990
     * This method is required by the interface [[\ArrayAccess]].
991
     * @param int|string $offset the offset to set element
992
     * @param mixed $item the element value
993
     */
994
    #[\ReturnTypeWillChange]
995
    public function offsetSet($offset, $item)
996
    {
997
        $this->open();
998
        $_SESSION[$offset] = $item;
999
    }
1000
1001
    /**
1002
     * This method is required by the interface [[\ArrayAccess]].
1003
     * @param int|string $offset the offset to unset element
1004
     */
1005
    #[\ReturnTypeWillChange]
1006
    public function offsetUnset($offset)
1007
    {
1008
        $this->open();
1009
        unset($_SESSION[$offset]);
1010
    }
1011
1012
    /**
1013
     * If session is started it's not possible to edit session ini settings. In PHP7.2+ it throws exception.
1014
     * This function saves session data to temporary variable and stop session.
1015
     * @since 2.0.14
1016
     */
1017 7
    protected function freeze()
1018
    {
1019 7
        if ($this->getIsActive()) {
1020 4
            if (isset($_SESSION)) {
1021 4
                $this->_frozenSessionData = $_SESSION;
1022
            }
1023 4
            $this->close();
1024 4
            Yii::info('Session frozen', __METHOD__);
1025
        }
1026
    }
1027
1028
    /**
1029
     * Starts session and restores data from temporary variable
1030
     * @since 2.0.14
1031
     */
1032 7
    protected function unfreeze()
1033
    {
1034 7
        if (null !== $this->_frozenSessionData) {
1035 4
            YII_DEBUG ? session_start() : @session_start();
1036
1037 4
            if ($this->getIsActive()) {
1038 4
                Yii::info('Session unfrozen', __METHOD__);
1039
            } else {
1040
                $error = error_get_last();
1041
                $message = isset($error['message']) ? $error['message'] : 'Failed to unfreeze session.';
1042
                Yii::error($message, __METHOD__);
1043
            }
1044
1045 4
            $_SESSION = $this->_frozenSessionData;
1046 4
            $this->_frozenSessionData = null;
1047
        }
1048
    }
1049
1050
    /**
1051
     * Set cache limiter
1052
     *
1053
     * @param string $cacheLimiter
1054
     * @since 2.0.14
1055
     */
1056 1
    public function setCacheLimiter($cacheLimiter)
1057
    {
1058 1
        $this->freeze();
1059 1
        session_cache_limiter($cacheLimiter);
1060 1
        $this->unfreeze();
1061
    }
1062
1063
    /**
1064
     * Returns current cache limiter
1065
     *
1066
     * @return string current cache limiter
1067
     * @since 2.0.14
1068
     */
1069
    public function getCacheLimiter()
1070
    {
1071
        return session_cache_limiter();
1072
    }
1073
}
1074