$_engine
$_engine : \SessionHandlerInterface
The Session handler instance used as an engine for persisting the session data.
This class is a wrapper for the native PHP session functions. It provides several defaults for the most common session configuration via external handlers and helps with using session in cli without any warnings.
Sessions can be created from the defaults using Session::create()
or you can get
an instance of a new session by just instantiating this class and passing the complete
options you want to use.
When specific options are omitted, this class will take its defaults from the configuration
values from the session.*
directives in php.ini. This class will also alter such
directives when configuration values are provided.
create(array $sessionConfig = array()) : static
Returns a new instance of a session after building a configuration bundle for it.
This function allows an options array which will be used for configuring the session
and the handler to be used. The most important key in the configuration array is
defaults
, which indicates the set of configurations to inherit from, the possible
defaults are:
config
key with the name of an already configured Cache engine.sessions
or a model
key in the configuration
to indicate which Table object to use.The full list of options follows:
array | $sessionConfig | Session config. |
__construct(array $config = array())
Constructor.
session.cookie_path
php.ini config. Defaults to base path of app.class
key. To be used as the session
engine for persisting data. The rest of the keys in the array will be passed as
the configuration array for the engine. You can set the class
key to an already
instantiated session handler object.array | $config | The Configuration to apply to this session object |
engine(string|\SessionHandlerInterface|null $class = null, array $options = array()) : \SessionHandlerInterface|null
Sets the session handler instance to use for this session.
If a string is passed for the first argument, it will be treated as the class name and the second argument will be passed as the first argument in the constructor.
If an instance of a SessionHandlerInterface is provided as the first argument, the handler will be set to it.
If no arguments are passed it will return the currently configured handler instance or null if none exists.
string|\SessionHandlerInterface|null | $class | The session handler to use |
array | $options | the options to pass to the SessionHandler constructor |
options(array $options) : void
Calls ini_set for each of the keys in `$options` and set them to the respective value in the passed array.
$session->options(['session.use_cookies' => 1]);
array | $options | Ini options to set. |
if any directive could not be set
read(string|null $name = null) : string|array|null
Returns given session variable, or all of them, if no parameters given.
string|null | $name | The name of the session variable (or a path as sent to Hash.extract) |
The value of the session variable, null if session not available, session not started, or provided name not found in the session.
consume(string $name) : mixed
Reads and deletes a variable from session.
string | $name | The key to read and remove (or a path as sent to Hash.extract). |
The value of the session variable, null if session not available, session not started, or provided name not found in the session.
id(string|null $id = null) : string
Returns the session id.
Calling this method will not auto start the session. You might have to manually assert a started session.
Passing an id into it, you can also replace the session id if the session has not already been started. Note that depending on the session handler, not all characters are allowed within the session id. For example, the file session handler only allows characters in the range a-z A-Z 0-9 , (comma) and - (minus).
string|null | $id | Id to replace the current session id |
Session id
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Http;
use Cake\Core\App;
use Cake\Utility\Hash;
use InvalidArgumentException;
use RuntimeException;
use SessionHandlerInterface;
/**
* This class is a wrapper for the native PHP session functions. It provides
* several defaults for the most common session configuration
* via external handlers and helps with using session in cli without any warnings.
*
* Sessions can be created from the defaults using `Session::create()` or you can get
* an instance of a new session by just instantiating this class and passing the complete
* options you want to use.
*
* When specific options are omitted, this class will take its defaults from the configuration
* values from the `session.*` directives in php.ini. This class will also alter such
* directives when configuration values are provided.
*/
class Session
{
/**
* The Session handler instance used as an engine for persisting the session data.
*
* @var \SessionHandlerInterface
*/
protected $_engine;
/**
* Indicates whether the sessions has already started
*
* @var bool
*/
protected $_started;
/**
* The time in seconds the session will be valid for
*
* @var int
*/
protected $_lifetime;
/**
* Whether this session is running under a CLI environment
*
* @var bool
*/
protected $_isCLI = false;
/**
* Returns a new instance of a session after building a configuration bundle for it.
* This function allows an options array which will be used for configuring the session
* and the handler to be used. The most important key in the configuration array is
* `defaults`, which indicates the set of configurations to inherit from, the possible
* defaults are:
*
* - php: just use session as configured in php.ini
* - cache: Use the CakePHP caching system as an storage for the session, you will need
* to pass the `config` key with the name of an already configured Cache engine.
* - database: Use the CakePHP ORM to persist and manage sessions. By default this requires
* a table in your database named `sessions` or a `model` key in the configuration
* to indicate which Table object to use.
* - cake: Use files for storing the sessions, but let CakePHP manage them and decide
* where to store them.
*
* The full list of options follows:
*
* - defaults: either 'php', 'database', 'cache' or 'cake' as explained above.
* - handler: An array containing the handler configuration
* - ini: A list of php.ini directives to set before the session starts.
* - timeout: The time in minutes the session should stay active
*
* @param array $sessionConfig Session config.
* @return static
* @see \Cake\Http\Session::__construct()
*/
public static function create($sessionConfig = [])
{
if (isset($sessionConfig['defaults'])) {
$defaults = static::_defaultConfig($sessionConfig['defaults']);
if ($defaults) {
$sessionConfig = Hash::merge($defaults, $sessionConfig);
}
}
if (!isset($sessionConfig['ini']['session.cookie_secure']) && env('HTTPS') && ini_get('session.cookie_secure') != 1) {
$sessionConfig['ini']['session.cookie_secure'] = 1;
}
if (!isset($sessionConfig['ini']['session.name'])) {
$sessionConfig['ini']['session.name'] = $sessionConfig['cookie'];
}
if (!empty($sessionConfig['handler'])) {
$sessionConfig['ini']['session.save_handler'] = 'user';
}
// In PHP7.2.0+ session.save_handler can't be set to user by the user.
// https://github.com/php/php-src/commit/a93a51c3bf4ea1638ce0adc4a899cb93531b9f0d
if (version_compare(PHP_VERSION, '7.2.0', '>=')) {
unset($sessionConfig['ini']['session.save_handler']);
}
if (!isset($sessionConfig['ini']['session.use_strict_mode']) && ini_get('session.use_strict_mode') != 1) {
$sessionConfig['ini']['session.use_strict_mode'] = 1;
}
if (!isset($sessionConfig['ini']['session.cookie_httponly']) && ini_get('session.cookie_httponly') != 1) {
$sessionConfig['ini']['session.cookie_httponly'] = 1;
}
return new static($sessionConfig);
}
/**
* Get one of the prebaked default session configurations.
*
* @param string $name Config name.
* @return bool|array
*/
protected static function _defaultConfig($name)
{
$defaults = [
'php' => [
'cookie' => 'CAKEPHP',
'ini' => [
'session.use_trans_sid' => 0,
]
],
'cake' => [
'cookie' => 'CAKEPHP',
'ini' => [
'session.use_trans_sid' => 0,
'session.serialize_handler' => 'php',
'session.use_cookies' => 1,
'session.save_path' => TMP . 'sessions',
'session.save_handler' => 'files'
]
],
'cache' => [
'cookie' => 'CAKEPHP',
'ini' => [
'session.use_trans_sid' => 0,
'session.use_cookies' => 1,
'session.save_handler' => 'user',
],
'handler' => [
'engine' => 'CacheSession',
'config' => 'default'
]
],
'database' => [
'cookie' => 'CAKEPHP',
'ini' => [
'session.use_trans_sid' => 0,
'session.use_cookies' => 1,
'session.save_handler' => 'user',
'session.serialize_handler' => 'php',
],
'handler' => [
'engine' => 'DatabaseSession'
]
]
];
if (isset($defaults[$name])) {
return $defaults[$name];
}
return false;
}
/**
* Constructor.
*
* ### Configuration:
*
* - timeout: The time in minutes the session should be valid for.
* - cookiePath: The url path for which session cookie is set. Maps to the
* `session.cookie_path` php.ini config. Defaults to base path of app.
* - ini: A list of php.ini directives to change before the session start.
* - handler: An array containing at least the `class` key. To be used as the session
* engine for persisting data. The rest of the keys in the array will be passed as
* the configuration array for the engine. You can set the `class` key to an already
* instantiated session handler object.
*
* @param array $config The Configuration to apply to this session object
*/
public function __construct(array $config = [])
{
if (isset($config['timeout'])) {
$config['ini']['session.gc_maxlifetime'] = 60 * $config['timeout'];
}
if (!empty($config['cookie'])) {
$config['ini']['session.name'] = $config['cookie'];
}
if (!isset($config['ini']['session.cookie_path'])) {
$cookiePath = empty($config['cookiePath']) ? '/' : $config['cookiePath'];
$config['ini']['session.cookie_path'] = $cookiePath;
}
if (!empty($config['ini']) && is_array($config['ini'])) {
$this->options($config['ini']);
}
if (!empty($config['handler']['engine'])) {
$class = $config['handler']['engine'];
unset($config['handler']['engine']);
$this->engine($class, $config['handler']);
}
$this->_lifetime = (int)ini_get('session.gc_maxlifetime');
$this->_isCLI = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg');
session_register_shutdown();
}
/**
* Sets the session handler instance to use for this session.
* If a string is passed for the first argument, it will be treated as the
* class name and the second argument will be passed as the first argument
* in the constructor.
*
* If an instance of a SessionHandlerInterface is provided as the first argument,
* the handler will be set to it.
*
* If no arguments are passed it will return the currently configured handler instance
* or null if none exists.
*
* @param string|\SessionHandlerInterface|null $class The session handler to use
* @param array $options the options to pass to the SessionHandler constructor
* @return \SessionHandlerInterface|null
* @throws \InvalidArgumentException
*/
public function engine($class = null, array $options = [])
{
if ($class === null) {
return $this->_engine;
}
if ($class instanceof SessionHandlerInterface) {
return $this->setEngine($class);
}
$className = App::className($class, 'Http/Session');
if (!$className) {
$className = App::className($class, 'Network/Session');
if ($className) {
deprecationWarning('Session adapters should be moved to the Http/Session namespace.');
}
}
if (!$className) {
throw new InvalidArgumentException(
sprintf('The class "%s" does not exist and cannot be used as a session engine', $class)
);
}
$handler = new $className($options);
if (!($handler instanceof SessionHandlerInterface)) {
throw new InvalidArgumentException(
'The chosen SessionHandler does not implement SessionHandlerInterface, it cannot be used as an engine.'
);
}
return $this->setEngine($handler);
}
/**
* Set the engine property and update the session handler in PHP.
*
* @param \SessionHandlerInterface $handler The handler to set
* @return \SessionHandlerInterface
*/
protected function setEngine(SessionHandlerInterface $handler)
{
if (!headers_sent() && session_status() !== \PHP_SESSION_ACTIVE) {
session_set_save_handler($handler, false);
}
return $this->_engine = $handler;
}
/**
* Calls ini_set for each of the keys in `$options` and set them
* to the respective value in the passed array.
*
* ### Example:
*
* ```
* $session->options(['session.use_cookies' => 1]);
* ```
*
* @param array $options Ini options to set.
* @return void
* @throws \RuntimeException if any directive could not be set
*/
public function options(array $options)
{
if (session_status() === \PHP_SESSION_ACTIVE || headers_sent()) {
return;
}
foreach ($options as $setting => $value) {
if (ini_set($setting, (string)$value) === false) {
throw new RuntimeException(
sprintf('Unable to configure the session, setting %s failed.', $setting)
);
}
}
}
/**
* Starts the Session.
*
* @return bool True if session was started
* @throws \RuntimeException if the session was already started
*/
public function start()
{
if ($this->_started) {
return true;
}
if ($this->_isCLI) {
$_SESSION = [];
$this->id('cli');
return $this->_started = true;
}
if (session_status() === \PHP_SESSION_ACTIVE) {
throw new RuntimeException('Session was already started');
}
if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
return false;
}
if (!session_start()) {
throw new RuntimeException('Could not start the session');
}
$this->_started = true;
if ($this->_timedOut()) {
$this->destroy();
return $this->start();
}
return $this->_started;
}
/**
* Write data and close the session
*
* @return bool True if session was started
*/
public function close()
{
if (!$this->_started) {
return true;
}
if (!session_write_close()) {
throw new RuntimeException('Could not close the session');
}
$this->_started = false;
return true;
}
/**
* Determine if Session has already been started.
*
* @return bool True if session has been started.
*/
public function started()
{
return $this->_started || session_status() === \PHP_SESSION_ACTIVE;
}
/**
* Returns true if given variable name is set in session.
*
* @param string|null $name Variable name to check for
* @return bool True if variable is there
*/
public function check($name = null)
{
if ($this->_hasSession() && !$this->started()) {
$this->start();
}
if (!isset($_SESSION)) {
return false;
}
return Hash::get($_SESSION, $name) !== null;
}
/**
* Returns given session variable, or all of them, if no parameters given.
*
* @param string|null $name The name of the session variable (or a path as sent to Hash.extract)
* @return string|array|null The value of the session variable, null if session not available,
* session not started, or provided name not found in the session.
*/
public function read($name = null)
{
if ($this->_hasSession() && !$this->started()) {
$this->start();
}
if (!isset($_SESSION)) {
return null;
}
if ($name === null) {
return isset($_SESSION) ? $_SESSION : [];
}
return Hash::get($_SESSION, $name);
}
/**
* Reads and deletes a variable from session.
*
* @param string $name The key to read and remove (or a path as sent to Hash.extract).
* @return mixed The value of the session variable, null if session not available,
* session not started, or provided name not found in the session.
*/
public function consume($name)
{
if (empty($name)) {
return null;
}
$value = $this->read($name);
if ($value !== null) {
$this->_overwrite($_SESSION, Hash::remove($_SESSION, $name));
}
return $value;
}
/**
* Writes value to given session variable name.
*
* @param string|array $name Name of variable
* @param mixed $value Value to write
* @return void
*/
public function write($name, $value = null)
{
if (!$this->started()) {
$this->start();
}
$write = $name;
if (!is_array($name)) {
$write = [$name => $value];
}
$data = isset($_SESSION) ? $_SESSION : [];
foreach ($write as $key => $val) {
$data = Hash::insert($data, $key, $val);
}
$this->_overwrite($_SESSION, $data);
}
/**
* Returns the session id.
* Calling this method will not auto start the session. You might have to manually
* assert a started session.
*
* Passing an id into it, you can also replace the session id if the session
* has not already been started.
* Note that depending on the session handler, not all characters are allowed
* within the session id. For example, the file session handler only allows
* characters in the range a-z A-Z 0-9 , (comma) and - (minus).
*
* @param string|null $id Id to replace the current session id
* @return string Session id
*/
public function id($id = null)
{
if ($id !== null && !headers_sent()) {
session_id($id);
}
return session_id();
}
/**
* Removes a variable from session.
*
* @param string $name Session variable to remove
* @return void
*/
public function delete($name)
{
if ($this->check($name)) {
$this->_overwrite($_SESSION, Hash::remove($_SESSION, $name));
}
}
/**
* Used to write new data to _SESSION, since PHP doesn't like us setting the _SESSION var itself.
*
* @param array $old Set of old variables => values
* @param array $new New set of variable => value
* @return void
*/
protected function _overwrite(&$old, $new)
{
if (!empty($old)) {
foreach ($old as $key => $var) {
if (!isset($new[$key])) {
unset($old[$key]);
}
}
}
foreach ($new as $key => $var) {
$old[$key] = $var;
}
}
/**
* Helper method to destroy invalid sessions.
*
* @return void
*/
public function destroy()
{
if ($this->_hasSession() && !$this->started()) {
$this->start();
}
if (!$this->_isCLI && session_status() === \PHP_SESSION_ACTIVE) {
session_destroy();
}
$_SESSION = [];
$this->_started = false;
}
/**
* Clears the session.
*
* Optionally it also clears the session id and renews the session.
*
* @param bool $renew If session should be renewed, as well. Defaults to false.
* @return void
*/
public function clear($renew = false)
{
$_SESSION = [];
if ($renew) {
$this->renew();
}
}
/**
* Returns whether a session exists
*
* @return bool
*/
protected function _hasSession()
{
return !ini_get('session.use_cookies')
|| isset($_COOKIE[session_name()])
|| $this->_isCLI
|| (ini_get('session.use_trans_sid') && isset($_GET[session_name()]));
}
/**
* Restarts this session.
*
* @return void
*/
public function renew()
{
if (!$this->_hasSession() || $this->_isCLI) {
return;
}
$this->start();
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
if (session_id()) {
session_regenerate_id(true);
}
}
/**
* Returns true if the session is no longer valid because the last time it was
* accessed was after the configured timeout.
*
* @return bool
*/
protected function _timedOut()
{
$time = $this->read('Config.time');
$result = false;
$checkTime = $time !== null && $this->_lifetime > 0;
if ($checkTime && (time() - (int)$time > $this->_lifetime)) {
$result = true;
}
$this->write('Config.time', time());
return $result;
}
}