Page 1 of 1

Session Handling and Regenerating Sessions

Posted: Wed May 12, 2010 8:44 am
by drayfuss
Hi,

I'm writing a session handling class to put session data into a database. The problem is that, if the session times out, the session gets deleted from the database and the user must reload the page in order to re-insert the session.

So in some instances the user has sent a session which has been deleted from the database in the same breath. When there is no corresponding session in the database, any session superglobals that are set at that point will be ignored in the next page refresh.

Is there any way round this?

If a regenerate_id function is needed, where do I need to put it? It doesn't seem to want to live inside the session handler class.

Thanks,

drayfuss

Re: Session Handling and Regenerating Sessions

Posted: Wed May 12, 2010 9:22 am
by flying_circus
Post your code and we might be able to help.

Re: Session Handling and Regenerating Sessions

Posted: Wed May 12, 2010 9:39 am
by drayfuss
The reason I didn't post the code is because people tend not to want to get stuck into something as long as this. It's a pretty standard session handler. I'm surprised I couldn't find anything on other forums regarding this issue.

I initiate the class like so:

Code: Select all


require_once ('SessionHandler2.php');
	
	$sess = new Session();
	
	session_start();

Code: Select all


<?php

class Session{

	//declare private vars

    private $connection;
	
	private $current_sess_id;
	
	private $errors = array();
    
    public function __construct() {
    	
		//establish connection
		$this->establish_connection();
        
		//envoke session_set_save_handler
        session_set_save_handler(array($this, "sess_open"), array($this, "sess_close"), array($this, "sess_read"), array($this, "sess_write"), array($this, "sess_destroy"), array($this, "sess_gc"));
        
		//close connection
		$this->close_connection();
		
    }
	
	function establish_connection(){
		
		//establish a session connection for querying sessions
		$this->connection = mysql_connect("localhost", "session_usr", "session_pwd");
        
		//select database for sessions
		mysql_select_db("dev",  $this->connection);
		
	}
	
	function close_connection(){
		
		//close session connection
		mysql_close($this->connection);
		
	}
    
    public function sess_open($sess_path, $sess_name) {
    	//open session
        return true;
    }
    
    public function sess_close() {
    	//close session
        return true;
    }
    
    public function sess_read($sess_id) {
    	
		//this function reads the session data from the current session id field on the database
	    
		//establish connection
		$this->establish_connection();
		
		//query the database for the current session id
	    $result = mysql_query(
			
			"SELECT Data FROM sessions WHERE SessionID = '".mysql_real_escape_string($sess_id,$this->connection)."' LIMIT 1;",
			$this->connection
			
		) or die(mysql_error());
	        
		//if there is no session id, create one in the sessions table
		if (!mysql_num_rows($result)) {
	        
			//get timestamp
	    	$CurrentTime = time();
				
			//insert session id into sessions table with current timestamp
			mysql_query(
				
				"INSERT INTO sessions (SessionID, DateTouched) VALUES ('".mysql_real_escape_string($sess_id,$this->connection)."',".$CurrentTime.");",
				$this->connection
				
			) or die(mysql_error());
			
			$this->current_sess_id = $sess_id;
			
			//close connection
			$this->close_connection();
	            
			return '';
	    
		//if the current session id already exists in the sessions table, extract the current session data and update the timestamp
		} else {
	        
			extract(mysql_fetch_array($result), EXTR_PREFIX_ALL, 'sess');
				
			mysql_query(
				
				"UPDATE sessions SET DateTouched = ".$CurrentTime." WHERE SessionID = '".$sess_id."' LIMIT 1;",
				$this->connection
				
			);
			
			$this->current_sess_id = $sess_id;
			
			//close connection
			$this->close_connection();
				
	        return $sess_Data;

		}
    }
    
    public function sess_write($sess_id, $data) {
    	
		//this function writes the session data to the current session id field on the database
    	
		//establish connection
		$this->establish_connection();
    	
		//get current timestamp
        $CurrentTime = time();
		
		//update the sessions table with the new data and the current timestamp
        mysql_query(
		
		"UPDATE sessions SET Data = '".$data."', DateTouched = ".$CurrentTime." WHERE SessionID = '".$sess_id."' LIMIT 1;",
		$this->connection
		
		) or die(mysql_error());
		
		//close connection
		$this->close_connection();
		
        return true;
    }
	
