PdoSessionHandler
extends AbstractSessionHandler
in package
Session handler using a PDO connection to read and write data.
It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements different locking strategies to handle concurrent access to the same session. Locking is necessary to prevent loss of data due to race conditions and to keep the session data consistent between read() and write(). With locking, requests for the same session will wait until the other one finished writing. For this reason it's best practice to close a session as early as possible to improve concurrency. PHPs internal files session handler also implements locking.
Attention: Since SQLite does not support row level locks but locks the whole database, it means only one session can be accessed at a time. Even different sessions would wait for another to finish. So saving session in SQLite should only be considered for development or prototypes.
Session data is a binary string that can contain non-printable characters like the null byte. For this reason it must be saved in a binary column in the database like BLOB in MySQL. Saving it in a character column could corrupt the data. You can use createTable() to initialize a correctly defined table.
Tags
Table of Contents
- LOCK_ADVISORY = 1
- Creates an application-level lock on a session. The disadvantage is that the lock is not enforced by the database and thus other, unaware parts of the application could still concurrently modify the session. The advantage is it does not require a transaction.
- LOCK_NONE = 0
- No locking is done. This means sessions are prone to loss of data due to race conditions of concurrent requests to the same session. The last session write will win in this case. It might be useful when you implement your own logic to deal with this like an optimistic approach.
- LOCK_TRANSACTIONAL = 2
- Issues a real row lock. Since it uses a transaction between opening and closing a session, you have to be careful when you use same database connection that you also use for your application logic. This mode is the default because it's the only reliable solution across DBMSs.
- MAX_LIFETIME = 315576000
- $connectionOptions : array<string|int, mixed>
- Connection options when lazy-connect.
- $dataCol : string
- $driver : string|null
- $dsn : string|false|null
- DSN string or null for session.save_path or false when lazy connection disabled.
- $gcCalled : bool
- Whether gc() has been called.
- $idCol : string
- $igbinaryEmptyData : mixed
- $inTransaction : bool
- Whether a transaction is active.
- $lifetimeCol : string
- $lockMode : int
- The strategy for locking, see constants.
- $newSessionId : mixed
- $password : string|null
- Password when lazy-connect.
- $pdo : PDO|null
- $prefetchData : mixed
- $prefetchId : mixed
- $sessionExpired : bool
- True when the current session exists but expired according to session.gc_maxlifetime.
- $sessionName : mixed
- $table : string
- $timeCol : string
- $unlockStatements : array<string|int, PDOStatement>
- It's an array to support multiple reads before closing which is manual, non-standard usage.
- $username : string|null
- Username when lazy-connect.
- __construct() : mixed
- You can either pass an existing database connection as PDO instance or pass a DSN string that will be used to lazy-connect to the database when the session is actually used. Furthermore it's possible to pass null which will then use the session.save_path ini setting as PDO DSN parameter.
- close() : bool
- createTable() : mixed
- Creates the table to store sessions which can be called once for setup.
- destroy() : bool
- gc() : int|false
- isSessionExpired() : bool
- Returns true when the current session exists but expired according to session.gc_maxlifetime.
- open() : bool
- read() : string
- updateTimestamp() : bool
- validateId() : bool
- write() : bool
- doDestroy() : bool
- doRead() : string
- Reads the session data in respect to the different locking strategies.
- doWrite() : bool
- getConnection() : PDO
- Return a PDO instance.
- beginTransaction() : void
- Helper method to begin a transaction.
- buildDsnFromUrl() : string
- Builds a PDO DSN from a URL-like connection string.
- commit() : void
- Helper method to commit a transaction.
- connect() : void
- Lazy-connects to the database.
- convertStringToInt() : int
- Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer.
- doAdvisoryLock() : PDOStatement
- Executes an application-level lock on the database.
- getInsertStatement() : PDOStatement
- Returns an insert statement supported by the database for writing session data.
- getMergeStatement() : PDOStatement|null
- Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
- getSelectSql() : string
- Return a locking or nonlocking SQL query to read session information.
- getUpdateStatement() : PDOStatement
- Returns an update statement supported by the database for writing session data.
- rollback() : void
- Helper method to rollback a transaction.
Constants
LOCK_ADVISORY
Creates an application-level lock on a session. The disadvantage is that the lock is not enforced by the database and thus other, unaware parts of the application could still concurrently modify the session. The advantage is it does not require a transaction.
public
mixed
LOCK_ADVISORY
= 1
This mode is not available for SQLite and not yet implemented for oci and sqlsrv.
LOCK_NONE
No locking is done. This means sessions are prone to loss of data due to race conditions of concurrent requests to the same session. The last session write will win in this case. It might be useful when you implement your own logic to deal with this like an optimistic approach.
public
mixed
LOCK_NONE
= 0
LOCK_TRANSACTIONAL
Issues a real row lock. Since it uses a transaction between opening and closing a session, you have to be careful when you use same database connection that you also use for your application logic. This mode is the default because it's the only reliable solution across DBMSs.
public
mixed
LOCK_TRANSACTIONAL
= 2
MAX_LIFETIME
private
mixed
MAX_LIFETIME
= 315576000
Properties
$connectionOptions
Connection options when lazy-connect.
private
array<string|int, mixed>
$connectionOptions
= []
$dataCol
private
string
$dataCol
= 'sess_data'
$driver
private
string|null
$driver
$dsn
DSN string or null for session.save_path or false when lazy connection disabled.
private
string|false|null
$dsn
= false
$gcCalled
Whether gc() has been called.
private
bool
$gcCalled
= false
$idCol
private
string
$idCol
= 'sess_id'
$igbinaryEmptyData
private
mixed
$igbinaryEmptyData
$inTransaction
Whether a transaction is active.
private
bool
$inTransaction
= false
$lifetimeCol
private
string
$lifetimeCol
= 'sess_lifetime'
$lockMode
The strategy for locking, see constants.
private
int
$lockMode
= self::LOCK_TRANSACTIONAL
$newSessionId
private
mixed
$newSessionId
$password
Password when lazy-connect.
private
string|null
$password
= null
$pdo
private
PDO|null
$pdo
PDO instance or null when not connected yet
$prefetchData
private
mixed
$prefetchData
$prefetchId
private
mixed
$prefetchId
$sessionExpired
True when the current session exists but expired according to session.gc_maxlifetime.
private
bool
$sessionExpired
= false
$sessionName
private
mixed
$sessionName
$table
private
string
$table
= 'sessions'
$timeCol
private
string
$timeCol
= 'sess_time'
$unlockStatements
It's an array to support multiple reads before closing which is manual, non-standard usage.
private
array<string|int, PDOStatement>
$unlockStatements
= []
An array of statements to release advisory locks
$username
Username when lazy-connect.
private
string|null
$username
= null
Methods
__construct()
You can either pass an existing database connection as PDO instance or pass a DSN string that will be used to lazy-connect to the database when the session is actually used. Furthermore it's possible to pass null which will then use the session.save_path ini setting as PDO DSN parameter.
public
__construct([PDO|string|null $pdoOrDsn = null ][, array<string|int, mixed> $options = [] ]) : mixed
List of available options:
- db_table: The name of the table [default: sessions]
- db_id_col: The column where to store the session id [default: sess_id]
- db_data_col: The column where to store the session data [default: sess_data]
- db_lifetime_col: The column where to store the lifetime [default: sess_lifetime]
- db_time_col: The column where to store the timestamp [default: sess_time]
- db_username: The username when lazy-connect [default: '']
- db_password: The password when lazy-connect [default: '']
- db_connection_options: An array of driver-specific connection options [default: []]
- lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
Parameters
- $pdoOrDsn : PDO|string|null = null
-
A \PDO instance or DSN string or URL string or null
- $options : array<string|int, mixed> = []
Tags
Return values
mixed —close()
public
close() : bool
Return values
bool —createTable()
Creates the table to store sessions which can be called once for setup.
public
createTable() : mixed
Session ID is saved in a column of maximum length 128 because that is enough even for a 512 bit configured session.hash_function like Whirlpool. Session data is saved in a BLOB. One could also use a shorter inlined varbinary column if one was sure the data fits into it.
Tags
Return values
mixed —destroy()
public
destroy(mixed $sessionId) : bool
Parameters
- $sessionId : mixed
Return values
bool —gc()
public
gc(mixed $maxlifetime) : int|false
Parameters
- $maxlifetime : mixed
Return values
int|false —isSessionExpired()
Returns true when the current session exists but expired according to session.gc_maxlifetime.
public
isSessionExpired() : bool
Can be used to distinguish between a new session and one that expired due to inactivity.
Return values
bool —open()
public
open(mixed $savePath, mixed $sessionName) : bool
Parameters
- $savePath : mixed
- $sessionName : mixed
Return values
bool —read()
public
read(mixed $sessionId) : string
Parameters
- $sessionId : mixed
Return values
string —updateTimestamp()
public
updateTimestamp(mixed $sessionId, mixed $data) : bool
Parameters
- $sessionId : mixed
- $data : mixed
Return values
bool —validateId()
public
validateId(mixed $sessionId) : bool
Parameters
- $sessionId : mixed
Return values
bool —write()
public
write(mixed $sessionId, mixed $data) : bool
Parameters
- $sessionId : mixed
- $data : mixed
Return values
bool —doDestroy()
protected
doDestroy(string $sessionId) : bool
Parameters
- $sessionId : string
Return values
bool —doRead()
Reads the session data in respect to the different locking strategies.
protected
doRead(string $sessionId) : string
We need to make sure we do not return session data that is already considered garbage according to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes.
Parameters
- $sessionId : string
Return values
string —doWrite()
protected
doWrite(string $sessionId, string $data) : bool
Parameters
- $sessionId : string
- $data : string
Return values
bool —getConnection()
Return a PDO instance.
protected
getConnection() : PDO
Return values
PDO —beginTransaction()
Helper method to begin a transaction.
private
beginTransaction() : void
Since SQLite does not support row level locks, we have to acquire a reserved lock on the database immediately. Because of https://bugs.php.net/42766 we have to create such a transaction manually which also means we cannot use PDO::commit or PDO::rollback or PDO::inTransaction for SQLite.
Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . So we change it to READ COMMITTED.
Return values
void —buildDsnFromUrl()
Builds a PDO DSN from a URL-like connection string.
private
buildDsnFromUrl(string $dsnOrUrl) : string
Parameters
- $dsnOrUrl : string
Tags
Return values
string —commit()
Helper method to commit a transaction.
private
commit() : void
Return values
void —connect()
Lazy-connects to the database.
private
connect(string $dsn) : void
Parameters
- $dsn : string
Return values
void —convertStringToInt()
Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer.
private
convertStringToInt(string $string) : int
Keep in mind, PHP integers are signed.
Parameters
- $string : string
Return values
int —doAdvisoryLock()
Executes an application-level lock on the database.
private
doAdvisoryLock(string $sessionId) : PDOStatement
Parameters
- $sessionId : string
Tags
Return values
PDOStatement —The statement that needs to be executed later to release the lock
getInsertStatement()
Returns an insert statement supported by the database for writing session data.
private
getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime) : PDOStatement
Parameters
- $sessionId : string
- $sessionData : string
- $maxlifetime : int
Return values
PDOStatement —getMergeStatement()
Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
private
getMergeStatement(string $sessionId, string $data, int $maxlifetime) : PDOStatement|null
Parameters
- $sessionId : string
- $data : string
- $maxlifetime : int
Return values
PDOStatement|null —getSelectSql()
Return a locking or nonlocking SQL query to read session information.
private
getSelectSql() : string
Tags
Return values
string —getUpdateStatement()
Returns an update statement supported by the database for writing session data.
private
getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime) : PDOStatement
Parameters
- $sessionId : string
- $sessionData : string
- $maxlifetime : int
Return values
PDOStatement —rollback()
Helper method to rollback a transaction.
private
rollback() : void