PHP TIPS AND TRICKS - Session Handling with PHP 4

This tutorial is designed for an advanced PHP programmer. It assumes you know how to handle cookies. ( See Feedback with a Cookie to learn about using cookies.) You should also know how to pass data from one page to another using the POST and GET methods.

(For more about Zend Technologies, please visit www.zend.com)

Introduction

Unlike PHP 3, PHP 4.0 has built-in capabilities to handle session management. The session management functionality in PHP 4.0 is easy to use, powerful and open for custom modifications. Creating sessions allows you to keep track of the actions of a particular user over the period of time she is viewing your web site.

To associate session data with a user, you need a session identity number, that is, a key that ties the user to his data. This tutorial discusses how to open sessions, track session data, and clear the data when you no longer need it.

PHP's Built-in Session Library

Luckily, PHP 4.0 has basic session management built in, which frees you from the task of inventing session IDs, serializing and storing session data. While it's very easy and straightforward to use and may suffice for your needs, it lacks some of the advanced features that the PHPLib provides.

Goals of the Tutorial

In this tutorial you can learn the following:

  • What a session is
  • How to use persistent variables
  • How session IDs are passed from page to page
  • How to end a session and handle clearing of data ( garbage collection)
  • How to register variables to refer to during user sessions
  • How to use the PHP 4.0 session management functions, including:
    • session_register ( )
    • session_start ( )
    • session_destroy ( )
    • gc_probability ( )
    • gc_maxlifetime
    • serialize ( )
    • deserialize ( )
    • session_save_path ( )
  • How PHP stores session data.

Basic Terms

Session management is a mechanism to maintain state about a series of requests from the same user across some period of time. That is, the term "session" refers to the time that a user is at a particular web site. The problem is, that HTTP has no mechanism to maintain state. Individual requests aren't related to each other. The Web server can't easily distinguish between single users and doesn't know about user sessions. Session management refers to the way that associate data with a user during a visit to a Web page. This tutorial uses the term session for a single visit of a user. For example, a typical online shopping session might include logging in, putting an item into the shopping cart, going to the checkout page, entering address and credit card data, submitting the order, and closing the browser window. PHP 4.0 includes native session management functions to ease the task of managing user sessions.

The "life: of a session refers to the amount of time the session is active. "Serializing" means the transformation of variables to a byte-code representation that can be stored anywhere as a normal string. Without the serializing feature, it wouldn't be possible, for example, to store PHP arrays into a database. Serializing data is very useful for preserving data across requests, an important facet of a session library. You can use serialize ( ) and deserialize ( ), but note that in PHP 3 these functions don't work correctly on objects ( classes ) ; class functions will be discarded.

Background Information

PHP's session management library offers the key characteristics required of a session management library:

  • It stores session data on the server. Because the library uses different storage modules, you can keep the data in plain text files, shared memory, or databases. The exact location of data is not really important ( as long the performance of the medium is sufficient).
  • It uses a cryptographically random session ID to identify a user.
  • It saves the session ID ( and only the session ID) on the client side using cookies, GET/POST, or the script path. ( The PHP library provides all of these methods ; we show how to use them a little later.)
  • If the user disables cookies, the application can use other means of session propagation.

Preliminary Tips and Prerequisites

To associate session data with a user, you need a session identity number: a key that ties the user to his data. PHP 4.0's session management frees you from the task of inventing session IDs, serializing and storing session data.

Note: PHPlib offers advanced session management functions if you require something beyond the standard PHP session functionality.

Starting a Session

A PHP 4 session is started either explicitly by session_start ( ), or implicitely by registering a variable for the session, using session_register ( ). Usually, you will call session_start ( ) on top of the page, so that session variables are available to your script, and register variables to the session later in the script. It wouldn't make a difference though, if you registered your session variables with session_register ( ) in the head of the script and left out the session_start ( ) call - session_register ( ) calls session_start ( ) internally, if the session isn't started yet. When you start a session either way, the following happens:

  • PHP checks whether a valid session ID exists.
  • If there is no session ID, PHP creates a new ID.
  • If a valid ID exists, the frozen variables of that session are reactivated and introduced back to the global namespace.