    public function sess_destroy($sess_id) {
    	
		//this function destroys session ids
		
		//establish connection
		$this->establish_connection();
    	
		//delete relevant session id from the sessions table
        mysql_query(
		
			"DELETE FROM sessions WHERE SessionID = '".$sess_id."';",
			$this->connection
		
		) or die(mysql_error());
		
		$this->current_sess_id = 0;
		
		//close connection
		$this->close_connection();
        
		//return true in good session handling fashion
		return true;
    
	}
    
    public function sess_gc($sess_maxlifetime) {
    	
		//this function handles garbage collection for sessions
    	
		//establish connection
		$this->establish_connection();
		
		//get the current time in order to verify whether a session has timed out
		$CurrentTime = time();
    	
		//query database for sessions that have timed out based on their DateTouched field
		$result = mysql_query(
		
			"SELECT SessionID FROM sessions WHERE DateTouched + ".$sess_maxlifetime." < ".$CurrentTime.";",
			$this->connection
		
		) or die(mysql_error());
		
		//throw the results into an array for easy iteration
		$result_array = mysql_fetch_row($result);
		
		//loop through array of timed out session ids in order to delete them from the database appropriately
		for($i = 0; $i<sizeof($result_array);$i++){

			mysql_query(
		
				"DELETE FROM sessions WHERE SessionID = '".$result_array[$i]."';",
				$this->connection
		
			) or die(mysql_error());
			
			if($result_array[$i] === $this->current_sess_id){
				
				$this->current_sess_id = 0;
				
			}
		}
		
		//close connection
		$this->close_connection();
        	
		//return true in good session handling fashion
		return true;
    }
	
	public function getErrors(){
		
		return $this->errors;
		
	}
	
	public function getSess(){
		
		return $this->current_sess_id;
		
	}
}
?>



Re: Session Handling and Regenerating Sessions

Posted: Wed May 12, 2010 11:53 am
by flying_circus
I understand, but I like sessions handlers.

Here are the modifications that I would make. See comments in the code.

Make sure you read ALL my comments, as there is a bit in there that talks about setting the SessionId field to a unique/primary key in your database, and its important.

Code: Select all

<?php
# Configuration
  define('DB_HOST', 'localhost');
  define('DB_USER', 'session_usr');
  define('DB_PASS', 'session_pwd');
  define('DB_DB', 'dev');
  
class Session{
  # Variables
    private $connection;
    //private $current_sess_id; // Redundant, use session_id() to get session id
    private $errors = array();
    
  # Methods
    public function __construct() {
    # Establish connection
      $this->establish_connection();

    # Invoke session_set_save_handler
      session_set_save_handler(array($this, "sess_open"), array($this, "sess_close"), array($this, "sess_read"), array($this, "sess_write"), array($this, "sess_destroy"), array($this, "sess_gc"));

    /*
     * Don't do this in a constructor.  This is what destructors are for.
    # close connection
      $this->close_connection();
    */
    }
    
    public function __destruct() {
    # Close connection
      $this->close_connection();
    }
    
    function establish_connection() {
    # Establish a database connection
      $this->connection = mysql_connect(DB_HOST, DB_USER, DB_PASS);
      
    # Select database for sessions
      mysql_select_db(DB_DB,  $this->connection);
    }
        
    function close_connection(){
    # Close session connection
      mysql_close($this->connection);
    }
    
    public function sess_open($sess_path, $sess_name) {
    # Open session
      return true;
    }
    
    public function sess_close() {
    # Close session
      return true;
    }
    
