[ Team LiB ] Previous Section Next Section

Setting a Cookie with PHP

You can set a cookie in a PHP script in two ways. You can use the header() function to set the Set-Cookie header. You encountered the header() function in Hour 10, "Working with Forms." header() requires a string that is included in the header section of the server response. Because headers are sent automatically for you, header() must be called before any output is sent to the browser:



header ("Set-Cookie: vegetable=artichoke; expires=Wed, 25-Aug-04 14:39:58 GMT; path=/;
graphics/ccc.gif domain=corrosive.co.uk ");

Although not difficult, this method of setting a cookie requires you to build a function to construct the header string. Formatting the date as in this example and URL encoding the name/value pair would not be a particularly arduous task. It would, however, be an exercise in wheel reinvention because PHP provides a function that does just that.

setcookie() does what the 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, expiry date in Unix epoch format, path, domain, and integer (which 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 19.1 uses setcookie() to set a cookie.

Listing 19.1 Setting and Printing a Cookie Value
 1:<?php
 2: setcookie( "vegetable", "artichoke", time()+3600, "/",
 3:      "corrosive.co.uk", 0 );
 4: ?>
 5:<!DOCTYPE html PUBLIC
 6:   "-//W3C//DTD XHTML 1.0 Strict//EN"
 7:   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 8: <html>
 9: <head>
10: <title>Listing 19.1 Setting and Printing a Cookie Value</title>
11: </head>
12: <body>
13: <?php
14: if ( isset( $_COOKIE['vegetable'] ) ) {
15: print "<p>Hello again, your chosen vegetable is ";
16: print "{$_COOKIE['vegetable']}</p>";
17: } else {
18: print "<p>Hello you. This may be your first visit</p>";
19: }
20: ?>
21: </body>
22: </html>

graphics/bytheway_icon.gif

If you want Listing 19.1 to run on your server, you must change the setCookie() function's host argument to match your domain, like so:


setcookie( "vegetable", "artichoke", time()+3600, "/",
"example.com", 0 );

You can also omit the last two arguments completely, and your current domain will be used implicitly:


setcookie( "vegetable", "artichoke", time()+3600, "/" );


Even though we set the cookie (line 2) when the script is run for the first time, the $vegetable variable is not created at this point. A cookie is read only when the browser sends it to the server, which doesn't happen until the user revisits a page in your domain. We set the cookie name to "vegetable" on line 2 and the cookie value to "artichoke". We use the time() function to get the current time stamp and add 3600 to it (there are 3600 seconds in an hour). This total represents our expiry date. We define a path of "/", which means a cookie should be sent for any page within our server environment. We set the domain argument to "corrosive.co.uk", which means a cookie will be sent to any server in that group (www.corrosive.co.uk as well as dev.corrosive.co.uk, for example). If you want the cookie returned only to the server hosting your script, you can use the $_SERVER['SERVER_NAME'] server variable instead of hard-coding the server name. The added advantage of this is that your code will work as expected even if you move it to a new server. Finally, we pass 0 to setcookie() signaling that cookies can be sent in an insecure environment.

Although you can omit all but the first argument, you should include all the arguments with the exception of the domain and the secure flag. This is because the path argument is required by some browsers for cookies to work as they should. Additionally, without the path argument the cookie is sent only to documents in the current directory or its subdirectories.

Passing setcookie() an empty string ("") for string arguments or 0 for integer fields causes these arguments to be skipped.

Deleting a Cookie

Officially, to delete a cookie, you should call setcookie() with the name argument only:


setcookie( "vegetable" );

This does not always work well, however, and should not be relied on. It is safest to set the cookie with a date that has already expired:


setcookie( "vegetable", "", time()-60, "/", "corrosive.co.uk", 0);

You should also be sure to pass setcookie() the same path, domain, and secure parameters as you did when originally setting the cookie.

Creating Session Cookies

To create a cookie that lasts only as long as the user is running her browser, pass setcookie() an expiry argument of 0. While the user's browser continues to run, the cookie is returned to the server. The browser does not remember the cookie, however, after it has been quit and restarted.

This can be useful for scripts that validate a user with a cookie, allowing continued access to personal information on multiple pages after a password has been submitted. You will not want the browser to have continued access to these pages after it has been restarted because you can't be sure that it has not been taken over by a new user:


setcookie( "session_id", "55435", 0 );



An Example—Tracking Site Usage

Imagine that we have been given a brief by a site publisher to use cookies and SQLite to gather statistics about visitors to the site. The client wants to get figures for the number of individual visitors to the site, average number of hits per visit for each visitor, and average time spent on the site for each user.

Our first duty will be to explain the limitations of cookies to the client. First, not all users will have cookies enabled on their browsers. If not passed a cookie by a browser, a cookie script is likely to assume that this is the user's first visit. The figures are therefore likely to be skewed by browsers that won't or can't support cookies. Furthermore, you cannot be sure that the same user will use the same browser all the time or that a single browser won't be shared by multiple users.

Having done this, we can move on to fulfilling the brief. In fact, we can produce a working example in fewer than 100 lines of code!

We need to create a database table with the fields listed in Table 19.1.

Table 19.1. Database Fields

Name

Type

Description

id

integer

An autoincremented field that produces and stores a unique ID for each visitor

first_visit

integer

A timestamp representing the moment of the first page request made by a visitor

last_visit

integer

A timestamp representing the moment of the most recent page request made by a visitor

num_visits

integer

The number of distinct sessions attributed to the visitor

total_duration

integer

The estimated total time spent on the site (in seconds)

total_clicks

integer

The total number of requests made by the visitor

Rather than create it manually we will embed the code to generate our table in the script itself. Once we have a table to work with, we need to write the code that will open a database connection and check for the existence of a cookie. If the cookie does not exist, we need to create a new row in our table, setting up the initial values for the fields we will maintain. We create this code in Listing 19.2.

Listing 19.2 A Script to Add New User Information to a SQLite Database
 1: <?php
 2: $GLOBALS['dbres'] = connect( "data/testdb" );
 3: $GLOBALS['visit_id'] = $_COOKIE['visit_id'];
 4:
 5: if ( empty( $visit_id ) ) {
 6:   newuser( );
 7:   print "<p>Welcome, first time user!</p>";
 8: } else {
 9:   print "<p>Welcome back $visit_id</p>";
10: }
11:
12: function newuser( ) {
13:   $visit_data = array (
14:         'first_visit' => time(),
15:         'last_visit' => time(),
16:         'num_visits' => 1,
17:         'total_duration' => 0,
18:         'total_clicks' => 1
19:         );
20:
21:   insert_visit( $visit_data );
22:   setcookie( "visit_id", $visit_data['id'],
23:        time()+(60*60*24*365*10), "/" );
24:   return $visit_data;
25: }
26:
27: function connect( $db ) {
28:   $dbres = sqlite_open($db, 0, $error);
29:   if ( ! is_resource( $dbres ) ) {
30:     die( "sqllite error: $error" );
31:   }
32:   $create = "CREATE TABLE track_visit (
33:         id INTEGER PRIMARY KEY,
34:         first_visit INTEGER,
35:         last_visit INTEGER,
36:         num_visits INTEGER,
37:         total_duration INTEGER,
38:         total_clicks INTEGER)";
39:   @sqlite_query( $dbres, $create );
40:    return $dbres;
41: }
42:
43: function insert_visit( &$visit_data ) {
44:   $query = "INSERT INTO track_visit ( ";
45:   $query .= implode( ", ", array_keys( $visit_data ) );
46:   $query .= " ) VALUES( ";
47:   $query .= implode(", ", array_values( $visit_data ) );
48:   $query .= " );";
49:   $result = sqlite_query( $GLOBALS['dbres'], $query );
50:   $visit_data['id'] = sqlite_last_insert_rowid( $GLOBALS['dbres'] );
51: }
52: ?>

We generate an SQLite resource variable using a convenience function called connect(), declared on line 27. This opens the database file on line 28, checking that a valid resource has been created on line 29 (you can read more about working with SQLite in Hour 13, "Database Integration—SQL"). We also create the 'track_visit' table on line 39 by passing a SQL CREATE statement to the sqlite_query() function. If the table already exists, a warning is generated, so we suppress this by adding an "at" character to the function call, like so:


@sqlite_query( $dbres, $create );

The connect() function returns a SQLite resource value that is stored in a global variable called $GLOBALS['dbres']. This is accessed by all functions that work with the database. On line 3, we attempt to extract the 'visit_id' element from the $_COOKIE array and assign it to a variable: $visit_id. On line 4, we test $visit_id. If the variable is empty, we assume that we are dealing with a new user, calling a function we have named newuser().

newuser() is declared on line 12, requires no arguments, and returns an array of the values we will add to our table. Within the function, we create an array called $visit_data on line 13. We set the first_visit and last_visit elements to the current time in seconds. Because this is the first visit, we set the num_visits and total_clicks elements to 1. No time has elapsed in this visit, so we set total_duration to 0.

On line 21 we call the insert_visit() function (declared on line 43) that accepts the $visit_data array and uses its elements to create a new row in our table, setting each field to the value of the element of the same name. Notice that we use the built-in implode() function on line 45 to construct our SQL statement. Because the id field autoincrements, this does not need to be inserted. We can subsequently access the value set for id using the sqlite_last_insert_rowid () function on line 50. Now that we have an ID for our new visitor, we add this to our $visit_data array, which then accurately reflects the visitor's row in the SQLite table. The $visit_data array was passed to insert_visit() by reference, so the array we manipulate here is also referenced from the variable of the same name in the calling newuser() function.

Finally, in the newuser() function, we use setcookie() on line 22 to set a visit_id cookie and return the $visit_data array to the calling code on line 24.

The next time our visitor hits this script, the $visit_id variable will have been populated with the value of the visit_id cookie. Because this variable is set, the user will be welcomed and no action will be taken.

In fact, we will need to update information in the track_visit table if we detect the return of a known visitor. We will need to test whether the current request is part of an ongoing visit or represents the beginning of a new visit. We do this with a global variable that defines a time in seconds. If the time of the last request added to this interval is greater than the current time, we assume that the current request is part of a session in progress. Otherwise, we are welcoming back an old friend.

Listing 19.3 adds new functions to the code created in Listing 19.2.

Listing 19.3 A Script to Track Users Using Cookies and a SQLite Database
 1: <?php
 2: $GLOBALS['slength'] = 300;
 3: $GLOBALS['dbres'] = connect( "data/testdb" );
 4: $GLOBALS['visit_id'] = $_COOKIE['visit_id'];
 5: $GLOBALS['user_stats'];
 6:
 7: if ( empty( $visit_id ) ) {
 8:   $user_stats = newuser( );
 9:   print "<p>Welcome, first time user!</p>";
10: } else {
11:   print "<p>Welcome back $visit_id</p>";
12:   $user_stats = olduser( $visit_id );
13: }
14:
15: function newuser( ) {
16:   $visit_data = array (
17:         'first_visit' => time(),
18:         'last_visit' => time(),
19:         'num_visits' => 1,
20:         'total_duration' => 0,
21:         'total_clicks' => 1
22:         );
23:
24:   insert_visit( $visit_data );
25:   setcookie( "visit_id", $visit_data['id'],
26:        time()+(60*60*24*365*10), "/" );
27:   return $visit_data;
28: }
29:
30: function olduser( $visit_id ) {
31:   $now = time();
32:   $visit_data = get_visit( $visit_id );
33:   if ( ! $visit_data ) {
34:     return newuser( );
35:   }
36:   $visit_data['total_clicks']++;
37:   if ( ( $visit_data['last_visit'] + $GLOBALS['slength'] ) > $now ) {
38:     $visit_data['total_duration'] +=
39:            ( $now - $visit_data['last_visit'] );
40:   } else {
41:     $visit_data['num_visits']++;
42:   }
43:   $visit_data['last_visit'] = $now;
44:   update_visit( $visit_data );
45:   return $visit_data;
46: }
47:
48: function connect( $db ) {
49:   $dbres = sqlite_open($db, 0, $error);
50:   if ( ! is_resource( $dbres ) ) {
51:     die( "sqlite error: $error" );
52:   }
53:   $create = "CREATE TABLE track_visit (
54:         id INTEGER PRIMARY KEY,
55:         first_visit INTEGER,
56:         last_visit INTEGER,
57:         num_visits INTEGER,
58:         total_duration INTEGER,
59:         total_clicks INTEGER)";
60:   @sqlite_query( $dbres, $create );
61:   return $dbres;
62: }
63:
64: function get_visit( $visit_id ) {
65:   $query = "SELECT * FROM track_visit WHERE id=$visit_id";
66:   $result = sqlite_query( $GLOBALS['dbres'], $query);
67:
68:   if ( ! sqlite_num_rows( $result ) ) {
69:     return false;
70:   }
71:   return sqlite_fetch_array( $result, SQLITE_ASSOC );
72: }
73:
74: function update_visit( &$visit_data ) {
75:   $update_pairs = array();
76:   foreach( $visit_data as $field=>$val ) {
77:     if ( ! is_int( $field ) ) {
78:       array_push( $update_pairs, "$field=$val" );
79:     }
80:   }
81:   $query = "UPDATE track_visit SET ";
82:   $query .= implode( ", ", $update_pairs );
83:   $query .= " WHERE id=".$visit_data['id'];
84:   sqlite_query( $GLOBALS['dbres'], $query );
85: }
86:
87: function insert_visit( &$visit_data) {
88:   $query = "INSERT INTO track_visit ( ";
89:   $query .= implode( ", ", array_keys( $visit_data ) );
90:   $query .= " ) VALUES( ";
91:   $query .= implode(", ", array_values( $visit_data ) );
92:   $query .= " );";
93:   $result = sqlite_query ( $GLOBALS['dbres'], $query );
94:   $visit_data['id'] = sqlite_last_insert_rowid( $GLOBALS['dbres'] );
95: }
96: ?>