Registering a session variable is done through the session_register ( ) command. This allows you to create ( register) variables which are stored throughout the session, and can be referred to during the session. All variables you want to preserve across page requests must be registered to the session library with the session_register ( ) function. Note that this function takes the name of a variable as argument, not the variable itself. You can use session_unregister ( ) to remove variables from the session, for example, when the user removes a product item from the shopping cart.

Syntax Example

This is an example of a counter.

  • Start a session
  • Print the most recent value of the counter
  • Increment the counter
  • Register the counter
session_start ( ) ;

print (

$ counter ) ;

$ counter++ ;

session_register ( "counter" ) ;

Of course, this example is different from a normal page counter: the session ( and thus the counter) is tied to one specific user. With PHP's default configuration, the session cookie has a lifetime of 0 ; if you close the browser and reopen it, the counter restarts from zero, as the cookie has been deleted.

Syntax Example

  • Register the variable $ foo
session_register ( "foo" ) ;

Syntax Example

  • Register a previously defined variable $ bar
$ bar = "This is a string" ;

$ foo = "bar" ;

session_register ( $ foo ) ;

The difference in the last two syntax examples is that in the first a variable named "foo" is registered as session variable, in the latter actually a variable named "bar" is registered. Session_register ( ) takes the name of a variable as argument, not the variable itself - this is easily confused, but the examples show the difference clearly.

It's as easy to handle session variables as it is to handle GET/POST variables. If you register a variable named foo, $ foo is accessible automatically after calling session_start ( ). Because the serialize ( ) function was improved in PHP 4, it's also feasible to treat objects ( classes) as session variables.

Tips

Note: At the start of a session, a new ID may be created if the session is refused and marked as invalid because the HTTP referrer for the page comes from a non-local site and extern_referer_check ( note the single "r") is enabled in the PHP configuration. This introduces some additional security, as it prevents users coming from other PHP sites taking over a session ( which is still highly improbable, however, due to the algorithm used for the generation of the session ID).

Ending a Session

Session ending is not automatic, because it is difficult for the system to tell when the user is finished the session. Several commands help you control how the system determines when to end a session for a user.

  • You can force a session end with the command session_destroy ( ).
  • If you propagate the session ID via cookies, the default cookie lifetime is 0, meaning that the cookie is deleted as soon as the user closes the browser. You can influence the cookie's lifetime with the configuration value lifetime.
  • You can use the gc_maxlifetime configuration directive to determine how long after the last access to this session the data should be destroyed. This is used because the server doesn't know whether the cookie still exists on the client side. However, performing such a cleanup of old sessions ( called "garbage collection") on every page request would cause considerable overhead. Therefore, in tangent with the gc_maxlifetime, you should use gc_probability. This specifies with what probability the garbage collection routine should be invoked. If gc_probability is 100, the cleanup is performed on every request ( that is, with a probability of 100 % ) ; if it's 1 as by default, old sessions will be removed with a probability of 1 % per request.

If you don't use cookies but pass the session ID via GET or POST instead, you need to pay special attention to the garbage collection routines. Users might bookmark URLs containing the session ID, so you need to make sure that sessions are cleared frequently. If the session data still exists when the user accesses the page with the session ID at a later time, PHP simply resumes the previous session instead of starting with a new session, which may not be your intention. A value of 10 to 20 for gc_probability would better fit this scenario than the default value of 1.

Tips

You might ask yourself why PHP allows you to specificy a probability (

gc_probability) which determines when garbage collection will occur, rather than a function which cleans up every n times. If PHP used a counting function, the server would need to track the number of opened sessions somehow. Using the probability function means that the server does not have to store counters for cleanup, which translates to cleaner and faster execution.

