5

After plenty of debugging it seems the problem was (embarrassingly) in my database session code, not a typical session problem. You can see my answer relating it here - Thanks


I understand this may be a duplicate of similar questions (e.g. one, two, three) but despite following what seems to be the best practices for this I'm still having problems.

When using the session_set_save_handler() to use my database session class the session data is cleared when the session begins on session2.php after being redirected from session1.php.

An overview of my observations:

  • Data is saved into the database correctly in session1.php
  • Data is lost on session_start() in session2.php
  • Data is still in database after the redirect and before session_start() is called in session2.php
  • Session ID remains the same and is stored in a cookie which is being sent back to the server in the request headers correctly
  • Using PHP's default Session handling it works okay

And note:

  • exit() used after header()
  • session_start() on every page before output

Have I made a silly typo? Made a daft error? Or is this a strange quirk?

Thanks in advance for any help offered.

Here's the code (extracted into testing files while fixing this issue):

session1.php

<?php

require_once('session.php');

session_start();  

$_SESSION['KEY'] = 'VALUE PHPSESSID: ' . session_id();

session_write_close();   
header('Location: session2.php');
exit;

session2.php

<?php

require_once('session.php');

session_start();

// Nothing?
var_dump( $_SESSION );

session.php

<?php

define( "DB_HOST", 'localhost' );
define( "DB_USER", '******' );
define( "DB_PWD", '******' );
define( "DB_NAME", '******' );

require_once('class/DatabaseSessionHandler.php');

// Use the DatabaseSessionHandler class to handle sessions
$session_handler = new DatabaseSessionHandler;
// Set up the handler above as the default session handler
session_set_save_handler(
    array($session_handler, 'open'),
    array($session_handler, 'close'),
    array($session_handler, 'read'),
    array($session_handler, 'write'),
    array($session_handler, 'destroy'),
    array($session_handler, 'gc')
);

DatabaseSessionHandler.php

<?php

class DatabaseSessionHandler
{

    protected $connection;
    protected $session_life_time;

    public function __construct()
    {
        // Ensure that everything is closed correctly as 
        // per warning on http://uk3.php.net/session_set_save_handler
        register_shutdown_function( 'session_write_close' );
    }

    public function open( $save_path, $session_name )
    {
        $this->connection = new mysqli( DB_HOST, DB_USER, DB_PWD, DB_NAME );
        $this->session_life_time = get_cfg_var( "session.gc_maxlifetime" );

        if ( $this->connection->connect_error )
            return false;

        return true;
    }

    public function close()
    {
        $this->connection->close();
        return true;
    }

    public function read( $session_id )
    {
        $data = '';

        $statement = $this->connection->prepare( "SELECT `session_data` 
                                                  FROM `session` 
                                                  WHERE `session_id` = ? " );
        $statement->bind_param( "s", $session_id );
        $statement->execute();
        $statement->bind_result( $data );

        return (string) $data;
    }

    public function write( $session_id, $session_data )
    {
        $expiry_time = time() + $this->session_life_time;
        $statement = $this->connection->prepare( "REPLACE INTO `session` 
                                                (`session_id`, `session_data`, 
                                                `expiry_time`)
                                                 VALUES (?, ?, ?)" );
        $statement->bind_param( "ssi", $session_id, $session_data, $expiry_time );

        if ( !$statement->execute() )
            return false;

        return true;
    }

    public function destroy( $session_id )
    {
        $statement = $this->connection->prepare( "DELETE FROM `session` 
                                                    WHERE `session_id` = ?" );
        $statement->bind_param( "s", $session_id );

        if ( !$statement->execute() )
            return false;

        return true;
    }

    public function gc( $max_lifetime )
    {
        $current_time = time();
        $statement = $this->connection->prepare( "DELETE FROM `session` 
                                                    WHERE `expiry_time` < ?" );
        $statement->bind_param( "i", $current_time );

        if ( !$statement->execute() )
            return false;

        return true;
    }

}
Community
  • 1
  • 1
Aled
  • 91
  • 3
  • 7
  • 2
    log something in destroy, and something else in gc, so that you know which one is reponsible for the deletion. Or use a debugger. – greg0ire Aug 08 '12 at 11:35
  • What if you try it without the `session_write_close();` ? Also, what server are you running this on? Check out this question (see comments in answer) http://stackoverflow.com/questions/10028643/php-session-data-not-being-stored-simply – Gerald Versluis Aug 08 '12 at 11:37
  • @GeraldVersluis the data is still in the database after the redirect and so it's not the session_write_close() function (tried removing it and still no luck) – Aled Aug 08 '12 at 12:12
  • @greg0ire it doesn't seem like either are getting called so they're not the culprits. – Aled Aug 08 '12 at 12:17
  • @GeraldVersluis I forgot to add, this is running on Apache 2.2.17 (Windows) using PHP 5.3.5 although it'll eventually be running on a Linux server (not tested there yet though.) – Aled Aug 08 '12 at 12:35

2 Answers2

2

You are clearing session data less then current time. it should be current time - max_lifetime

Here is the solution

public function gc( $max_lifetime )
{
    $current_time = time() - $max_lifetime;
    $statement = $this->connection->prepare( "DELETE FROM `session` 
                                                    WHERE `expiry_time` < ?" );
    $statement->bind_param( "i", $current_time );

    if ( !$statement->execute() )
            return false;

    return true;
}
Dinesh
  • 66
  • 5
  • Ah, that was a problem I'd overlooked but it doesn't seem to be the problem. The problem occurs even when the garbage collector doesn't get called. I appreciate you correcting that though as it would have most likely been missed otherwise. – Aled Aug 08 '12 at 12:22
0

Well after extensive file logging and printing out variables (some of the session functions are called after the output to the browser is sent and as such you can't output errors in the normal fashion) it turns out the issue was with my database session code.

I'd forgotten to add '$statement->fetch()' after binding the MySQLi output to a variable and as such I was never really getting the data out of the database.

An embarrassing mistake but it served to drive home with the fact that if there's something wrong with your software, more often than not it's a problem in your code and not in the language or libraries you're using.

Thanks to those who commented and answered, I've learnt a lot about PHP sessions over the past couple of days because of this.

Aled
  • 91
  • 3
  • 7