PHP: Sessions and Dynamic URLs

Session IDs

In many situations we would like to track users without requiring login or registration, e.g. for a shopping cart: we do not need the user name or other personal data, we only have to make a connection between users and shopping carts. This is easily achieved with session IDs.

Session IDs are 32-character strings used to identify sessions:

Session IDs are generated to be unique for each session. Temporary use in tables such as shopping carts is unlikely to cause problems; however, the uniqueness cannot be guaranteed. SQL sequences provide a clean and reliable solution as a source of unique identifiers.

Let's try it out:

<?php session_start(); ?> <h2> Session Test</h2> <?php $s = session_id(); ?> <p> Session ID: <?php echo $s; ?>

We should see a session ID that remains the same if we click reload. However, when we close and then restart the browser, or open the URL in a different browser, we should get a different session ID.

This can now be used to identify the user in the sense that the same session ID usually refers to the same user. Obviously there is a chance that after some time there is in fact someone else sitting at the computer now, but this is also tricky to avoid with other methods, and will not concern us here.

The session ID is usually implemented via cookies which touches on the nasty subject of the abysmal EU cookie laws. However, technical cookies strictly necessary for the provision of the service are exempt; these include our session cookie: gdpr.eu specifically mentions 'Cookies that allow web shops to hold your items in your cart' under this category. These cookies do not require consent, though you are still required (or is it just a suggestion?) to inform users about the use of cookies. Note that this is as of Nov 2020, and see e.g. gdpr.eu/cookies for details. To be safe from legal attack it is probably a good idea to put a note like 'technical cookies strictly necessary for the provision of the service are being used' on all your web pages; this can be done with e.g. a footer.php that is included in all scripts, similar to header.php.

There are other options for obtaining the shopping cart functionality:

These options are a little more programming effort but at least they provide an alternative way of maintaining session info if cookies are disabled in the browser, or unavailable for some other reason, or undesirable.

Note that the variable $_SESSION can hold additional values for use in the application, e.g.

$_SESSION['favcolor'] = 'green';

Shopping Cart

The session ID for us will represent a specific shopping cart. The same user always sees the same shopping cart while the session lasts, and other users during the same time period see their own respective carts.

Our Shopping Cart Table

We create a table for the shopping cart data:

create table cart (sess varchar, prod varchar, qty int);

In order to implement our shopping cart feature we now need a way to pass parameters to procedures, so that we can put a specific product into the shopping cart.

Put Products into the Cart: Pass Parameters in the URL

Sometimes it is convenient to pass a parameter to a script as part of the URL pointing to that script, e.g. when generating a product list with links to a script incart.php to put a product into the shopping cart.

An URL that is generated by a script rather than entered in a text editor is called a dynamic URL.

For this purpose, the link to the script incart.php has to be generated as part of the product list:

... while ($x = pg_fetch_object($result)) { ... <td> <a href=incart.php?prod=$x->id>Buy</a> ...

To make it a little easier, here is the complete listprod.php in the new version:

<?php include 'header.php'; ?> <h2>Products</h2> <table> <tr><th>ID<th>Name<th>Price</tr> <?php $query = 'select id, name, price from prod'; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($x = pg_fetch_object($result)) { echo "<tr><td> $x->id <td> $x->name <td align=right> $x->price <td> <a href=incart.php?prod=$x->id>Buy</a> </tr>\n"; } ?> </table>

Use View/Page Source to check that we see a different product ID in each entry of the list.

Handle the Shopping Cart Insert

The script incart.php gets the product id from the URL via the $_GET variable and executes an SQL insert statement:

<?php session_start(); ?> <?php include 'header.php'; ?> <?php $sess = session_id(); $query = "insert into cart (sess, prod, qty) values ('$sess', '$_GET[prod]', 1)"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); echo "<p> Product is in the shopping cart.\n"; ?>

Note that the call to session_start() must be the first PHP command.

Display the Shopping Cart

We now have a working shopping cart. We just need to display it: Let's call this script cart.php:

<?php session_start(); ?> <?php include 'header.php'; ?> <h2>Shopping Cart</h2> <table> <tr><th>Product<th>Qty <?php $sess = session_id(); $query = "select prod, qty from cart where sess = '$sess'"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($x = pg_fetch_object($result)) { echo "<tr><td> $x->prod <td align=right> $x->qty \n"; } ?> </table>

Upsert: Update or Insert

In some cases we want to update a row if it exists, or insert a new one if it doesn't; one way to do this is the trial update and counting the affected rows:

if (pg_affected_rows(pg_query("update ... ")) > 0) { echo "Updated ..."; } else { pg_query("insert into ..."); echo "Inserted ..."; }

If the update is successful, one or more rows will be affected, otherwise the else part executes the insert. Note that an update affecting no rows is not an error.

Automated Reports

With this technique reports can be generated automatically, including text blocks depending on the statistical results. Use CSS for a more professional-looking layout.

<?php include 'header.php'; ?> <h4>Correlation Price/Stock</h4> <?php $query = 'select corr(price, stock) as r, regr_count(price, stock) as n from prod'; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($x = pg_fetch_object($result)) { $r = $x->r; $n = $x->n; } ?> <p> Number of values: <?php echo $n; ?> <p> Correlation r: <?php echo $r; ?> <p> The correlation is <?php if (abs($r) < 0.3) { echo "weak."; } elseif (abs($r) < 0.7) { echo "moderate."; } else { echo "strong."; } ?>

We are using some neat PostgresQL and PHP functions:

The rest of the code should be fairly self-explaining. The limits 0.3 and 0.7 are of course somewhat arbitrary.

Challenge: provide reliable information on the significance of the correlation coefficient.

Exercises

Find more applications for dynamically constructed URLs and implement them. Here are some suggestions:

Further Reading

The primary source for everything PHP is php.net.

Prospective developers of real-world applications should take a close look at the section security.