Reputation: 1959
I've written a custom session handler class which works for the most part, until I try to log my users out and destroy the session, then I keep getting the following error:
ErrorException: 0
session_write_close(): Failed to write session data using user defined save handler. (session.save_path: D:\xampp\tmp)
I am running PHP Version 7.3.1 on XAMPP.
This is my custom class
class CornerstoneSessionHandler implements SessionHandlerInterface {
/**
* Construct the Session
* No parameters required, nothing will be returned
*/
public function __construct() {
// Set the handler to overide SESSION
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
// Set the shutdown function
register_shutdown_function('session_write_close');
/** Define and initialise the Session Handler */
session_start();
}
/**
* Set the open callback
*
* @param string $savePath
* @param string $sessionName
*
* @return bool return value should be true for success or false for failure
*/
public function open($savePath, $sessionName) {
// Make the $csdb global accessible
global $csdb;
// Check that the DB connection is set
return ((!empty($csdb)) && $csdb->isConnected() != 1) ? FALSE : TRUE ;
}
/**
* Set the close callback
*
* @return bool return value can only be true for success
*/
public function close() {
return TRUE;
}
/**
* Set the read callback
*
* @param string $sessionID
*
* @return string return value should be the session data or an empty string
*/
public function read($sessionID) {
// Make the $csdb global accessible
global $csdb;
// Get the session from the database
$csdb->query_prepared("SELECT session_data FROM cs_session WHERE session_id=?", [$sessionID]);
// If results returned, continue
if($csdb->getNum_Rows() > 0) {
// Get the data
$result = $csdb->get_row(NULL);
return $result->session_data;
} else { // Else return an empty string
return '';
}
}
/**
* Set the write callback
*
* @param string $sessionID
* @param string $data
*
* @return bool return value should be true for success or false for failure
*/
public function write($sessionID, $data) {
// Make the $csdb global accessible
global $csdb;
// Set the time stamp
$access_dtm = new \DateTime();
// Replace the data
$csdb->query_prepared('REPLACE INTO cs_session(session_id, session_ip_address, session_data , session_access_dtm) VALUES (?, ?, ?, ?)', [$sessionID, $_SERVER['REMOTE_ADDR'], $data, $access_dtm->format('Y-m-d H:i:s')]);
// If modified a success, return true
if($csdb->getNum_Rows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
/**
* Set the destroy callback
*
* @param string $sessionID
*
* @return bool return value should be true for success or false for failure
*/
public function destroy($sessionID) {
// Make the $csdb global accessible
global $csdb;
// Delete the session from the database
$csdb->delete('cs_session', where(eq('session_id', $sessionID)));
// If results returned, return true
if($csdb->affectedRows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
/**
* Set the garbage collector callback
*
* @param string $lifetime
*
* @return bool return value should be true for success or false for failure
*/
public function gc($lifetime) {
// Make the $csdb global accessible
global $csdb;
// Set the date calculation
$expiredTime = new \DateTime();
$expiredTime->modify('-' . $lifetime . ' seconds');
// Get the session from the database
$csdb->delete('cs_session', where(lt('session_access_dtm', $expiredTime->format('Y-m-d H:i:s'))));
// If results deleted, return true
if($csdb->affectedRows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
}
I am using ezSQL as my database handler.
This is the code in the top of all my pages
/**
* Set session data so that logins work properly over all browsers
* This should fix login errors some users can face
* More info can be found at {@link https://www.php.net/manual/en/session.security.ini.php the php user manual}
*/
# PREVENTING SESSION HIJACKING
# Prevents javascript XSS attacks aimed to steal the session ID
ini_set('session.cookie_httponly', 1);
# Make sure the cookie lifetime is set to '0'
ini_set('session.cookie_lifetime', 0);
# Adds entropy into the randomization of the session ID, as PHP's random number
# generator has some known flaws
ini_set('session.entropy_file', '/dev/urandom');
# Uses a strong hash
ini_set('session.hash_function', 'whirlpool');
# Set the session save location (best for shared servers)
# Uncomment out the next line if you would like to set a custom path and haven't already set the value in your `php.ini` file.
# ini_set('session.save_path',realpath(ABSPATH . 'tmp' . DIRECTORY_SEPARATOR));
# Note: The folder referenced above must exist for it to work.
# Set the session garbage collection lifetime to custom defined minutes (PHP default is 24 minutes)
ini_set('session.gc_maxlifetime', (int)get_option("session_expire") * MINUTE_IN_SECONDS);
# Enable session garbage collection with a 1% chance of
# running on each session_start()
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 100);
# Uses a secure connection (HTTPS) if possible
# Allow cookies to be sent over insecure connections if not an HTTPS site
(SITE_HTTPS) ? ini_set('session.cookie_secure', 1) : ini_set('session.cookie_secure', false);
# PREVENTING SESSION FIXATION
# Session ID cannot be passed through URLs
# so only use cookies to store the session id on the client side
ini_set('session.use_only_cookies', 1);
# Set a custom session name
session_name('CSSESSID');
# Load the session class
new CornerstoneSessionHandler();
# Start the session if it's not already started
if (session_status() == PHP_SESSION_NONE) {session_start();}
The weird thing is, even though it's giving me that error, if I check the database the data is being updated no worries.
This is my logout code
/**
* Logout the user
*
* @return bool
*/
public static function logoutUser() {
// Make the $fear global accessible
global $fear; // Direct access check
// Check if session set (just in case) and start if it isn't
if(session_id() == '') {session_start();}
// Delete the $_SESSION data set in `authenticateUser()`
unset($_SESSION['HTTP_USER_AGENT']);
unset($_SESSION['_cs-uid']);
unset($_SESSION['_cs-ue']);
unset($_SESSION['_cs-ul']);
unset($_SESSION['_cs-un']);
/**
* Get the "ext.auth.php" file and run `clearCustomAuth()` function
* to clear any custom set $_SESSION items
*/
require_once( get_lib_path('ext.auth.php') );
clearCustomAuth();
// Regenerate a new session ID just to be sure
session_regenerate_id();
// Destroy the session
session_destroy();
// Check if the $_COOKIE data if set
if(isset( $_COOKIE['_cs-ti'] ) && !empty( $_COOKIE['_cs-ti'] )) {
// Delete the cookie token from the database
if(!self::deleteAuthCookie($_COOKIE['_cs-ti'])) {
// Return false if cookie couldn't be deleted
return false;
}
}
// Return true if run all the way
return true;
}
I've tried shifting around the session_regenerate_id()
but if I put it after the session_destroy()
it says there's no session to regenerate, but if I put it before the session_destroy()
or don't have it at all, my session is deleted from my database but I can still see it in my storage in my inspector on Firefox.
It only gives me an error when trying to delete a session from the database. Everything else works no worries! I've manually tried to delete a session from the database in testing and I still get that error.
Is anyone able to see what I might be doing wrong or how I can fix it? I've only just learnt about this so pretty new to it. If I need to supply more information, just let me know. I've been searching over the web for nearly 2 days with no luck.
Upvotes: 1
Views: 14541
Reputation: 3406
Did you try to turn it off and on? :) Hope this helps somebody but when using Docker (and Lando) we suddenly had this one in our local Symfony environment.
Tried everything, from removing specific containers to lando destroy,... Always the same error.
In the end a restart of the computer simply solved the issue. I now assume a Docker restart should also fix it. But anyway, hope this helps.
Upvotes: 0
Reputation: 4211
You can use session_start
optional parameters
session_set_save_handler(new CornerstoneSessionHandler(), true);
session_start([
'read_and_close' => true, //you should add this
]);
$_SESSION['a'] = 45;
echo $_SESSION['a'];
unset($_SESSION['a']);
Upvotes: 1
Reputation: 398
I don't see a reason why you need to set a shutdown handler. Sessions are automatically saved when php shuts down, even when you get a fatal error.
Also the write, destroy and gc should always return true, even if no rows were updated / deleted. This seems counter intuitive, but php considers deleting a non existing session also a success.
Upvotes: 2