skip to Main Content

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:xampptmp)

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.

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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']);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search