Storage Modules

To read and save session data, PHP uses storage modules, thus abstracting the back end of the library. There are currently three storage modules available:

  • Files. By default, PHP uses the files module to save the session data to disk. It creates a text file named after the session ID in /tmp. You probably won't ever need to access this file directly. In the example of the session counter, the content of this file would look like this, which is a serialized representation of the variable: counter | i:4 ;
  • mm. If you need higher performance, the mm module is a viable alternative ; it stores the data in shared memory and is therefore not limited by the hardware I/O system.
  • User. Used internally to realize user-level callback functions that you define with session_set_save_handler ( ).

The real power lies in the capacity to specify user callbacks as storage modules. Because you can write your functions to handle sessions while still being able to rely on the standardized PHP API, you can store sessions wherever and however you want: in a database like MySQL, XML files, on a remote FTP server ( an FTP server is unlikely, but you get the idea).

The function

session_set_save_handler ( ) takes six strings as arguments, which must be your callback functions.

The syntax of the function is as follows:

void session_set_save_handler ( string open, string close, string read, string write, string destroy, string gc ) ;

Tip

To leave out one argument, pass an empty string ( "" ) to

session_set_save_handler ( ).

The functions are defined as follows:

bool open ( string save_path, string sess_name ) ;

This function is executed on the initialization of a session ; you should use it to prepare your functions, to initialize variables, or the like. It takes two strings as arguments. The first is the path where sessions should be saved. This variable can be specified in php.ini or by the

session_save_path ( ) function - you can use this variable as a joker and use it for module-specific configuration. The second argument is the session's name, by default PHPSESSID. Returns true on success and false on error.

bool close ( ) ;

This function is executed on shutdown of a session. Use it to free memory or to destroy your variables. It takes no arguments and returns true on success and false on error.

mixed read ( string sess_id, ) ;

This important function is called whenever a session is started. It must read out the data of the session identified with sess_id and return it as a serialized string. If there's no session with this ID, an empty string "" is returned. If there is an error, false is returned.

bool write ( string sess_id, , string value ) ;

When the session needs to be saved, this function is invoked. The first argument is a string containing the session's ID ; the second argument is the serialized representation of the session variables. This function returns true on success and false on error.

bool destroy ( string sess_id, ) ;

When the developer calls

session_destroy ( ), this function is executed. It destroys all data associated with the session sess_id and returns true on success and false on error.

bool gc ( int max_lifetime, ) ;

This function is called on a session's start-up with the probability specified in

gc_probability. It's used for garbage collection ; that is, to remove sessions that weren't updated for more than gc_maxlifetime seconds. This function returns true on success and false on error.

If you want to setup your own storage modules, for example to store session data in a MySQL database, you need to provide PHP implementations of these functions. The prototypes will look similar to these examples:

function sess_open ( $ save_path, $ sess_name )

{

}

function

sess_read ( $ sess_id )

{

}

function

sess_write ( $ sess_id, $ val )

{

}

function

sess_destroy ( $ sess_id )

{

}

function

sess_gc ( $ max_lifetime )

{

}

To register these calback functions, you use

session_set_save_handler ( ):

session_set_save_handler ( "sess_open", "", "sess_read", "sess_write", "sess_destroy", "sess_gc" ) ;

Session ID Propagation

PHP 4 sessions support the following methods of passing the session ID:

  • Cookies ( default)
  • GET/POST
  • Hidden in the URL, either done manually or by automatic URL rewriting

Cookies are the default way to pass the session ID between pages. If you're happy with cookies, you don't have to worry about any special configuration. Another common way is to pass the ID is with GET/POST. Your URL would then be similar to script.php3 ? < ; session-name > = < ; session-id > . You can create such URLs by using the global constant SID:

printf ( '< ; a href="script.php? % s" > Link< ; /a > ', SID ) ;