    /* This function reads the session data from the current session id field on the database */
    public function sess_read($sess_id) {
    /*
     * This is already done in the constructor.  see $this->connection.
    # Establish connection
      $this->establish_connection();
    */
      
    # Query the database for the current session id
      $result = mysql_query(sprintf("SELECT `Data` FROM `sessions` WHERE `SessionID` = '%s' LIMIT 1;",
                                    mysql_real_escape_string($sess_id, $this->connection)), $this->connection) or die(mysql_error());
      
    # If there is no session id, create one in the sessions table
      if(mysql_num_rows($result) == 0) {
      # Get timestamp
        //$CurrentTime = time(); - Let's shorten the code, and put the time function right in the query string
        
      /*
       * Rather than do this here, lets move it to the write function, so we can make 1 less call to the database.  Let's be efficient, if it's easy.
       * 
       * (NOTE) FOR MY METHOD TO WORK, SESSION_ID MUST BE A UNIQUE/PRIMARY KEY IN THE TABLE, WHICH IT SHOULD BE ANYWAYS!
       * 
      # Insert session id into sessions table with current timestamp
        $result = mysql_query(sprintf("INSERT INTO sessions (SessionID, DateTouched) VALUES ('%s', '%u');",
                                      mysql_real_escape_string($sess_id, $this->connection),
                                      mysql_real_escape_string(time(), $this->connection)), $this->connection) or die(mysql_error());
       */
       
        // This is redundant.  use session_id() to get the session id
        //$this->current_sess_id = $sess_id;
              
        /*
         * This is done in the destructor
        #close connection
          $this->close_connection();
         */
         
         return '';
      //if the current session id already exists in the sessions table, extract the current session data and update the timestamp
      } else {
      /*
       * Dont do any of this stuff here.  Just return the session data.
       * 
       * Don't update the session timestamp here, do it when you write the session to eliminate a query to the database.  I'll show you how in the write function.
       * Don't set the $this->current_sess_id, use session_id() instead.
       * Don't close the database here, this is what the destructor is for.
       * 
            extract(mysql_fetch_array($result), EXTR_PREFIX_ALL, 'sess');
                    
            mysql_query(
                    
                    "UPDATE sessions SET DateTouched = ".$CurrentTime." WHERE SessionID = '".$sess_id."' LIMIT 1;",
                    $this->connection
                    
            );
            
            $this->current_sess_id = $sess_id;
            
            //close connection
            $this->close_connection();
       */
       
      # Fetch Session from result set
        $session = $result->fetch_assoc();
        
      # Return Session Data
        return $session['Data'];
      }
    }
    
    /* This function writes the session data to the current session id field on the database */
    public function sess_write($sess_id, $data) {
    /*
     * No need, it was created in the constructor
    # Establish connection
      $this->establish_connection();
     */
        
    # Get current timestamp
      //$CurrentTime = time(); - Let's shorten the code, and put the time function right in the query string
                
    # Update the sessions table with the new data and the current timestamp.  Return true on success, false on failure
      return mysql_query(sprintf("INSERT INTO `sessions` (`Data`, `DateTouched`, `SessionID`) VALUES ('%s', '%u', '%s') ON DUPLICATE KEY UPDATE `Data` = '%s', DateTouched = '%u';",
                                 mysql_real_escape_string($data, $this->connection),
                                 mysql_real_escape_string(time(), $this->connection),
                                 mysql_real_escape_string($sess_id, $this->connection)),
                                 mysql_real_escape_string($data, $this->connection),
                                 mysql_real_escape_string(time(), $this->connection), $this->connection);
      
    # Close connection
      //$this->close_connection();  - This is what the desctructor is for
                
      //return true; - Nope, see above.  The query success or failure indicates whether to return true or false.
    }
    
    /* this function destroys session ids */
    public function sess_destroy($sess_id) {
    /*
     * No need, it was created in the constructor
    # Establish connection
      $this->establish_connection();
     */
     
    # Delete relevant session id from the sessions table and return result
        return mysql_query(sprintf("DELETE FROM `sessions` WHERE `SessionID` = '%s' LIMIT 1;",
                                   mysql_real_escape_string($sess_id, $this->connection)),$this->connection);
    
    /*
    // Right... the whole session_id() thing...
      $this->current_sess_id = 0;
      
    // Don't you do it!  We've been over this before
    # Close connection
      $this->close_connection();
      
    // There's no good fashion in returning true on a false condition!
    # return true in good session handling fashion
      return true;
     */
    }
    
