With the release of PHP4, session management was introduced as an extension to the PHP language. PHP provides several session-related functions, and developing applications that use PHP sessions is straightforward. The three important features of session management are mostly taken care of by the PHP scripting engine.
In this section, we present how to use PHP sessions, showing how sessions are started and ended and how session variables are used. We list the PHP functions for building session-based web applications. Because not all browsers support cookies, and some users actively disable them, we describe how to use PHP sessions without relying on cookies. Finally, we show how to configure PHP session management with a discussion on the garbage collection used to remove old sessions and other configuration parameters.
An overview of PHP session management is shown in Figure 8-1. When a user first enters the session-based application by making a request to a page that starts a session, PHP generates a session ID and creates a file that stores the session-related variables. PHP sets a cookie to hold the session ID in the response the script generates. The browser then records the cookie and includes it in subsequent requests. In the example shown in Figure 8-1, the script welcome.php records session variables in the session store, and a request to next.php then has access to those variables because of the session ID.
The out-of-the-box configuration of PHP session management uses disk-based files to store session variables. Using files as the session store is adequate for most applications in which the numbers of concurrent sessions are limited. A more scalable solution that uses a MySQL database as a session store is provided in Appendix D.
PHP provides a session_start( ) function that creates a new session and subsequently identifies and establishes an existing one. Either way, a call to the session_start( ) function initializes a session.
The first time a PHP script calls session_start( ), a session identifier is generated, and, by default, a Set-Cookie header field is included in the response. The response sets up a session cookie in the browser with the name PHPSESSID and the value of the session identifier. The PHP session management automatically includes the cookie without the need to call to the setcookie( ) or header( ) functions.
The session identifier (ID) is a random string of 32 hexadecimal digits, such as fcc17f071bca9bf7f85ca281094390b4. As with other cookies, the value of the session ID is made available to PHP scripts in the $HTTP_COOKIE_VARS associative array and in the $PHPSESSID variable.
When a new session is started, PHP creates a session file. With the default configuration, session files are written in the /tmp directory using the session identifier, prefixed with sess_, for the filename. The filename associated with our example session ID is /tmp/sess_fcc17f071bca9bf7f85ca281094390b4.
If a call is made to session_start( ), and the request contains the PHPSESSID cookie, PHP attempts to find the session file and initialize the associated session variables as discussed in the next section. However, if the identified session file can't be found, session_start( ) creates an empty session file.
Variables need to be registered with the session_register( ) function that's used in a session. If a session has not been initialized, the session_register( ) function calls session_start( ) to open the session file. Variables can be registered—added to the session file—with the session_register( ) call as follows:
// Register the variable named "foo" session_register("foo"); $foo = "bar";
Note that it is the name of the variable that is passed to the session_register( ) function, not the variable itself. Once registered, session variables are made persistent and are available to scripts that initialize the session. PHP tracks the values of session variables and saves their values to the session file: there is no need to explicitly save a session variable before a script ends. In the previous example, the variable $foo is automatically saved in the session store with its value bar.
Variables can be removed from a session with the session_unregister( ) function call; again, the name of the variable is passed as the argument, not the variable itself. A variable that is unregistered is no longer available to other scripts that initialize the session. However, the variable is still available to the rest of the script immediately after the session_unregister( ) function call.
Scripts that initialize a session have access to the session variables through the associative array $HTTP_SESSION_VARS, and PHP automatically initializes the named session variables if register_globals is enabled.
Example 8-2 shows a simple script that registers two variables: an integer $count, which is incremented each time the script is called, and $start, which is set to the current time from the library function time( ) when the session is first initialized. The script tests if the variable $count has been registered to determine if a new session has been created. If the variable $count has been registered already, the script increments its value.
Do not use the existence of $PHPSESSID as indicative of a new session, or as a method to access the session ID. The first time a script is called and the session is created, the PHPSESSID cookie may not be set. Only subsequent requests are guaranteed to contain the PHPSESSID cookie. PHP provides a session_id( ) function that returns the session ID for the initialized session.
The script shown in Example 8-2 displays both variables: $count shows how many times the script has been called, and time( ) - $start shows how many seconds the session has lasted.
<?php // Initialize a session. This call either creates // a new session or re-establishes an existing one. session_start( ); // If this is a new session, then the variable // $count will not be registered if (!session_is_registered("count")) { session_register("count"); session_register("start"); $count = 0; $start = time( ); } else { $count++; } $sessionId = session_id( ); ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <html> <head><title>Sessions</title></head> <body> <p>This page points at a session (<?=$sessionId?>) <br>count = <?=$count?>. <br>start = <?=$start?>. <p>This session has lasted <?php $duration = time( ) - $start; echo "$duration"; ?> seconds. </body> </html>
Session variables can be of the type Boolean, integer, double, string, object, or arrays of those variable types. Care must be taken when using object session variables, because PHP needs access to the class definitions of registered objects when initializing an existing session. If objects are to be stored as session variables, you should include class definitions for those objects in all scripts that initialize sessions, whether the scripts use the class or not. Objects and classes are described in Chapter 2.
PHP stores session variables in the session file by serializing the values. The serialized representation of a variable includes the name, the type, and the value as a stream of characters suitable for writing to a file. Here's an example of a file that was created when the script shown in Example 8-2 was run several times:
count|i:6;start|i:986096496;
A PHP developer need not worry how serialization occurs; PHP session management takes care of reading and writing session variables automatically.
At some point in an application, sessions may need to be destroyed. For example, when a user logs out of an application, a call to the session_destroy( ) function can be made. A call to session_destroy( ) removes the session file from the system but doesn't remove the PHPSESSID cookie from the browser.
Example 8-3 shows how the session_destroy( ) function is called. A session must be initialized before the session_destroy( ) call can be made. You should also test to see if $PHPSESSID is a set variable before killing the session. This prevents the code from creating a session, then immediately destroying it if the script is called without identifying a session. However, if the user has previously held a session cookie, PHP initializes the $PHPSESSID variable, and the code redundantly creates and destroys a session.
<?php // Only attempt to end the session if there // is a $PHPSESSID set by the request. if(isset($PHPSESSID)) { $message = "<p>End of session ($PHPSESSID)."; session_start( ); session_destroy( ); } else { $message = "<p>There was no session to destroy!"; } ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <html> <head><title>Sessions</title></head> <body> <?=$message?> </body> </html>
In this section we list the key functions used to build session-based applications in PHP. Greater control over sessions can be achieved through the configuration of PHP—as we discuss in the Section 8.3.8 section—or by using GET variables to encode the session ID, as discussed in the next section.
A change that can be made to the default PHP session management is to encode the $PHPSESSID value as an attribute in a GET or POST method request and avoid the need to set a cookie.
A simple experiment that illustrates what happens when users disable cookies is to request the script shown in Example 8-2 from a browser that has cookie support turned off. When repeated requests are made, the counter doesn't increment, and the session duration remains at zero seconds. Because a cookie isn't sent from the browser, the variable $PHPSESSID is never set. The other side effect is that each time the page is requested, a session file is created in the /tmp directory. Many users configure their browsers to not accept cookies, and session-based applications won't work unless they are written to handle the missing cookie.
The session identifier that would have been sent as a cookie in this experiment can be transmitted in a GET or POST method request. While the session_start( ) function can use $PHPSESSID set by either a GET or POST method request, it is more practical to use the GET variable. Using the POST variable leads to the reload problem described in Chapter 6. Continuing the experiment, requests that don't contain the cookie can identify an existing session by setting an attribute in a GET method request with the name PHPSESSID and the value of the session ID. For example, an initial request can be made to Example 8-1 with the URL:
http://localhost/example.8-1.php
This creates a session and an associated file such as:
/tmp/sess_be20081806199800da22e24081964000
Subsequent requests can be made that include the PHPSESSID:
http://localhost/example.8-1.php?PHPSESSID=be20081806199800da22e24081964000
The response shows the counter set to 1 and the correct session duration. Repeated requests to this URL behave as expected: the counter increments, and the calculated duration increases.
If you write session-based applications to use the URL to identify sessions, the application doesn't fail for users who don't allow cookies. Applications can use a test cookie to see if cookies are supported by the browser or just not use cookies at all.
WARNING: When register_globals is enabled, and both a cookie and GET or POST are used to set the $PHPSESSID, the cookie wins. A GET or POST attribute value is overwritten by the value associated with the cookie because of the default order in which PHP initializes those variables.The safe way to read cookies and GET and POST attributes that have name conflicts is to use the $HTTP_COOKIE_VARS, $HTTP_GET_VARS, and $HTTP_POST_VARS arrays.
Another advantage of avoiding cookies is that some browsers, such as Netscape and Internet Explorer, share cookies across all instances of the program running for a particular user on the same machine. This behavior prevents a user from having multiple sessions with a web database application.
Scripts that generate embedded links to pages that use session variables need to include a GET attribute named PHPSESSID in the URL. This can be done using the basic PHP string support and calls to session_id( ). For example:
<?php // Initialize the session session_start( ); // Generate the embedded URL to link to // a page that processes an order $orderUrl = "/order.php?PHPSESSID=" . session_id( ); ?> <a href="<?=$orderUrl ?>">Create Order</a>
To aid the creation of URLs that link to session-based scripts, PHP sets the constant SID that contains the session ID in the form suitable to use as a URL query string. If there is no session initialized, PHP sets the value of SID to be a blank string. If a session is initialized, it sets the SID to a string containing the session ID in the form:
PHPSESSID=be20081806199800da22e24081964000
By including the value of SID when URLs are constructed, the hypertext links correctly identify the session. A link that points to a script that expects a session ID can be encoded like this:
<?php // Initialize the session session_start( ); ?> <a href="/order.php?<?=SID?>">Create Order</a>
As an alternative to writing code to formulate the session ID into the URL, PHP includes a URL rewrite feature that automatically modifies reference URLs to include the session ID as a GET attribute. To activate this feature, PHP needs to be configured with - -enable-trans-id and then recompiled. Once URL rewrite is activated, PHP parses the HTML generated by scripts and automatically alters the embedded URLs to include the PHPSESSID query string. The URL rewrite feature has the disadvantage that extra processing is required to parse every generated page.
PHP session management can be instructed not to set the PHPSESSID cookie by changing the session.use_cookies parameter to 0 in the php.ini file. The session configuration parameters in the php.ini file are described in the later section Section 8.3.8.
While it is good practice to build applications that provide a way to end a session—with a script that makes a call to session_destroy( )—there is no guarantee that a user will log out by requesting the appropriate PHP script. PHP session management has a built-in garbage collection mechanism that ensures unused session files are eventually cleaned up. This is important for two reasons: it prevents the directory from filling up with session files that can cause performance to degrade and, more importantly, it reduces the risk of someone guessing session IDs and hijacking an old unused session.
There are two parameters that control garbage collection: session.gc_maxlifetime and session.gc_probability, both defined in the php.ini file. A garbage collection process is run when a session is initialized, for example, when session_start( ) is called. Each session is examined by the garbage collection process, and any sessions that have not been accessed for a specified period of time are removed. This period is specified as seconds of inactivity in the gc_maxlifetime parameter—the default value being 1,440 seconds. The file-based session management uses the update time of the file to determine the last access. To prevent the garbage collection process from removing active session files, PHP must modify the update time of the file when session variables are read, not just when they are written.
The garbage collection process can become expensive to run, especially in sites with high numbers of users, because the last-modified date of every session file must be examined. The second parameter gc_probability sets the percentage probability that the garbage collection process will be activated. A setting of 100% ensures that sessions are examined for garbage collection with every session initialization. The default value of 1% means that garbage collection occurs with a probability of 1 in 100.[10] Depending on the requirements, some figure between these two extremes balances the needs of the application and performance. Unless a site is receiving less that 1,000 hits per day, the probability should be set quite low. For example, an application that receives 1,000 hits in a 10-hour period with a gc_probability setting of 10% runs the garbage collection function, on average, once every 6 minutes. Setting the gc_probability too high adds unnecessary processing load on the server.
[10]Perhaps the gc_maxlifetime parameter should have been called gc_minlifetime, because the value represents the minimum time garbage collection permits an inactive session to exist. Remember that garbage collection is performed only when a request that initializes a session is made, and then only with the probability set by gc_probability.
When it is important to prevent users from accessing old sessions, the gc_probability should be increased. For example, the default session configuration sets up a cookie in the browser to be deleted when the browser program is terminated. This prevents a user from accidentally reconnecting to an old session. However, if the session ID is encoded into a URL, a bookmarked page can find an old session if it still exists. If session IDs are passed using the GET method, you should increase the probability of running garbage collection.
There are several parameters that can be manipulated to change the behavior of the PHP session management. These parameters are set in the php.ini file in the section headed [Session].
[11]The actual expiry of the cookie is performed by the browser, which compares the expiry date and time of the cookie with the client machine's date and time. If the date and time are incorrectly set on the client, a cookie might expire immediately or persist longer than expected.
Copyright © 2003 O'Reilly & Associates. All rights reserved.