Chapter 14. Sessions
14.1. Problem and solution
Classical server-side Web programs suffer from a large limitation:
A Web server handles each Web request individually, without the context
of other requests coming from the same user. This is a major limitation
in situations such as sites that require users to log in and others
where the user builds a shopping cart
of desired items
to buy. Such problems require that a server-side script remember
what happened with previous requests, which can become a fairly
complicated process as a Web server interleaves servicing requests from
a variety of users.
PHP addresses this problem with the notion of a
session. Using sessions with PHP in their basic form is
quite simple: It is simply a matter of calling the
session_start
function before sending any HTML code to the
client. PHP will create an array called $_SESSION
corresponding to this user; anything placed in this array will be
associated with the session, and will be recalled into the array when
the same user later requests a script that also calls
session_start
.
This is all deceptively easy to use. As we'll see later in this chapter, PHP does quite a bit behind the scenes to allow sessions to work well.
14.2. Putting it together: A page counter
Before we go further, though, let's look at a simple example involving sessions: We'll look at a PHP script that counts how many times a user has loaded the page in question. Not very useful, yes, but it does illustrate all of the concepts essential to using sessions profitably.
1 <?php
2 session_start();
3
4 if(isset($_SESSION["count"])) {
5 $accesses = $_SESSION["count"] + 1;
6 } else {
7 $accesses = 1;
8 }
9
10 $_SESSION["count"] = $accesses;
11
?>
12 <html>
13 <head>
14 <title>Access counter</title>
15 </head>
16
17 <body>
18 <h1>Access counter</h1>
19
20 <p>You have accessed this page <?php echo $accesses;
?> times today.</p>
21
22 <p>[<a href="counter.php">Reload</a>]</p>
23 </body>
24 </html>
In line 2, we begin the session by
invoking the session_start
function.
Remember, this must occur before any HTML
code is sent to the Web browser (for reasons explains later). Even
a blank line before the <?php will qualify as sending
information to the browser, and it will then be too late to use
session_start
.
The session_start
function creates the $_SESSION
array, which contains any values saved into the session by previous
accesses to the Web site. In this example, our Web site has just one
page, but in general, the values may have been saved by different
pages within the same site. However, it may be that no pages in the site
have saved any values in the Web site — in which case the
$_SESSION
variable would be empty.
The purpose of line 4 is to check
whether a count has been stored into $_SESSION
previously.
If it has, then in line 5 we retrieve
that value, and we add 1 since the user has now loaded the page one more
time. If not, then we simply note that the user has loaded the page just
this once.
Line 10 saves the new count back into
$_SESSION
. Note that we use the same string in the parentheses
as we used in lines 4
and 5, so that those lines will be able
to retrieve the saved value when the page is loaded again.
14.3. Forum example using sessions
Now let's look at an example applying sessions. In particular, we want to modify our forum site so that the user must first log in to view any posts. We'll also modify the posting process so that the user doesn't need to retype the user ID and password with each new posting.
To enable this, we first must create a Web form for logging into the forum, a file that we name login.html.
<html>
<head><title>Log in</title></head>
<body>
<h1>Log in to forum</h1>
<form method="post" action="view.php">
<p>User ID: <input type="text" name="userid" /></p>
<p>Password: <input type="password" name="passwd" /></p>
<p><input type="submit" value="Log In" /></p>
</form>
</body>
</html>
Then we will modify the view.php script
from Section 8.2 to identify when the user is trying to log
in. The important part is the portion at the beginning: We first call
session_start
. We use the
userid
key in the $_SESSION
array
to store the user ID logged into the session; if this isn't set, then
the user hasn't logged in.
<?php
session_start();
$db = mysql_connect("localhost:/export/mysql/mysql.sock");
mysql_select_db("forum", $db);
// To tell whether the user is coming here from the login form, we
// check whether the $_POST array has passwd and userid keys. If so,
// then we will attempt to verify that the user ID/password
// combination is correct through a database query.
if(isset($_POST["passwd"]) && isset($_POST["userid"])) {
$userid = $_POST["userid"];
$passwd = $_POST["passwd"];
$sql = "SELECT userid"
. " FROM Users"
. " WHERE userid = '$userid' AND passwd = '$passwd'";
$rows = mysql_query($sql, $db);
if(!$rows) {
// If the query fails, either because the SQL is incorrect
// or because the userid/password combination isn't in the
// database, then we will store an error message in a place
// that we can access later, and we will unset the session
// key userid, just in case the user is trying to log in
// as somebody else.
$error = "Password lookup error: " . mysql_error();
unset($_SESSION["userid"]);
} elseif(mysql_num_rows($rows) == 0) {
$error = "Password not accepted.";
unset($_SESSION["userid"]);
} else {
// We got a valid login: Remember this for later.
$_SESSION["userid"] = $userid;
}
} elseif(!isset($_SESSION["userid"])) {
// If the user hasn't logged in previously and isn't coming from the
// login form, then we want to reject the user's request.
$error = "You must first log in.";
}
// The above sets $error if there is some problem that should
// prevent listing the posts. In this case, we should show the error
// message to the user and exit so the posts aren't listed.
if(isset($error)) {
echo "<html>\n";
echo "<head><title>Cannot Access Forum</title></head>\n";
echo "<body>\n";
echo "<h1>Cannot Access Forum</h1>\n";
echo "<p>Error encountered trying access form: $error.</p>\n";
echo "</body>\n";
echo "</html>\n";
exit;
}
?>
<html>
<head>
<title>Current Posts</title>
</head>
<body>
<h1>Current Posts</h1>
<?php
$sql = "SELECT subject, body, userid, name"
. " FROM Posts, Users"
. " WHERE poster = userid"
. " ORDER BY postdate";
$rows = mysql_query($sql, $db);
if(!$rows) {
echo "<p>SQL error: " . mysql_error() . "</p>\n";
} elseif(mysql_num_rows($rows) == 0) {
echo "<p>There are not yet any posts.</p>\n";
} else {
for($row = 0; $row < mysql_num_rows($rows); $row++) {
$subj = mysql_result($rows, $row, 0);
$body = mysql_result($rows, $row, 1);
$id = mysql_result($rows, $row, 2);
$name = mysql_result($rows, $row, 3);
echo "<h2>$subj</h2>\n";
echo "<p>By: <a href=\"user.php?userid=$id\">$name</a></p>\n";
echo "<p>$body</p>\n";
}
}
?>
<h2>Post new message</h2>
<form method="post" action="post.php">
<p>Subject: <input type="text" name="subj" />
<br />Body: <input type="text" name="body" />
<br /><input type="submit" value="Post" />
</p></form>
</body>
</html>
?>
Of course, we'd also want to modify post.php so that it
looks up the user ID from the $_SESSION
rather than try to
read the user ID from the form.
<?php
session_start();
if(!isset($_SESSION["userid"])) {
$message = "You must first log in to post messages.";
} else if(!isset($_POST["subj"]) || $_POST["subj"] == "") {
$message = "The post's subject is missing.";
} else if(!isset($_POST["body"]) || $_POST["body"] == "") {
$message = "The post's title is missing.";
} else {
$userid = $_SESSION["userid"];
$subj = $_POST["subj"];
$body = $_POST["body"];
$db = mysql_connect("localhost:/export/mysql/mysql.sock");
mysql_select_db("forum", $db);
$sql = "INSERT INTO Posts (poster, postdate, subject, body)"
. " VALUES ('$userid', CURRENT_DATE(), '$subj', '$body')";
$rows = mysql_query($sql, $db);
if(!$rows) {
$message = "Insert error: " . mysql_error();
} else {
$message = "The post was successfully inserted.";
}
}
?>
<html>
<head>
<title>Post requested</title>
</head>
<body>
<h1>Post requested</h1>
<p><?php echo $message;
?></p>
<p>[<a href="view.php">List Posts</a>]</p>
</body>
</html>