    /* this function handles garbage collection for sessions */
    public function sess_gc($sess_maxlifetime) {
    /*
     * No need, it was created in the constructor
    # Establish connection
      $this->establish_connection();
     */
    
    /*
     * DEAR GOD!  This is terribly inefficient.  Can I show you a better way?
     * 
    //get the current time in order to verify whether a session has timed out
    $CurrentTime = time();

    //query database for sessions that have timed out based on their DateTouched field
    $result = mysql_query("SELECT SessionID FROM sessions WHERE DateTouched + ".$sess_maxlifetime." < ".$CurrentTime.";", $this->connection) or die(mysql_error());
    
    //throw the results into an array for easy iteration
    $result_array = mysql_fetch_row($result);
    
    //loop through array of timed out session ids in order to delete them from the database appropriately
    for($i = 0; $i<sizeof($result_array);$i++){
      mysql_query("DELETE FROM sessions WHERE SessionID = '".$result_array[$i]."';", $this->connection) or die(mysql_error());
            
      if($result_array[$i] === $this->current_sess_id) {
        $this->current_sess_id = 0;
      }
    }
    
    //close connection
    $this->close_connection();
    
    //return true in good session handling fashion
    return true;
     */
    
    # Delete Expired Sessions
      return mysql_query(sprintf("DELETE FROM `sessions` WHERE `DateTouched` < '%u';",
                                 mysql_real_escape_string((time() - $sess_maxlifetime), $this->connection)),$this->connection);
    }
        
  /* Great idea for a function, but errors are not implemented in your code, so it's useless */
  public function getErrors(){
  # Retrieve errors array
    return $this->errors;
  }
  
  /* This function can be deleted completely, as session_id() accomplishes the same thing. */
  public function getSess(){
  # Return session id
    //return $this->current_sess_id; - use session_id()
    return session_id();
  }
}
?>

Re: Session Handling and Regenerating Sessions

Posted: Wed May 12, 2010 11:55 am
by flying_circus
Oh, and if it's available to you, start using the mysqli extension, rather than mysql. They both do the same thing but mysqli is a little newer and much improved. :)

Re: Session Handling and Regenerating Sessions

Posted: Wed May 12, 2010 12:44 pm
by drayfuss
Flying_Circus,

Firstly I'd just like to thank you for putting the time in with this one. I really appreciate the comments. As an actionscript developer, I completely understand the reason for optimizing, and ridding the code of repeat patterns.

I agree that I should be implementing mysqli_, which I will do as soon as I've gotten mysql_ to work.

I read all your comments, and I think I understood them - but I'm having trouble with the new code.

A whole host of errors reveal themselves:

[text]
Warning: mysql_real_escape_string(): 3 is not a valid MySQL-Link resource in /Applications/XAMPP/xamppfiles/htdocs/SessionHandler2.php on line 134

Warning: mysql_real_escape_string(): 3 is not a valid MySQL-Link resource in /Applications/XAMPP/xamppfiles/htdocs/SessionHandler2.php on line 134

Warning: mysql_real_escape_string(): 3 is not a valid MySQL-Link resource in /Applications/XAMPP/xamppfiles/htdocs/SessionHandler2.php on line 134

Warning: sprintf() [function.sprintf]: Too few arguments in /Applications/XAMPP/xamppfiles/htdocs/SessionHandler2.php on line 134

Warning: mysql_real_escape_string(): 3 is not a valid MySQL-Link resource in /Applications/XAMPP/xamppfiles/htdocs/SessionHandler2.php on line 134

Warning: mysql_real_escape_string(): 3 is not a valid MySQL-Link resource in /Applications/XAMPP/xamppfiles/htdocs/SessionHandler2.php on line 134

Warning: mysql_query() expects at most 2 parameters, 4 given in /Applications/XAMPP/xamppfiles/htdocs/SessionHandler2.php on line 134
[/text]

So, most of them I can see are reffering to an invalid link resource, but there is no error on connecting to the database, or selecting a table. It just chokes on the second argument of the mysql_real_escape_string function.

