Sessions


Along with the ability to send tidbits of information between client and server, PHP provides us with the ability to follow the progress of a particular client (user) as he works through our web application. This is done with a feature called sessions and is integrated directly into PHP, requiring no extra compilation or installation efforts.

Sessions work by using session cookies (cookies with an expiration of 0) and associating a unique identifier called a session ID (a long series of alphanumeric characters) with the user or client. The web application stores data along with these sessions and has the data move from page to page with the user.

We begin by showing the basic usage of sessions in PHP5.

Basic Usage

A session is initiated by calling the session_start function near the top of every script for which you wish the session to be active or valid. By calling this function, you can make sure that a session cookie has been assigned to the client and initializes the session storage for the session. If this is the first time the session is started, new storage is created for the session, and the $_SESSION superglobal array representing this session storage is empty. On the other hand, if there is a session already associated with this visitor when session_start is called, the $_SESSION array has any session data as members.

A basic example that shows you how to use sessions is one in which we create a session and store a single variable, such as one counting how many visits the user has made to a particular page. The code is as follows:

 <?php session_start(); // create session variable if it doesn't exist yet if (isset($_SESSION['counter']))   $_SESSION['counter'] ++; else   $_SESSION['counter'] = 1; var_dump($_SESSION); echo "<br/>\n"; var_dump(session_id()); echo "<br/>\n"; var_dump(session_name()); echo "<br/>\n"; var_dump(session_get_cookie_params()); echo "<br/>\n"; ?> 