Automatic URL rewriting is one of the very cool new features of PHP 4, allowing you to add the session ID to all the links within the page. To enable it, you need to configure PHP with

--enable-trans-id and recompile it. Then the session ID in the form = will be added to all relative links within your PHP-parsed pages. While this is a handy feature, it should be used with caution on high-performance sites. PHP has to look at each individual page, analyze it to see whether it contains relative links, and eventually add the ID to the links. This obviously introduces a performance penalty. Cookies, on the other hand, are set only once, avoiding the overhead of URL rewriting.

Example Code

The following is completely commented sample code. The popular hangman game is a great way to show how to use persistent variables. In this game, the computer chooses a random five-letter word and the player needs to figure it out by choosing letters to fill in. The user has only six guesses, or he'll end up on the gallows. We've chosen a database of common English female names, but you're free to use any other word list you want. Simply replace the existing words.txt file with your own version ; the only constraint is that each word must be on one line. Of course, this game cannot work without keeping state information. the player would always have five tries left, could never win without guessing the whole word correctly the first time, and generally, it wouldn't be much fun at all. If you think for a moment about the game's logic, you'll come up with three variables that need to be remembered from request to request:

  • The random word the user is to guess.
  • The characters the user has already guessed.
  • How many tries have failed so far.

The number of tries could be calculated with some tricks, but for clarity's sake this example stores it separately.

The example uses the default method of passing the session ID, cookies, so no tricks are necessary in the use of the session library. It follows the following program logic:

  • The three session variables are registered.
  • The script checks whether the form is invoked by a Post operation, meaning that the user has submitted a guess.
  • If this is the case, the script processes the input, updates the game state ( won, lost, correct guess, incorrect guess) and outputs a message accordingly.
  • If not, a new game is started, and a random word is extracted from the file words.txt.
  • A string, which hides the not-yet-guessed characters in the guess word, is created and printed to the browser.
  • The rest of the page is drawn.

All HTML output is handled by a separate template class. This allows the separation of code and layout needed often in professional web applications, but that will be the topic of another tutorial.

The code is simple, but easy to understand therefore and can give you a good idea how and for what tasks to use the session library. And you could even extend the application ; for example, you could calculate the player's highscore and store it in a database. If you do such fancy things with this game, be sure to let us know and we'd be happy to feature your versions on our web site!

NOTE:

The code contains comments preceded by "//" marks.

require ( "EasyTemplate.inc.php3" ) ;

define ( "HANG_MAX_TRIES", 6 ) ;

$ guess = strtolower ( $ guess ) ;

function

hang_get_random_line ( $ file )

{

// Try to open the file

if ( !file_exists ( $ file ) | | ! ( $ fp = fopen ( $ file, "r" )))

{

die (

"Could not open file \" $ file\" fo reading." ) ;

}

// Get file size

$ size = filesize ( $ file ) ;

// Init randomizer, get a random value in the range 0..filesize

srand ( ( double)microtime ( )*1000000 ) ;

$ randval = rand ( 0, $ size ) ;

// Seek to a random position in the file - possible this is EOF.

fseek ( $ fp, $ randval ) ;

// Get line - possibly empty ( if at EOF)

$ line = trim ( fgets ( $ fp, 1024 ) ) ;

$ line = trim ( fgets ( $ fp, 1024 ) ) ;

// Close file

fclose ( $ fp ) ;

// If line was empty, try again

if ( empty ( $ line ))

{

$ line = hang_get_random_line ( $ file ) ;

}

// Return random line

return ( $ line ) ;

}

// Initialize session

session_start ( ) ;

// Register our variables

session_register ( "num_of_tries" ) ;

session_register ( "guessed_chars" ) ;

session_register ( "word" ) ;

if (

$ REQUEST_METHOD == "POST" && !empty ( $ word ) && !empty ( $ guess ))

{

$ guessed_chars[] = $ guess ;

if ( !

strstr ( $ word, $ guess