I don't know much about sprint if - and the syntax you've used for some of the mysql queries has thrown me a little (for example, your use of '%s' and '%u'.

I have made the table field SessionID a primary and unique key, as far as I can tell.

I appreciate the time you've put in so far, but for it to click into place you may have to give me a few sparing tips and tricks. I can't, for the life of me, work out what the problem is. I'm assuming, that on making your code work, it should solve the original issue I posted about.

Thanks again,
drayfuss

Re: Session Handling and Regenerating Sessions

Posted: Wed May 12, 2010 1:12 pm
by flying_circus
Whoops, looks like I had a paratheses in the wrong spot. Try this:

Code: Select all

    public function sess_write($sess_id, $data) {
    /*
     * No need, it was created in the constructor
    # Establish connection
      $this->establish_connection();
     */
        
    # Get current timestamp
      //$CurrentTime = time(); - Let's shorten the code, and put the time function right in the query string
                
    # Update the sessions table with the new data and the current timestamp.  Return true on success, false on failure
      return mysql_query(sprintf("INSERT INTO `sessions` (`Data`, `DateTouched`, `SessionID`) VALUES ('%s', '%u', '%s') ON DUPLICATE KEY UPDATE `Data` = '%s', DateTouched = '%u';",
                                 mysql_real_escape_string($data, $this->connection),
                                 mysql_real_escape_string(time(), $this->connection),
                                 mysql_real_escape_string($sess_id, $this->connection),
                                 mysql_real_escape_string($data, $this->connection),
                                 mysql_real_escape_string(time(), $this->connection)), $this->connection);
      
    # Close connection
      //$this->close_connection();  - This is what the desctructor is for
                
      //return true; - Nope, see above.  The query success or failure indicates whether to return true or false.
    }
Sprintf takes the format of a string with identifiers, and then an additional argument for each identifier.

example: sprintf("Hello %s", "world");
example: sprintf("My name is %s, and the timestamp is %u", "Jonas", time());

%s = string
%u - unsigned integer

the s in sprintf means silent, so it doesnt print the results to the screen. printf will print the results to the screen, much like echo.

Re: Session Handling and Regenerating Sessions

Posted: Thu May 13, 2010 6:06 am
by drayfuss
Hi Flying Circus,

thanks for the modification. Unfortunately, the code still didn't work straight out of the box. But I had a little tweak and it works perfectly. Here are the changes I made:

+ I'm not sure whether it's a difference in MySQL versions, but my mysql queries didn't like their fields (ie Data, SessionID) or tables wrapped in quotation marks.

+ The closing the connection on the destruct method doesn't bode well for the class. Unsure why, but people seem to be saying session handling objects and destructors don't mix very well. Any way, removing this solved a lot of the bogus link identifier errors. Any suggestions as to where I should call the close_connection() function?

+ At one point you used $session = $result->fetch_assoc();. The script choked on that. Perhaps that is valid mysqli_ syntax? Anyway, I changed this to $session = mysql_fetch_assoc($result); and it worked a treat.

I just want to say, thanks a bunch for helping me out on this one. I've taken on board all your efficiency comments, and I agree that your code is much stronger beast.

On the subject of my original problem (which was garbage collection deleting the old session and not re-instating it) as I understand it, the reason this has been solved with your code is because we've brought the INSERT function into the write method? Also, what's the significance of making the MySQL field into a PRIMARY and UNIQUE key? I'm not too up on my keys.

Many Kind Regards,

drayfuss

Code: Select all


<?php 

define('DB_HOST', 'localhost');
define('DB_USER', 'session_usr');
define('DB_PASS', 'session_pwd');
define('DB_DB', 'dev');

class Session {

    # Variables
    
    private $connection;
    
    private $errors = array();
    
	# Methods
    
	public function __construct() {
		
        # Establish connection
        $this->establish_connection();
        
        # Invoke session_set_save_handler
        session_set_save_handler(array($this, "sess_open"), array($this, "sess_close"), array($this, "sess_read"), array($this, "sess_write"), array($this, "sess_destroy"), array($this, "sess_gc"));
		
    }
    
    public function __destruct() {
		
        # Close connection
        session_write_close();
		
    }
    
    function establish_connection() {
		
        # Establish a database connection
        $this->connection = mysql_connect(DB_HOST, DB_USER, DB_PASS);
        
        # Select database for sessions
        mysql_select_db(DB_DB, $this->connection) or die(mysql_error());
    
	}
    
    function close_connection() {
		
        # Close session connection
        if($this->connection){
        	
        	mysql_close($this->connection);
			unset($this->connection);
		
		}
	}
    
    public function sess_open($sess_path, $sess_name) { 
		
        # Open session
        return true;
		
    }
    
    public function sess_close() {
    	
		$this->errors[] = 'close';
		 
        # Close session
        return true;
    
	}
    
    public function sess_read($sess_id) {
		
        # Query the database for the current session id
        $result = mysql_query(
		
			sprintf(
			
				"SELECT Data FROM sessions WHERE SessionID = '%s' LIMIT 1;",
				mysql_real_escape_string($sess_id, $this->connection)),
				
			$this->connection
			
		) or die(mysql_error());
        
        # If there is no session id, create one in the sessions table
        if (mysql_num_rows($result) == 0) {
        	
            return '';
			
        } else {
        	
			# Get Session Data
            $session = mysql_fetch_assoc($result);
            
            # Return Session Data
            return $session['Data'];
			
        }
    }
    
    public function sess_write($sess_id, $data) {
		
        # Update the sessions table with the new data and the current timestamp.  Return true on success, false on failure
        return mysql_query(
		
			sprintf(
			
				"INSERT INTO sessions (Data, DateTouched, SessionID) VALUES ('%s', '%u', '%s') ON DUPLICATE KEY UPDATE Data = '%s', DateTouched = '%u';",
				mysql_real_escape_string($data, $this->connection),
				mysql_real_escape_string(time(), $this->connection),
				mysql_real_escape_string($sess_id, $this->connection),
				mysql_real_escape_string($data, $this->connection),
				mysql_real_escape_string(time(), $this->connection)),
			
			$this->connection
		
		) or die(mysql_error());
        
    }
    
    public function sess_destroy($sess_id) {
		
        # Delete relevant session id from the sessions table and return result
        return mysql_query(
		
			sprintf(
			
				"DELETE FROM 'sessions' WHERE 'SessionID' = '%s' LIMIT 1;",
				mysql_real_escape_string($sess_id, $this->connection)),
				
			$this->connection
		
		);
    }
	
    public function sess_gc($sess_maxlifetime) {
		
        # Delete Expired Sessions
        return mysql_query(
		
			sprintf(
			
				"DELETE FROM sessions WHERE DateTouched < '%u';",
				mysql_real_escape_string((time() - $sess_maxlifetime),$this->connection)),
				
			$this->connection
			
		);
	}
	
    public function getErrors() {
    	
        # Retrieve errors array
        return $this->errors;
    
	}
    
    public function getSess() {
        
        # Return session id
        return session_id();
    }
}
?>



Re: Session Handling and Regenerating Sessions

Posted: Thu May 13, 2010 11:38 am
by flying_circus
Hey Drayfuss,

I'm glad you were able to get it working! It's tough to post working code samples from work, as I dont work for a development company and I don't have my dev environment. I'm glad the concept helped :)