graphics/bytheway_icon.gif

Remember that you can alter the length of a session timeout in Listing 19.3 by changing the value of $GLOBALS['slength'] on line 2. This global variable defines the interval of time (in seconds) that the script will accept before declaring one visit over and another started. Although the value of 300 that we use would be acceptable in a real-world situation, you might want to set a smaller value (such as 30) for testing purposes, like this:


$GLOBALS['slength'] = 30;


We add a new global variable to the script called $slength on line 2. This defines the interval after which we assume that a new visit is taking place. If the $visit_id variable contains a value, we know that the cookie was in place. We call the olduser() function on line 10, passing it the $visit_id variable.

Within the olduser() function, we first acquire visit data by calling the get_visit() function on line 32. get_visit() is declared on line 64 and requires the visit ID, which it stores in an argument variable called $visit_id. This is used to extract the relevant row from the track_visit table using sqlite_query() on line 66. Assuming we have located the row in our table that matches the visit_id cookie, we use sqlite_fetch_array() on line 71 to populate to return an associative array. The calling code on line 32 assigns this associative array to the $visit_data variable. The olduser() function should now have a populated $visit_data array containing fields for all the columns in our table. If not, we give up and call newuser() (line 34), which adds a row to the database.

On line 37, we test whether the value of the $visit_data['last_visit'] element added to the interval stored in $GLOBALS['slength'] is greater than the current time. If so, fewer than $GLOBALS['slength'] seconds have elapsed since the last hit and we can assume that this request is part of a current session. We therefore add the time elapsed since the last hit to the $visit_data['total_duration'] element on line 38.

