Sams Teach Yourself PHP, MySQL and Apache All in One (2012)
Part III. Getting Involved with the Code
Chapter 12. Working with Cookies and User Sessions
In this chapter, you learn the following:
• How to store and retrieve cookie information
• What session variables are and how they work
• How to start or resume a session
• How to store variables in a session
• How to destroy a session
• How to unset session variables
PHP contains numerous functions for managing and keeping track of user information, including both simple cookies and all-encompassing user sessions. Sessions use techniques built in to the PHP language, making the act of saving state as easy as referencing a superglobal variable.
Introducing Cookies
You can use cookies within your PHP scripts to store small bits of information about a user. A cookie is a small amount of data stored by the user’s browser in compliance with a request from a server or script. A single host can request that up to 20 cookies be stored by a user’s browser. Each cookie consists of a name, value, and expiration date, as well as host and path information. The size of an individual cookie is limited to 4KB.
After a cookie is set, only the originating host can read the data, ensuring that the user’s privacy is respected. Furthermore, users can configure their browser to notify them upon receipt of all cookies, or even to refuse all cookie requests. For this reason, cookies should be used in moderation and should not be relied on as an essential element of an environment design without first warning users.
The Anatomy of a Cookie
A PHP script that sets a cookie might send headers that look something like this:
HTTP/1.1 200 OK
Date: Wed, 18 Jan 2012 10:50:58 GMT
Server: Apache/2.2.21 (Unix) PHP/5.4.0
X-Powered-By: PHP/5.4.0
Set-Cookie: vegetable=artichoke; path=/; domain=yourdomain.com
Connection: close
Content-Type: text/html
As you can see, this Set-Cookie header contains a name/value pair, a path, and a domain. If set, the expiration field provides the date at which the browser should “forget” the value of the cookie. If no expiration date is set, the cookie expires when the user’s session expires—that is, when he closes his browser.
The path and domain fields work together: The path is a directory found on the domain, below which the cookie should be sent back to the server. If the path is "/", which is common, that means the cookie can be read by any files below the document root. If the path is “/products/”, the cookie can be read only by files within the /products directory of the website.
The domain field represents the Internet domain from which cookie-based communication is allowed. For example, if your domain is www.yourdomain.com and you use www.yourdomain.com as the domain value for the cookie, the cookie will be valid only when browsing thewww.domain.com website. This could pose a problem if you send the user to some domain like www2.domain.com or billing.domain.com within the course of his browsing experience, because the original cookie will no longer work. Therefore, it is common simply to begin the value of the domain slot in cookie definitions with a dot, leaving off the host (for example, .domain.com). In this manner, the cookie is valid for all hosts on the domain. The domain cannot be different from the domain from which the cookie was sent; otherwise, the cookie will not function properly, if at all, or the web browser will refuse the cookie in its entirety.
Accessing Cookies
If your web browser is configured to store cookies, it keeps the cookie-based information until the expiration date. If the user points the browser at any page that matches the path and domain of the cookie, it resends the cookie to the server. The browser’s headers might look something like this:
GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like
Gecko) Chrome/16.0.912.75 Safari/535.7
Host: www.yourdomain.com
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*
Accept-Encoding: gzip
Accept-Language: en,pdf
Accept-Charset: iso-8859-1,*,utf-8
Cookie: vegetable=artichoke
A PHP script then has access to the cookie in the environment variable HTTP_COOKIE or as part of the $_COOKIE superglobal variable, which you may access three different ways:
echo $_SERVER['HTTP_COOKIE']; // will print "vegetable=artichoke"
echo getenv('HTTP_COOKIE'); // will print "vegetable=artichoke"
echo $_COOKIE['vegetable']; // will print "artichoke"
Setting a Cookie with PHP
You can set a cookie in a PHP script in two ways. First, you can use the header() function to set the Set-Cookie header. The header() function requires a string that is then included in the header section of the server response. Because headers are sent automatically for you, header() must be called before any output at all is sent to the browser:
header("Set-Cookie: vegetable=artichoke; expires=Thu, 19-Jan-12 14:39:58 GMT;
path=/; domain=yourdomain.com");
Although not difficult, this method of setting a cookie requires you to build a function to construct the header string. Although formatting the date as in this example and URL-encoding the name/value pair is not a particularly arduous task, it is a repetitive one because PHP provides a function that does just that: setcookie().
The setcookie() function does what its name suggests—it outputs a Set-Cookie header. For this reason, it should be called before any other content is sent to the browser. The function accepts the cookie name, cookie value, expiration date in UNIX epoch format, path, domain, and integer that should be set to 1 if the cookie is to be sent only over a secure connection. All arguments to this function are optional apart from the first (cookie name) parameter.
Listing 12.1 uses setcookie() to set a cookie.
Listing 12.1 Setting and Printing a Cookie Value
1: <?php
2: setcookie("vegetable", "artichoke", time()+3600, "/", ".yourdomain.com", 0);
3:
4: if (isset($_COOKIE['vegetable'])) {
5: echo "<p>Hello again! You have chosen: ".$_COOKIE['vegetable'].".</p>";
6: } else {
7: echo "<p>Hello, you. This may be your first visit.</p>";
8: }
9: ?>
Even though the listing sets the cookie (line 2) when the script is run for the first time, the $_COOKIE[ 'vegetable' ] variable is not created at this point. Because a cookie is read only when the browser sends it to the server, you cannot read it until the user revisits a page within this domain.
The cookie name is set to "vegetable" on line 2, and the cookie value to "artichoke". The time() function gets the current timestamp and adds 3600 to it (3,600 seconds in an hour). This total represents the expiration date. The code defines a path of "/", which means that a cookie should be sent for any page within this server environment. The domain argument is set to ".yourdomain.com" (you should make the change relevant to your own domain or leave it blank if you are working on localhost), which means that a cookie will be sent to any server in that group. Finally, the code passes 0 to setcookie(), signaling that cookies can be sent in an unsecure environment.
Passing setcookie() an empty string ("") for string arguments or 0 for integer fields causes these arguments to be skipped.
Note
With using a dynamically created expiration time in a cookie, as in Listing 12.1, note the expiration time is created by adding a certain number of seconds to the current system time of the machine running Apache and PHP. If this system clock is not accurate, the machine may send the cookie at an expiration time that has already passed.
You can view your cookies in most modern web browsers. Figure 12.1 shows the cookie information stored for Listing 12.1. The cookie name, content, and expiration date appear as expected; the domain name will differ when you run this script on your own domain.
Figure 12.1 Viewing a stored cookie in a web browser.
For more information on using cookies, and the setcookie() function in particular, see the PHP Manual entry at http://www.php.net/setcookie.
Deleting a Cookie with PHP
Officially, to delete a cookie, you call setcookie() with the name argument only:
setcookie("vegetable");
This approach does not always work well, however, and you should not rely on it. Instead, to delete a cookie, it is safest to set the cookie with a date that you are sure has already expired:
setcookie("vegetable", "", time()-60, "/", ".yourdomain.com", 0);
Also make sure that you pass setcookie() the same path, domain, and secure parameters as you did when originally setting the cookie.
Session Function Overview
Session functions provide a unique identifier to a user, which can then be used to store and acquire information linked to that ID. When a visitor accesses a session-enabled page, either a new identifier is allocated or the user is reassociated with one that was already established in a previous visit. Any variables that have been associated with the session become available to your code through the $_SESSION superglobal.
Session state is usually stored in a temporary file, although you can implement database storage or other server-side storage methods using a function called session_set_save_handler(). The use of session_set_save_handler() and a discussion about other advanced session functionality are beyond the scope of this book, but you can find more information in the PHP Manual section for sessions for all items not discussed here.
Starting a Session
To work with a session, you need to explicitly start or resume that session unless you have changed your php.ini configuration file. By default, sessions do not start automatically. If you want to start a session this way, you must find the following line in your php.ini file and change the value from 0 to 1 (and restart the web server):
session.auto_start = 0
By changing the value of session.auto_start to 1, you ensure that a session initiates for every PHP document. If you don’t change this setting, you need to call the session_start() function in each script.
After a session is started, you instantly have access to the user’s session ID via the session_id() function. The session_id() function enables you to either set or retrieve a session ID. Listing 12.2 starts a session and prints the session ID to the browser.
Listing 12.2 Starting or Resuming a Session
1: <?php
2: session_start();
3: echo "<p>Your session ID is ".session_id().".</p>";
4: ?>
When this script (let’s call it session_checkid.php) is run for the first time from a browser, a session ID is generated by the session_start() function call on line 2. If the script is later reloaded or revisited, the same session ID is allocated to the user. This action assumes that the user has cookies enabled. For example, when I run this script the first time, the output is as follows:
Your session ID is 8jou17in51d08e5onsjkbles16.
When I reload the page, the output is still
Your session ID is 8jou17in51d08e5onsjkbles16.
because I have cookies enabled and the session ID still exists.
Because start_session() attempts to set a cookie when initiating a session for the first time, it is imperative that you call this function before you output anything else at all to the browser. If you do not follow this rule, your session will not be set, and you will likely see warnings on your page.
Sessions remain current as long as the web browser is active. When the user restarts the browser, the cookie is no longer stored. You can change this behavior by altering the session.cookie_lifetime setting in your php.ini file. The default value is 0, but you can set an expiry period in seconds.
Working with Session Variables
Accessing a unique session identifier in each of your PHP documents is only the start of session functionality. When a session is started, you can store any number of variables in the $_SESSION superglobal and then access them on any session-enabled page.
Listing 12.3 adds two variables into the $_SESSION superglobal: product1 and product2 (lines 3 and 4).
Listing 12.3 Storing Variables in a Session
1: <?php
2: session_start();
3: $_SESSION['product1'] = "Sonic Screwdriver";
4: $_SESSION['product2'] = "HAL 2000";
5: echo "The products have been registered.";
6: ?>
The magic in Listing 12.3 will not become apparent until the user moves to a new page. Listing 12.4 creates a separate PHP script that accesses the variables stored in the $_SESSION superglobal.
Listing 12.4 Accessing Stored Session Variables
1: <?php
2: session_start();
3: ?>
4: <p>Your chosen products are:</p>
5: <ul>
6: <li><?php echo $_SESSION['product1']; ?></li>
7: <li><?php echo $_SESSION['product2']; ?></li>
8: </ul>
Figure 12.2 shows the output from Listing 12.4. As you can see, you have access to the $_SESSION['product1'] and $_SESSION['product2'] variables in an entirely new page.
Figure 12.2 Accessing stored session variables.
Although not a terribly interesting or useful example, the script does show how to access stored session variables. Behind the scenes, PHP writes information to a temporary file. You can find out where this file is being written on your system by using the session_save_path() function. This function optionally accepts a path to a directory and then writes all session files to it. If you pass it no arguments, it returns a string representing the current directory to which it saves session files. On my system, the following prints /tmp:
echo session_save_path();
A glance at my /tmp directory reveals a number of files with names like the following:
sess_fa963e3e49186764b0218e82d050de7b
sess_76cae8ac1231b11afa2c69935c11dd95
sess_bb50771a769c605ab77424d59c784ea0
Opening the file that matches the session ID I was allocated when I first ran Listing 12.2, I can see how the registered variables have been stored:
product1|s:17:"Sonic Screwdriver";product2|s:8:"HAL 2000";
When a value is placed in the $_SESSION superglobal, PHP writes the variable name and value to a file. This information can be read and the variables resurrected later—as you have already seen. After you add a variable to the $_SESSION superglobal, you can still change its value at any time during the execution of your script, but the altered value is not reflected in the global setting until you reassign the variable to the $_SESSION superglobal.
The example in Listing 12.3 demonstrates the process of adding variables to the $_SESSION superglobal. This example is not very flexible, however. Ideally, you should be able to register a varying number of values. You might want to let users pick products from a list, for example. In this case, you can use the serialize() function to store an array in your session.
Listing 12.5 creates a form that allows a user to choose multiple products. You use the session variables to create a rudimentary shopping cart.
Listing 12.5 Adding an Array Variable to a Session Variable
1: <?php
2: session_start();
3: ?>
4: <!DOCTYPE html>
5: <html>
6: <head>
7: <title>Storing an array with a session</title>
8: </head>
9: <body>
10: <h1>Product Choice Page</h1>
11: <?php
12: if (isset($_POST['form_products'])) {
13: if (!empty($_SESSION['products'])) {
14: $products = array_unique(
15: array_merge(unserialize($_SESSION['products']),
16: $_POST['form_products']));
17: $_SESSION['products'] = serialize($products);
18: } else {
19: $_SESSION['products'] = serialize($_POST['form_products']);
20: }
21: echo "<p>Your products have been registered!</p>";
22: }
23: ?>
24: <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
25: <p><label for="form_products">Select some products:</label><br />
26: <select id="form_products" name="form_products[]" multiple="multiple"
size="3">
27: <option value="Sonic Screwdriver">Sonic Screwdriver</option>
28: <option value="Hal 2000">Hal 2000</option>
29: <option value="Tardis">Tardis</option>
30: <option value="ORAC">ORAC</option>
31: <option value="Transporter bracelet">Transporter bracelet</option>
32: </select></p>
33: <button type="submit" name="submit" value="choose">Submit Form</button>
34: </form>
35: <p><a href="session1.php">go to content page</a></p>
36: </body>
37: </html>
The listing starts or resumes a session by calling session_start() on line 2. This call gives access to any previously set session variables. An HTML form begins on line 24 and, on line 26, creates a SELECT element named form_products[], which contains OPTION elements for a number of products.
Note
Remember that HTML form elements that allow multiple selections, such as check boxes and multiple select lists, should have square brackets appended to the value of their NAME attributes. This makes the user’s choices available to PHP in an array.
The block of PHP code beginning on line 11 tests for the presence of the $_POST['form_products'] array (line 12). If the variable is present, you can assume that the form has been submitted and information has already been stored in the $_SESSION superglobal.
Line 12 tests for an array called $_SESSION['products']. If the array exists, it was populated on a previous visit to this script, so the code merges it with the $_POST['form_products'] array, extracts the unique elements, and assigns the result back to the $products array (lines 14–16). Then the$products array is added to the $_SESSION superglobal on line 17.
Line 35 contains a link to another script, which will demonstrate access to the products the user has chosen. This new script is created in Listing 12.6, but in the meantime you can save the code in Listing 12.5 as arraysession.php.
Moving on to Listing 12.6, you see how to access the items stored in the session created in arraysession.php.
Listing 12.6 Accessing Session Variables
1: <?php
2: session_start();
3: ?>
4: <!DOCTYPE html>
5: <html>
6: <head>
7: <title>Accessing session variables</title>
8: </head>
9: <body>
10: <h1>Content Page</h1>
11: <?php
12: if (isset($_SESSION['products'])) {
13: echo "<strong>Your cart:</strong><ol>";
14: foreach (unserialize($_SESSION['products]) as $p) {
15: echo "<li>".$p."</li>;
16: }
17: echo "</ol>";
18: }
19: ?>
20: <p><a href="arraysession.php">return to product choice page</a></p>
21: </body>
22: </html>
Once again, session_start() resumes the session on line 2. Line 12 tests for the presence of the $_SESSION['products'] variable. If it exists, the variable is unserialized and looped through on lines 14–16, printing each of the user’s chosen items to the browser. Figure 12.3 shows an example of the output.
Figure 12.3 Accessing an array of session variables.
For a real shopping cart program, of course, you would keep product details in a database and test user input, rather than blindly store and present it, but Listings 12.5 and 12.6 demonstrate the ease with which you can use session functions to access array variables set in other pages.
Destroying Sessions and Unsetting Variables
You can use session_destroy() to end a session, erasing all session variables. The session_destroy() function requires no arguments. You should have an established session for this function to work as expected. The following code fragment resumes a session and abruptly destroys it:
session_start();
session_destroy();
When you move on to other pages that work with a session, the session you have destroyed will not be available to them, forcing them to initiate new sessions of their own. Any registered variables will be lost.
The session_destroy() function does not instantly destroy registered variables, however. They remain accessible to the script in which session_destroy() is called (until it is reloaded). The following code fragment resumes or initiates a session and registers a variable called test, set to 5. Destroying the session does not destroy the registered variable:
session_start();
$_SESSION['test'] = 5;
session_destroy();
echo $_SESSION['test']; // prints 5
To remove all registered variables from a session, you simply unset the variable:
session_start();
$_SESSION['test'] = 5;
session_destroy();
unset($_SESSION['test']);
echo $_SESSION['test']; // prints nothing (or a notice about an undefined index)
Using Sessions in an Environment with Registered Users
The examples you’ve seen so far have gotten your feet wet with sessions, but perhaps additional explanation is warranted for using sessions “in the wild,” so to speak. The following two sections outline some examples of common session usage. In later chapters of this book, sessions are used in the sample applications you build.
Working with Registered Users
Suppose that you’ve created an online community, or a portal, or some other type of application that users can “join.” The process usually involves a registration form, where the user creates a username and password and completes an identification profile. From that point forward, each time a registered user logs in to the system, you can grab the user’s identification information and store it in the user’s session.
The items you decide to store in the user’s session should be those items you can imagine using quite a bit—and that would be inefficient to continually extract from the database. For example, suppose that you have created a portal in which users are assigned a certain level, such as administrator, registered user, anonymous guest, and so forth. Within your display modules, you would always want to check to verify that the user accessing the module has the proper permissions to do so. Thus, “user level” is an example of a value stored in the user’s session, so that the authentication script used in the display of the requested module only has to check a session variable—there is no need to connect to, select, and query the database.
Working with User Preferences
If you are feeling adventurous in the design phase of a user-based application, you might build a system in which registered users can set specific preferences that affect the way they view your site. For example, you might allow your users to select from a predetermined color scheme, font type and size, and so forth. Or, you might allow users to turn “off” (or “on”) the visibility of certain content groupings.
You can store each of those functional elements in a session. When the user logs in, the application loads all relevant values into the user’s session and reacts accordingly for each subsequently requested page. Should the user decide to change her preferences, she could do so while logged in—you could even prepopulate a “preferences” form based on the items stored in the session instead of going back to the database to retrieve them. If the user changes any preferences while she is logged in, simply replace the value stored in the $_SESSION superglobal with the new selection—no need to force the user to log out and then log back in again.
Summary
In this chapter, you looked at different ways of saving state in a stateless protocol, including setting a cookie and starting a session. All methods of saving state use some manner of cookies or query strings, sometimes combined with the use of files or databases. These approaches all have their benefits and problems.
You learned that a cookie alone is not intrinsically reliable and cannot store much information. However, it can persist over a long period. Approaches that write information to a file or database involve some cost to speed and might become a problem on a popular site; this is a matter to explore with your systems administrators.
About sessions themselves, you learned how to initiate or resume a session with session_start(). When in a session, you learned how to add variables to the $_SESSION superglobal, check that they exist, unset them if you want, and destroy the entire session.
Q&A
Q. What will happen to my application if users disable cookies?
A. Simply put, if your application relies heavily on cookies and users have cookies disabled, your application won’t work. However, you can do your part to warn users that cookies are coming by announcing your intention to use cookies, and also by checking that cookies are enabled before doing anything “important” with your application. The idea being, of course, that even if users ignore your note that cookies must be turned on in order to use your application, specifically disallowing users to perform an action if your cookie test fails will get their attention!
Q. Should I be aware of any pitfalls with session functions?
A. The session functions are generally reliable. However, remember that cookies cannot be read across multiple domains. So, if your project uses more than one domain name on the same server (perhaps as part of an e-commerce environment), you might need to consider disabling cookies for sessions by setting the
session.use_cookies
directive to 0 in the php.ini file.
Workshop
The workshop is designed to help you review what you’ve learned and begin putting your knowledge into practice.
Quiz
1. Which function would you use to start or resume a session within a PHP script?
2. Which function can return the current session’s ID?
3. How can you end a session and erase all traces of it for future visits?
Answers
1. You can start a session by using the session_start() function within your script.
2. You can access the session’s ID by using the session_id() function.
3. The session_destroy() function removes all traces of a session for future requests.
Activities
• Create a script that uses session functions to track which pages in your environment the user has visited.
• Create a new script that will list for the user all the pages she has visited within your environment, and when.