The quoting of field names using backticks (` key in top left corner of the keyboard) is used to tell the query parser that you mean string literal. I've seen people name their fields using a reserved word, like date. So when you try to query on date, the parser thinks you mean the function date, and not the field name date. Not sure if it's with the mysql extension or the version of mysql you're using. I've never run across that issue before. Just be aware of it, and you should be ok.

I was going to suggest calling session_write_close() before destroying your database connection, but it looks like you've already tried that. This situation was unique, as I typically abstract the database portion into its own class. This way, I only pass the resource to the database to the session object, rather than have the session object create a connection. Most websites will require the database connection for more than just sessions, so it seems logical to abstract it into a globally accesible connection. In my database class, session_write_close() is in the database object destructor, and I've never had a problem. Maybe something to think about?

Yup, you caught me :) I havent used the mysql extension in years, I use mysqli exclusively, so those details always seem to trip me up. Glad you got it dialed in.



I am far from a SQL master, but a field specified as unique or primary key means that it has to be a unique value in the table. We dont desire duplicate records for a session id, so we specify the field as unique or primary and sql wont allow duplicates. The primary key is also indexed. Indexing fields that you routinely search (in the where clause) will help increase performance.

Re: Session Handling and Regenerating Sessions

Posted: Thu May 13, 2010 12:08 pm
by drayfuss
Flying Circus,

thanks for the detailed reply. I shall take your backtick comment on board. Perhaps I over de-bugged and forgot to try the new script with backticks.

You've replied just as I've finished something which I was hoping you would look at. In a previous post (which you have replied to) I mentioned that I was powering the user authentication within the session class. Someone, it may have been you, advised me that this was a bad idea as I might run into problems in the future. On the back of that advise, I've written a User class which corresponds with the SessionHandler class we've just been discussing.

It works (or seems to) but I was hoping you might glance over it, purely for advice on how you would tighten it up.

Here's my php front end (note that I reference our beloved SessionHandler):

Code: Select all

<?php
	/*
	* Session Handler
	*/
	
	require_once ('SessionHandler2.php');
	
	$sess = new Session();
	
	session_start();
	
	require_once('User.php');
	
	$_SESSION['cart'] = 'cart';
	$_SESSION['location'] = 'Ukraine';
	
	if($_POST['usr']){
		
		$usr_mail = $_POST['usr'];
		$usr_pwd = $_POST['pwd'];
		
	}else{
		
		$usr_mail = $_SESSION['usr_mail'];
		$usr_pwd = NULL;
		
	}
	
	$usr = new User($sess->getSess(), $usr_mail, $usr_pwd);
	
	echo($_SESSION['usr_mail']);
		
?>

<form method="POST" action='session2.php'>
	<input name='usr' type='text'>
	<input name='pwd' type='password'>
	<button type='submit'>login</button>
</form>
Her is the User class (note that I'm using your sprintf() function):

Code: Select all


<?php

define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_DB', 'dev');

    class User{
    	
		# Variables
		
		private $connection_usr;
		
		private $usr_update_fields = array(
		
			array('detail_country', 'location'),
			array('usr_cart', 'cart')
			
		);
		
		# Methods
		
		public function __construct($sess_id, $usr_mail, $usr_pwd = 0){
			
			if($usr_pwd){
			
				if($success = $this->auth_usr_sess($sess_id, $usr_mail, $usr_pwd)){
					
					$_SESSION['usr_mail'] = $usr_mail;
					
					$this->update_usr_sess_data($sess_id, $usr_mail);
					
				}else{
					
					$_SESSION['usr_mail'] = "guest";
				
				}
			}else{
				
				if($success = $this->auth_usr($sess_id, $usr_mail)){
					
					$_SESSION['usr_mail'] = $usr_mail;
					
					$this->update_usr_sess_data($sess_id, $usr_mail);
				
				}else{
					
					$_SESSION['usr_mail'] = "guest";
					
				}
			}
		}
		
		private function auth_usr_sess($sess_id, $usr_mail, $usr_pwd){
			
			# establish connection
			$this->establish_connection_usr();
			
			# prepare password
			$usr_pwd = md5($usr_pwd);
			
			$result = mysql_query(
				
				sprintf(
						
					"SELECT usr_mail FROM users WHERE usr_mail = '%s' AND usr_pwd = '%s' LIMIT 1;",
					mysql_real_escape_string($usr_mail, $this->connection_usr),
					mysql_real_escape_string($usr_pwd, $this->connection_usr)
						
						),
						
				$this->connection_usr
				
			) or die(mysql_error());
			
			
			# if a user exists, update usr_session where the usr_mail and usr_pwd match and return
			if(mysql_num_rows($result)){
				
				mysql_query(
				
					sprintf(
						
						"UPDATE users SET usr_session = '%s' WHERE usr_mail = '%s' AND usr_pwd = '%s' LIMIT 1;",
						mysql_real_escape_string($sess_id, $this->connection_usr),
						mysql_real_escape_string($usr_mail, $this->connection_usr),
						mysql_real_escape_string($usr_pwd, $this->connection_usr)
						
						),
						
					$this->connection_usr
				
				) or die(mysql_error());
				
				return 1;
			
			}else{
				
				return 0;
			
			}
		}
		
		private function auth_usr($sess_id, $usr_mail){
			
			# establish connection
			$this->establish_connection_usr();
			
			# update usr_session where the usr_mail and usr_pwd match and return
			$result = mysql_query(
			
				sprintf(
					
					"SELECT usr_mail FROM users WHERE usr_session = '%s' AND usr_mail = '%s' LIMIT 1;",
					mysql_real_escape_string($sess_id, $this->connection_usr),
					mysql_real_escape_string($usr_mail, $this->connection_usr)
					
					),
					
				$this->connection_usr
			
			) or die(mysql_error());
			
			return mysql_num_rows($result);
			
		}
		
		private function update_usr_sess_data($sess_id, $usr_mail){
			
			# start query string
			$query = "UPDATE users SET ";
			
			# iterate through the usr_update_fields and add to the query string respectively
			for($i = 0; $i<sizeof($this->usr_update_fields); $i++){
				
				$query .= sprintf(
				
					"%s = '%s' ",
					mysql_real_escape_string($this->usr_update_fields[$i]['0'], $this->connection_usr),
					mysql_real_escape_string($_SESSION[$this->usr_update_fields[$i]['1']], $this->connection_usr)
					
				) or die(mysql_error());
				
				if($i < (sizeof($this->usr_update_fields)-1)){
					
					$query .= ", ";
					
				}
			}
			
			# finish the query string with the conditionals set by the users mail and session id
			$query .= sprintf("WHERE usr_session = '%s' AND usr_mail = '%s' LIMIT 1;", $sess_id, $usr_mail);
			
			# query database and return true or false
			return mysql_query($query, $this->connection_usr);
			
		}
		
		public function destroy_usr_sess($sess_id, $usr_mail){
			
			# nullifies the users session
			
			$this->establish_connection_usr();
			
			# update the usr_session to NULL
			mysql_query(
			
				"UPDATE users SET usr_session = NULL WHERE usr_mail = '".mysql_real_escape_string($usr_mail)."' AND usr_session = '".mysql_real_escape_string($sess_id)."' LIMIT 1;",
				$this->connection_usr
			
			) or die(mysql_error());
			
			# close connection
			$this->close_connection_usr();
			
		}
		
		private function establish_connection_usr(){
		
			# establish a connection for querying users
			$this->connection_usr = mysql_connect(DB_HOST, DB_USER, DB_PWD);
	        
			# select database for users
			mysql_select_db("DB_DB",  $this->connection_usr);
		
		}
	
		private function close_connection_usr(){
		
			# close users connection
			mysql_close($this->connection_usr);
		
		}
    }
?>

The users tables is created with the following mysql query:

CREATE TABLE IF NOT EXISTS `users` (
`usr_mail` varchar(320) NOT NULL,
`usr_pwd` varchar(50) NOT NULL,
`usr_session` varchar(50) NOT NULL DEFAULT '',
`detail_country` varchar(20) DEFAULT NULL,
`detail_birthday` varchar(10) DEFAULT NULL,
`detail_heard_about` varchar(50) DEFAULT NULL,
`usr_cart` text,
PRIMARY KEY (`usr_mail`,`usr_session`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Some places where I feel you might help:

+ Where I initiate authentication based on a user, session AND password (auth_usr_sess()), I'm worried about having to use a SELECT query and an UPDATE query. I'm being forced to use a SELECT query so I can get the appropriate feedback from the database. Is there a way to get feedback in an UPDATE, even if the UPDATE conditions are true but no update is made due to an already populated cell?

+ The different functions are called based on some very messy looking if statements at the __construct stage. Is there a neater way of doing this?

I completely understand if you want to back out at this stage, your help and advice has already been paramount. Truth be told, I need an inspiring coding mentor, and I suppose I like the way that you've explained things so far. I've got a pile of O'Reilly books. They're good, but they don't answer those little questions that inspire you to keep going.

drayfuss