If the request represents a new visit, we increment $visit_data['num_visits'] on line 41.

Finally, we pass $visit_data to update_visit() on line 44. update_visit() is declared on line 67 and constructs a SQL UPDATE statement by looping through the altered values in the array. The statement is passed to sqlite_query() on line 84 to update the user's row in the track_visit table. olduser(), the function that called update_visit() on line 44, returns the altered $visit_data array to the calling code.

Now that we've created the code, we should create a quick function to demonstrate it in action. The outputStats() function simply calculates the current user's averages and prints the result to the browser. In reality, you would probably want to create some analysis screens for your client, which would collate overall information. Listing 19.4 creates the outputStats() function. The code from previous examples is incorporated into this script using an include() statement.

Listing 19.4 A Script to Output Usage Statistics Gathered in Listing 19.3
 1: <?php
 2: include("listing19.3.php");
 3: outputStats();
 4: function outputStats() {
 5:   global $user_stats;
 6:   $clicks = sprintf( "%.2f",
 7:        ($user_stats['total_clicks']/$user_stats['num_visits']) );
 8:   $duration = sprintf( "%.2f",
 9:        ($user_stats['total_duration']/$user_stats['num_visits']) );
10:   print "<p>Hello! Your id is ".$user_stats['id']."</p>\n\n";
11:   print "<p>You have visited
12:        ".$user_stats['num_visits']." time(s)</p>\n\n";
13:   print "<p>Av clicks per visit: $clicks</p>\n\n";
14:   print "<p>Av duration of visit: $duration seconds</p>\n\n";
15: }
16: ?>

Figure 19.1 shows the output from Listing 19.4. We use an include() statement on line 2 to call the tracking code we have written. We will be including a similar line on every page of our client's site. The outputStats() function called on line 3 and declared on line 4 works with the global $user_stats array variable. This was returned by either newuser() or olduser() and contains the same information as our user's row in the track_visit table.

Figure 19.1. Reporting usage statistics

graphics/19fig01.gif

On line 6, to calculate the user's average number of clicks, we divide the $user_stats['total_clicks'] element by the number of visits we have detected. Similarly on line 8, we divide the $user_stats['total_duration'] element by the same figure. We use sprint() to round the results to two decimal places. All that remains is to write a report to the browser.

We could, of course, extend this example to track user preference on a site, as well as to log browser types and IP addresses. Imagine a site that analyzes a user's movements and emphasizes content according to the links he chooses.

    [ Team LiB ] Previous Section Next Section