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>

14.4. How sessions work