You should see that the counter variable inside of the $_SESSION array is increased by one every time you reload this page in the client web browser. The output will look roughly as follows:

 array(1) { ["counter"]=> int(7) } string(32) "2412e9b2ac02dac44eae25d06b8601c7"  string(9) "PHPSESSID"  array(4) { ["lifetime"]=> int(0) ["path"]=> string(1) "/"            ["domain"]=> string(0) " ["secure"]=> bool(false)} 

You can access the session ID by calling the session_id function. The session_name function shows you the name of the session and is simply the name of the cookie that PHP returns to the client to hold the session ID. It can be set in php.ini (see the "Configuring PHP for Sessions" section), or it can be individually set by calling this function with a new name. Finally, the session_get_cookie_params shows you the details of the session cookie associated with the session.

A more interesting example of sessions shows how data is transmitted across pages. If we were to have the following script that holds the data of a visitor to our web application

 <?php     /* file1.php */ session_name('SESSIONSAMPLE'); session_start(); // // we'll just put some sample info in here to show how  // data is carried between pages. // $_SESSION['user_name'] = 'chippy'; $_SESSION['full_name'] = 'Chippy the Chipmunk'; $_SESSION['last_visit'] = '2 weeks ago'; $_SESSION['favourites'] = 'Books'; print_r($_SESSION); echo "<br/>\n"; print_r(session_id()); echo "<br/>\n"; print_r(session_name()); echo "<br/>\n"; print_r(session_get_cookie_params()); echo "<br/>\n"; ?> <br/> <a href="file2.php">Click Here for Next Page</a> <br/> 

The script would produce output similar to the following:

 Array ( [user_name] => chippy          [full_name] => Chippy the Chipmunk          [last_visit] => 2 weeks ago [favourites] => Books ) 68cf093a297ec5aa4f6cb608cb667c86 SESSIONSAMPLE Array ( [lifetime] => 0 [path] => / [domain] => [secure] => ) Click Here for Next Page 

The code for the second script in the series is as follows:

 <?php     /* file2.php */ session_name('SESSIONSAMPLE'); session_start(); // // show the session data as we have it thus far. // print_r($_SESSION); echo "<br/>\n"; print_r(session_id()); echo "<br/>\n"; print_r(session_name()); echo "<br/>\n"; print_r(session_get_cookie_params()); echo "<br/>\n"; ?> 

The output should be identical to that shown in the first page (without the hyperlink at the bottom). The session carries all of the data along with it.

Configuring PHP for Sessions

A number of configuration options in php.ini can control the way in which PHP sessions operate. Here are the more interesting of these options. (Refer to the PHP Manual for other options.)

  • session.auto_start We fibbed when we said that you must use session_start to initiate sessions in PHP. If the configuration option is set to 1 (it defaults to 0), a session is initiated every time a new page is requested in PHP. We can leave this with its default value since there are times when we do not want to use sessions. This setting also interferes with some aspects of variable storage, which is mentioned in the "Storing Data with the Session" section.

  • session.name This is the name of the cookie sent to the client browser to hold the session ID. All users connected to our page(s) using sessions get the same session name, but different session IDs. The default value is "PHPSESSID".

  • session.save_handler This controls how session data is stored. The default value of "files" indicates that an internal PHP storage mechanism is used to store session data in a file on the local server file system. You can write your own custom session data storage mechanism with this, which is covered in the "How Session Storage Works" section.

  • session.save_path If you are using the PHP session data storage mechanism (when session.save_handler is set to "files"), this specifies the directory into which session data files are stored. It defaults on Unix machines to /tmp and on Windows machines to C:\php\sessiondata.

  • session.gc_maxlifetime This specifies, in seconds, how long the session should be considered valid. After this time passes, the session is marked for destruction. The default value is 1440 seconds, or 24 minutes.

  • session.gc_probability, session.gc_divisor These two options control the garbage collection of sessions. When a new page request is received, PHP sometimes goes through all the session files and cleans up (destroys) those that have expired. The chance of this cleanup occurring for any given request is as follows:

     ( session.gc_probability / session.gc_divisor ) x 100% 

    The default values of this are 1 and 100, which you might want to change to reflect the traffic that comes to your web site. If you have very high traffic, then the default value of starting garbage collection for 1 percent of pages is sufficient. However, if the web application has reasonably low traffic, then you might want to consider a 710 percent value.

  • session.cookie_lifetime, session.cookie_path, session.cookie_domain, session.cookie_secure These variables correspond to the extra parameters to the setcookie function and let you control or restrict the applicability of the session's cookie. By default, it is created with an expiration of 0 and is valid across the entire domain.

  • session.use_trans_sid PHP has the ability to detect clients whose cookies are disabled and put the session ID in the URL as a GET parameter. (This is discussed in the "How the Session ID Is Transmitted" section.) The default for this is off (0).

  • session.use_cookies This controls whether PHP can use cookies to transmit the session ID, the default of which is TRue (1).

  • session.use_only_cookies Because of concerns with putting the session ID as a GET parameter, you may want to explicitly prohibit PHP from using anything other than cookies. You can do this by setting this option to 1 (default is 0).

You can configure many of these variables for individual pages and sites within a virtual server (where you may not have access to php.ini) by using the ini_set function in PHP. This function takes the name of the setting and the new value, such as

 ini_set('session.save_path', '/home/webapps/sess_data'); 

Many of these options were not configurable at runtime in this fashion until the release of PHP5.

How the Session ID Is Transmitted

Session IDs are sent around in a cookie whose name is that of the session name, as follows:

 Set-Cookie: PHPSESSID=2412e9b2ac02dac44eae25d06b8601c7; path=/ 

When you next call session_start, PHP gets the session ID from the cookie and looks in its store of session data for things associated with the given ID. If none is found, a new set of data is created.

However, some users configure their browsers not to accept cookies. Given that your entire sessions mechanism operates on the basis of cookies, this creates a problem.

PHP has a solution to work around this. When it detects that cookies do not work, it can put the session ID as a parameter in URLs accessed by the page. There is code that processes the output you generate with your page and replaces markup tags, such as <a href=''> and <frame src='/books/3/445/1/html/2/'>, to add the session name and session ID to these as a parameter:

 <a href='adduser.php?PHPSESSID=68cf093a2ec5aa4f6cb608cb66786'> 

This has the advantage of letting us always follow a user's progress through our web application, even in situations when cookies are disabled.

To enable this functionality, you must turn on the session.use_trans_sid configuration option in php.ini (set it to 1) and make sure that the session.use_only_cookies configuration option is set to 0.

When the session.use_trans_sid is turned on and a user connects with cookies disabled, PHP knows to start sending the session ID information via URLs instead of cookies. PHP knows which URLs to modify by looking at the url_rewriter.tags configuration option in php.ini. This is a comma-separated list of tags that PHP modifies seamlessly to include session information.

Even though this feature is handy, it is one we will not often use. Having session IDs in plain text cookies already creates a security problem for us, as we will see in the "Session Security" section. We are sure that the session cookies are deleted when the user closes the browser. However, if you send session ID information in URLs, it is stored in browser histories and is easily viewed by subsequent users of the same computer.

Storing Data with the Session

A more powerful feature of sessions in PHP is the ability to carry data around with the user as he travels from page to page in your web application. We have shown previously some basic examples of where data can be placed in the $_SESSIONS superglobal. This is saved every time script execution terminates and restored when the next session with the same session ID is created. Where and how this data is stored depends on the session.save_handler and session.save_path settings in php.ini.

For example, let us look at an example where we put some user information into the session data to save database requests later on.

 $row = $db_results->fetch_row_assoc(); $_SESSION['user_name'] = $row['user_name']; $_SESSION['full_name'] = $row['full_name']; $_SESSION['user_id'] = $row['user_id']; 

Querying the data is simply a matter of accessing this array again:

   $query = <<<EOQ SELECT * FROM Messages  WHERE author_id = '{$_SESSION['user_id']}' EOQ; 

However, PHP is so powerful that we are not restricted to trivial variable assignments. In fact, PHP contains a robust object serialization mechanism through which we can save complex data types, such as arrays and objects, to the $_SESSION array. These are saved (serialized) for us when the page exits and restored for us when the session next starts.

Consider the following declaration of a UserInfo class:

 <?php      /* userinfo.inc */ class UserInfo {   private $user_id;   private $userName;   private $fullName;   private $address;   private $birthDate;   function __construct($in_user_id, $in_userName,                        $in_fullName, $in_address,                        $in_birthDate)   {     $this->user_id = $in_user_id;     $this->userName = $in_userName;     $this->fullName = $in_fullName;     $this->address = $in_address;     $this->birthDate = $in_birthDate;   }   public function get_UserID() { return $this->user_id; }   public function get_UserName() { return $this->userName; }   public function get_FullName() { return $this->fullName; }   public function get_Address() { return $this->address; }   public function get_BirthDate() { return $this->birthDate; } } ?> 

We can make a page that creates one of these objects and saves it to the $_SESSION array:

 <?php      /* file1.php */ require_once('userinfo.inc'); session.name('OBJECTSESSION'); session_start(); $user = new UserInfo(123123, 'Chippy', 'Chippy the Chipmunk',                      '123 Happy Oak Tree Lane',                      '2003/04/12'); $_SESSION['current_user'] = $user; var_export($_SESSION); echo "<br/><br/>\n"; ?> <br/> <a href="file2.php">Click For Next Page</a> <br/> 

We will see the following output from this page:

 array ( 'current_user' => class UserInfo {           private $user_id = 123123;          private $userName = 'Chippy';          private $fullName = 'Chippy the Chipmunk';          private $address = '123 Happy Oak Tree Lane';          private $birthDate = '2003/04/12'; }, ) Click For Next Page 

When we go to our next page, which wants to access the data associated with the session

 <?php      /* file2.php */ require_once('userinfo.inc'); session.name('OBJECTSESSION'); session_start(); // de-serialize the object $user = $_SESSION['current_user']; echo "I got my \$user back correctly:<br/>\n"; echo '<b>User Name</b>: ' . $user->get_UserName() . "<br/>\n"; echo '<b>Full Name</b>: ' . $user->get_FullName() . "<br/>\n"; echo '<b>Address</b>: ' . $user->get_Address() . "<br/>\n"; echo '<b>Birth Date</b>: ' . $user->get_BirthDate() . "<br/>\n"; echo "<br/><br/>\n"; var_dump($_SESSION); echo "<br/>\n"; ?> 

we see that PHP correctly rebuilds the UserInfo instance for us. The output of the second page is

 I got my $user back correctly: User Name: Chippy Full Name: Chippy the Chipmunk Address: 123 Happy Oak Tree Lane Birth Date: 2003/04/12 array(1) {["current_user"]=> object(UserInfo)#1 (5) {             ["user_id:private"]=> int(123123)             ["userName:private"]=> string(6) "Chippy"             ["fullName:private"]=> string(19) "Chippy the Chipmunk"             ["address:private"]=> string(23) "123 Happy Oak Tree Lane"             ["birthDate:private"]=> string(10) "2003/04/12" } } 

The ability to save objects in our session data is truly compelling and something we will endeavor to use as much as possible.

The one caveat to serializing objects is that you must have the class declaration loaded in before the session_start call is made. If you do not, then PHP does not know the structure of the class and cannot properly deserialize the object for us. Thus, the following code snippet is problematic:

 <?php session.name('OBJECTSESSION'); session_start(); require_once('userinfo.inc'); ?> 

This generates the following (rather verbose) error:

 Fatal error: main() [function.main]: The script tried to execute   a method or access a property of an incomplete object. Please   ensure that the class definition "UserInfo" of the object you   are trying to operate on was loaded _before_ unserialize()   gets called or provide a __autoload() function to load the   class definition in   /home/httpd/www/sessions/file2.php on line 12 

It is for this reason that we cannot use session.auto_start if we want to serialize objects in our session data. Auto-started sessions are begun before the first line of code executes in our script files, which gives us no opportunity to load our class declarations. By using session.auto_start, we cannot save objects into the session data.

Finally, we cannot use the object serialization functionality and sessions to store references (such as database connections and file handles). As mentioned in Chapter 2, "The PHP Language," these are only handles in PHP, and the underlying data is not something that the language engine readily has access to.

Page Caching

One of the concerns we might have with session data is browser caching. This is a feature by which the client web browser stores copies of downloaded pages on the local machine to save the trouble of re-fetching them. For static content, this saves both time and net work bandwidth since the user probably has much of a given page and its contents on the local machine. Other pieces of network equipment might even get in on the action. Some networks have proxy servers that cache HTML data, and larger web sites might choose to have dedicated cache machines whose sole job is to reduce the amount of traffic going to the web application servers.

However, you might not want this to happen for more dynamic or sensitive content (such as bank account information). When we are transmitting this sort of data, we want a way to tell the client browser (and other devices along the way) to avoid caching them. If there were some degree of granularity to this, too, we would be even happier.

Shockingly, this functionality exists. A number of HTTP headers (sent from PHP via the header function) can control this. The Cache-Control and Expires headers are used specifically for this. The only downside is that their exact usage and functionality can be arcane and difficult to understand.

Thus, PHP provides a function called session_cache_limiter for pages that are being managed through sessions (where you are likely to have sensitive data). When it is called with no arguments, this function tells us what current caching scheme is being used. When it is called with a parameter, it sets the scheme for the current output page to that value.

 $cache = session_cache_limiter(); session_cache_limiter('private'); session_start(); echo "Old Caching was:  $cache<br/>\n"; 

The most common values and return values for this function are

  • public This indicates that anybody may get involved in caching of both this page and its associated content (such as Cascading Style Sheets files, associated JavaScript files, or image files). This is used best with static content.

  • private This tells the client browser that it may cache data in this page, including associated content, but that other devices involved in caching (such as proxy servers and network devices) should not attempt it. This is more suited for somewhat sensitive content of a static nature.

  • nocache (default value) This tells any devices along the way that they should not attempt to cache the page contents (although associated content, such as scripts, style sheets, and images, may still be stored). This is well-suited for sensitive or dynamic content and has the advantage of leaving expensive image and style sheet files to be cached.

  • no-store This instructs all devices and computers not to cache either the page content or any of its associated files.

You can also control how long pages are stored in the various caches (for caching schemes that permit some degree of storage). This is known as cache expiration, and it is controlled within sessions via the session_cache_expire function. This returns the current value of the cache expiration timeout, specified in minutes. When it has passed, a parameter sets the new timeout to that value. The default value is 3 hours (180 minutes).

 <?php   $timeout = session_cache_expire();   session_cache_expire(15);  // reasonably dynamic content   session_start();   echo "The old cache expire timeout: {$timeout}min.<br/>\n";   // etc... ?> 

By using some degree of caching with a shorter cache timeout, you may realize some of the benefits of caching without sacrificing any of the dynamic nature of your application.

Finally, both of these options may be set in php.ini instead of at the top of each page within a session. When set in the configuration file, all output generated after a session_start function call will have the given cache_limiter and cache_expire values.

The two options are

  • session.cache_limiter Defaults to "nocache".

  • session.cache_expire The number of minutes before a page is considered expired. The default value is 180.

Destroying Sessions

As much as we would like to think that our web applications are so well-written and fun that people would never want to leave, there comes a time when users will want to log off or terminate their sessions (if they are using a public computer). For these cases, we need a way to remove the data associated with a session and a means for eliminating the session and its session ID.

There are three separate parts to destroying a session, each of which requires a slightly different piece of code. First, we need to destroy the session data that is stored by default in a file on the server's hard disk. This is done via the session_destroy function.

The second step is to destroy the actual session, which is done by eliminating the session cookie. By calling the setcookie function with the name of the session, we can specify a time in the past to delete the cookie from the client's machine. If you do not perform this second step, the user would still send his session cookie with any further requests to this site, and any subsequent calls to session_start would give him the same session ID.

Our final step is to destroy the $_SESSION superglobal array to remove any data associated with the session. This is done by assigning $_SESSION a new value.

These three steps put together give us the following code:

 <?php   //   // 1. destroy the session data on disk.   //   session_destroy();   //   // 2. delete the session cookie.   //   setcookie(session_name(), '', time()  3600);   //   // 3. destroy the $_SESSION superglobal array.   //   $_SESSION = array(); ?> 

After both destroying the session data and deleting the session cookie, any new call to session_start correctly generates a new session ID and data storage.

How Session Storage Works

Throughout the discussion on sessions, we have alluded to session storage and even discussed the session.save_handler and session.save_path configuration options in php.ini. We will now examine what these are for and what options we have for expanding upon their functionality.

By default, PHP writes session data into files and puts it in the location specified by the session.save_path configuration option. When a new session is created and a session ID assigned, PHP writes a file into that directory with a name starting with sess_ and ending in the session ID (for example, sess_2412e9b2ac02dac44eae25d06b8601c7). Every time a page operating under that session finishes executing, PHP writes out the data in the $_SESSION array into that file. The next time the same session is started, PHP loads the data from the file and re-creates the array.

As we also mentioned previously, PHP periodically looks through these files (the exact periodicity is controllable via the session.gc_probability and session.gc_divisor configuration options) for expired sessions and deletes them.

Apart from the security concerns addressed in the "Session Security" section, this mechanism works quite well and is well-suited to small- and medium-sized web applications. However, a problem arises when we want to write large web applications and start sharing the load across multiple servers. (The means of how that is done are beyond the scope of this book.) In this case, different requests for the same session might go to different servers. If session data remains in a storage file on one of those servers, we might find ourselves unable to access it from any of the others!

One possible solution would be to investigate network-based file systems, such as the Network File System (NFS) for Unix or Microsoft SMB Shares for Windows-based solutions. Unfortunately, both of these systems have incomplete file-locking solutions, and we would thus find ourselves worried about concurrent access to directories and files.

It is far better to use a database for storage since databases have extremely desirable reliability, concurrency, and transaction capabilities. The only problem remaining is finding a way for PHP to let us use the database instead of the default file-based session storage mechanism.

It was for exactly this purpose that the session_set_save_handler function was created. This function takes as arguments the names of six functions that should be used for session handling. These six functions are as follows:

  • open This function is used to begin or "open" the session storage mechanism. It must return trUE on success, and FALSE on failure.

  • close This closes and terminates the session storage operation. It returns trUE on success, and FALSE on failure.

  • read This function is used when you load the data for a session from storage. This is a large chunk of text that PHP uses to reconstruct the session data. The function is given the session ID for which to retrieve data as a parameter. It returns the requested session data on success and "" on failure.

  • write This writes all of the data for the given session ID to storage. The data is written in one large chunk and is in text format. The function returns trUE on success and FALSE on failure.

  • destroy This function is called when the data associated with the specified session ID is to be destroyed. It returns trUE when this has been accomplished.

  • gc This function is expected to garbage collect the data in the storage system. The number of seconds that session data should be considered valid is passed as a parameter to this function. The function returns trUE on success.

To show how this works, we have written a simple example of how we might go about storing this information in a MySQL database using the mysqli class. To make this work, we need to create a new table in your database with the following statement:

 CREATE TABLE SessionData (   session_id VARCHAR(255) PRIMARY KEY,   last_update DATETIME,   session_data TEXT ); 

We will then write a DatabaseSesssionStorage class to implement the methods required to implement our session storage. This is a class with only static methods on it. We will have a static member variable where we store a database connection, as follows:

 class DatabaseSessionStorage {   private static $s_conn = NULL;   // method next. } 

Our open and close methods create and close a connection to the database, as follows:

 public static function open (   $save_path,   $session_name ) {   self::$s_conn = @new mysqli('localhost', 'message_user',                                'secret',                                'MessageBoard');   if (mysqli_connect_errno() != 0)     return FALSE;   else     return TRUE; } public static function close() {   if (self::$s_conn !== NULL)   {     self::$s_conn->close();     self::$s_conn = NULL;   } } 

The read function is easy to implementwe simply look for data in the table with the given session ID. For the write function, we write the data for the given session ID into the table. However, we will quickly delete any existing records with the same session ID:

   public static function read($id)   {     $id = self::$s_conn->real_escape_string($id);     $query = <<<EOQUERY SELECT * FROM SessionData WHERE session_id='$id' EOQUERY;     $result = @self::$s_conn->query($query);     if (self::$s_conn->errno != 0)     {       $err = self::$s_conn->error;       return "";     }     if (($row = @$result->fetch_assoc()) !== NULL)       $data = $row['session_data'];     else       $data = "";     $result->close();     return $data;   }   public static function write($id, $session_data)   {     $id = self::$s_conn->real_escape_string($id);     $query = "DELETE FROM SessionData WHERE session_id='$id'";     $result = @self::$s_conn->query($query);     if (self::$s_conn->errno != 0)       return FALSE;     $session_data = self::$s_conn->real_escape_string($session_data);     $query = <<<EOQ INSERT INTO SessionData VALUES('$id', NOW(), '$session_data') EOQ;     $result = @self::$s_conn->query($query);     if (self::$s_conn->errno != 0)       return FALSE;     else       return TRUE;   } 

Finally, the destroy function deletes any records with the given session ID, while the gc function deletes any that are older than the current time minus the maxLifeTime (in seconds) that PHP has given us as the parameter:

   public static function destroy($id)   {     $id = self::$s_conn->real_escape_string($id);     $query = "DELETE FROM SessionData WHERE session_id='$id'";     $result = @self::$s_conn->query($query);     if (self::$s_conn->errno != 0)       return FALSE;     else       return TRUE;   }   public static function gc($maxLifeTime)   {     $maxDate = date('Y-m-d H:m:s', time() - $maxLifeTime);     $query = <<<EOQUERY DELETE FROM SessionData WHERE last_update < '$maxDate' EOQUERY;     $result = @self::$s_conn->query($query);     if (self::$s_conn->errno != 0)       return FALSE;     return TRUE;   } 

The only trick to this system we have devised is how exactly we tell PHP to use our static functions for the save handler. The support in PHP5 for object-oriented programming is so robust that instead of a string with the name of a function to be called back, we can optionally pass PHP an array instead. This has two values in it that represent the class name and method name of a static function. For example, to use DatabaseSessionStorage::open as a callback, we just pass PHP:

 array('DatabaseSessionStorage', 'open') 

So, as our final step in our database-based session persistence, we call the session_set_save_handler function to tell it to start using our new static member functions:

 session_set_save_handler(     array('DatabaseSessionStorage', 'open'),     array('DatabaseSessionStorage', 'close'),     array('DatabaseSessionStorage', 'read'),     array('DatabaseSessionStorage', 'write'),     array('DatabaseSessionStorage', 'destroy'),     array('DatabaseSessionStorage', 'gc') ); 

This gives us a more robust solution to session data storage for our pages. If we put the DatabaseSessionStorage class and the session_set_save_handler function call into a single file for inclusion, we can get this function in each of our script files by adding the following at the top:

 <?php   require_once('db_sessions.inc'); // incl. set_save_handler   session_start();   // etc. ... ?> 




Core Web Application Development With PHP And MYSQL
Core Web Application Development with PHP and MySQL
ISBN: 0131867164
EAN: 2147483647
Year: 2005
Pages: 255

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net