Main Head First PHP & MySQL

Head First PHP & MySQL

,
Head First PHP & MySQL (A Brain-Friendly Guide) КНИГИ ; WEB-СОЗИДАНИЕ Название: Head First PHP & MySQL (A Brain-Friendly Guide) Автор:Lynn Beighley, Michael Morrison Год выпуска: December 30, 2008 Издательство: O'Reilly Media Страниц: 812 Формат: pdf Язык: English Размер: 46.5 mbIf you're ready to create web pages more complex than those you can build with HTML and CSS, Head First PHP & MySQL is the ultimate learning guide to building dynamic, database-driven websites using PHP and MySQL. Packed with real-world examples, this book teaches you all the essentials of server-side programming, from the fundamentals of PHP and MySQL coding to advanced topics such as form validation, session IDs, cookies, database queries and joins, file I/O operations, content management, and more.Head First PHP & MySQL offers the same visually rich format that's turned every title in the Head First series into a bestseller, with plenty of exercises, quizzes, puzzles, and other interactive features to help you retain what you've learned.- Use PHP to transform static HTML pages into dynamic web sites - Create and populate your own MySQL database tables, and work with data stored in files - Perform sophisticated MySQL queries with joins, and refine your results with LIMIT and ORDER BY - Use cookies and sessions to track visitors' login information and personalize the site for users - Protect your data from SQL injection attacks - Use regular expressions to validate information on forms - Dynamically display text based on session info and create images on the fly - Pull syndicated data from other sites using PHP and XMLThroughout the book, you'll build sophisticated examples -- including a mailing list, a job board, and an online dating site -- to help you learn how to harness the power of PHP and MySQL in a variety of contexts. If you're ready to build a truly dynamic website, Head First PHP & MySQL is the ideal way to get going. Free-share.ru 85
Year: 2008
Edition: 1
Publisher: O'Reilly Media
Language: english
Pages: 816 / 762
ISBN 13: 978-0-596-00630-3
File: PDF, 66.34 MB
Download (pdf, 66.34 MB)
Preview

You may be interested in

 
 
You can write a book review and share your experiences. Other readers will always be interested in your opinion of the books you've read. Whether you've loved the book or not, if you give your honest and detailed thoughts then people will find new books that are right for them.
1

Heartful Quilt de Reiko Kato

Year: 2008
Language: english
File: PDF, 12.05 MB
2

Head First Design Patterns

Year: 2004
Language: english
File: PDF, 42.28 MB
Head First PHP & MySQL
by Lynn Beighley and Michael Morrison
Copyright © 2009 O’Reilly Media, Inc. All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
O’Reilly Media books may be purchased for educational, business, or sales promotional use. Online editions are
also available for most titles (safari.oreilly.com). For more information, contact our corporate/institutional sales
department: (800) 998-9938 or corporate@oreilly.com.

Series Creators:		

Kathy Sierra, Bert Bates

Series Editor:		

Brett D. McLaughlin

Editor:			

Sanders Kleinfeld

Design Editor:		

Louise Barr

Cover Designers:		

Louise Barr, Steve Fehler

Production Editor:		

Brittany Smith

Proofreader:			

Colleen Gorman

Indexer:			

Julie Hawks

Page Viewers:

Julien and Drew

Michael’s nephew Julien
generously lent his
Superman powers to help
get this book finished.

Printing History:
December 2008: First Edition.

Drew is, at thi
moment, installins very
a new kitchen in g
Lynn’s new old ho
use.

The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. The Head First series designations,
Head First PHP & MySQL, and related trade dress are trademarks of O’Reilly Media, Inc.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and O’Reilly Media, Inc., was aware of a trademark
claim, the designations have been printed in caps or initial caps.
While every precaution has been taken in the preparation of this book, the publisher and the authors assume no
responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.
No hardwood floors, UFOs, Elvis look-alikes, or virtual guitars were harmed in the making of this book. But a
few broken hearts were mended thanks to some careful mismatching!

ISBN: 978-0-596-00630-3
[M]										

7 building personalized web apps

Remember me?
What’s your name again? Johnson, right.
Well, I’m showing no record of you, Mr. Jackson.
Are you sure you signed up for a warranty on
your cryogenic storage cell? Oh, I see, so you’re
calling from inside your cell right now. And what
was your name again?

No one likes to be forgotten, especially users of web
applications. If an application has any sense of “membership,” meaning that users
somehow interact with the application in a personal way, then the application needs to
remember the users. You’d hate to have to reintroduce yourself to your family every time
you walk through the door at home. You don’t have to because they have this wonderful
thing called memory. But web applications don’t remember people automatically—it’s up
to a savvy web developer to use the tools at their disposal (PHP and MySQL, maybe?) to
build personalized web apps that can actually remember users.

this is a new chapter   345

a good mismatch is hard to find

They say opposites attract
It’s an age-old story: boy meets girl, girl thinks boy is completely nuts, boy
thinks girl has issues, but their differences become the attraction, and
they end up living happily ever after. This story drives the innovative new
dating site, Mis-match.net. Mismatch takes the “opposites attract” theory
to heart by mismatching people based on their differences.

Check out
these guns!

Problem is, Mismatch has yet to get off the ground and is in dire need of
a web developer to finish building the system. That’s where you come in.
Millions of lonely hearts are anxiously awaiting your completion of the
application... don’t let them down!

Sidney loves reality TV,
yoga, and sushi, and is
hoping for a successful
mismatch.

I can’t wait to
find my perfect
mismatch.

Johan Nettles
Male
1981-11-03
Athens, GA

Personal web applications
thrive on personal information,
which requires users to be
able to access an application
on a personal level.

Sidney Kelsow
Female
1984-07-19
Tempe, AZ

Johan loves professional
wrestling, weightlifting, and
Spam, and is excited about
anyone who’ll reply to him.

Mismatch users need to be able to interact with the site on a personal
level. For one thing, this means they need personal profiles where
they enter information about themselves that they can share with
other Mismatch users, such as their gender, birthdate, and location.
346

Chapter 7

building personalized web apps

Mismatch is all about personal data
So Mismatch is all about establishing connections through personal data.
These connections must take place within a community of users, each
of whom is able to interact with the site and manage their own personal
data. A database called mismatch_user is used to keep up with
Mismatch users and store their personal information.

mismatch_user

This is the
Mismatch database.

first_name

last_name

gender

birthdate

city

picture

join_date

state

user_id

2008-04-17
09:43:11

Sidney

Kelsow

F

1984-07-19

Tempe

AZ

sidneypic.jpg

1

Johan

Nettles

M

1981-11-03

Athens

GA

johanpic.jpg

11

2008-05-23
12:24:06

...

...

The Edit and View
Profile pages need
to know whose
profile to access.

Within the Mismatch
database, the
mismatch_user table
stores users and their
personal profile data.

Each row of the
mismatch_user
table contains
personal data
for a single user.

Sidney Kelsow
Female
1984-07-19
Tempe, AZ

In addition to viewing a user profile, Mismatch users can
edit their own personal profiles using the Edit Profile page.
But there’s a problem in that the application needs to know
which user’s profile to edit. The Edit Profile page somehow
needs to keep track of the user who is accessing the page.

How can Mismatch customize
the Edit Profile page for each
different user?

you are here 4   347

adding log-ins to mismatch

Mismatch needs user log-ins
The solution to the Mismatch personal data access problem involves user
log-ins, meaning that users need to be able to log into the application.
This gives Mismatch the ability to provide access to information that
is custom-tailored to each different user. For example, a logged-in user
would only have the ability to edit their own profile data, although they
might also be able to view other users’ profiles. User log-ins provide the
key to personalization for the Mismatch application.
A user log-in typically involves two pieces of information, a username
and a password.

User log-ins
allow web
applications to
get personal
with users.

Username

Password

The job of the username is to provide each
user with a unique name that can be used to
identify the user within the system. Users can
potentially access and otherwise communicate
with each other through their usernames.

The password is responsible for providing
a degree of security when logging in users,
which helps to safeguard their personal data.
To log in, a user must enter both a username
and password.

jnettles

********

sidneyk

Usernames typically consist of
alphanumeric characters and
are entirely up to the user.

A username and password allows a user to log in to the Mismatch
application and access personal data, such as editing their profile.

sidneyk
******

The user’s username and
password are all that is
required to let the application
know who they are.

348

Chapter 7

When a user logs
in, the application is
able to remember the
user and provide a
personalized experience.

******

Passwords are extremely
sensitive pieces of data and
should never be made visible
within an application, even
inside the database.

The Edit Profile page
now indicates that
the user is logged in.

building personalized web apps

Come up with a user log-in gameplan
Adding user log-in support to Mismatch is no small feat, and it’s important
to work out exactly what is involved before writing code and running
database queries. We know there is an existing table that stores users, so
the first thing is to alter it to store log-in data. We’ll also need a way for
users to enter their log-in data, and this somehow needs to integrate with
the rest of the Mismatch application so that pages such as the Edit Profile
page are only accessible after a successful log-in. Here are the log-in
development steps we’ve worked out so far:

1

 se ALTER to add username and
U
password columns to the table.
The database needs new columns for storing the
log-in data for each user. This consists of a username
and password.
username

3

password

2

Build a new
Log-In script
that prompts
the user to en
ter their user
name and
password.
The Log In form
is what will ultim
ately protect
personalized page
s in that it prompt
s for a valid
username and pa
ssword. This info
rm
ation must
be entered proper
ly before Mismat
ch
can display
user-specific data
. So the script mus
t
lim
it access
to personalized pa
ges so that they ca
n’t be viewed
without a valid lo
g-in.

Connect the Log-In script
to the rest of the Mismatch
application.
The Edit Profile and View Profile pages
of the Mismatch application should only
be accessible to logged in users. So we
need to make sure users log in via the
Log In script before being allowed to
access these pages.

you are here 4   349

mismatch setup

Before going any further, take a moment to tinker with the Mismatch
application and get a feel for how it works.
Download all of the code for the Mismatch application from the Head First Labs web site
at www.headfirstlabs.com/books/hfphp. Post all of the code to your web server
except for the .sql files, which contain SQL statements that build the necessary Mismatch
tables. Make sure to run the statement in each of the .sql files in a MySQL tool so that you
have the initial Mismatch tables to get started with.
When all that’s done, navigate to the index.php page in your web browser, and check out
the application. Keep in mind that the View Profile and Edit Profile pages are initially broken
since they are entirely dependent upon user log-ins, which we’re in the midst of building.

These two links
lead into the
personalized parts
of the application.

The main Mismatch page allows
you to see the name and picture
of the latest users, but not much
else without being logged in.

Download It!
The complete source code for the Mismatch
application is available for download from the
Head First Labs web site:
www.headfirstlabs.com/books/hfphp

350

Chapter 7

building personalized web apps

Prepping the database for log-ins
OK, back to the construction. The mismatch_user table already does
a good job of holding profile information for each user, but it’s lacking
when it comes to user log-in information. More specifically, the table is
missing columns for storing a username and password for each user.

mismatch_user
user_id

join_date

first_name

last_name

gender

city

birthdate

state

Username and password data both consist of pure text, so it’s
possible to use the familiar VARCHAR MySQL data type for the
new username and password columns. However, unlike some
other user profile data, the username and password shouldn’t ever
be allowed to remain empty (NULL).
password

The username and password
columns contain simple
text data but should never
be allowed to go empty.

********
username ******
jnettles *******
baldpaul

...

dierdre
...

The mismatch_user table
needs columns for username
and password in order to
store user log-in data.

picture

Q:

Why can’t you just use user_id instead
of username for uniquely identifying a user?

A:
user_id

You can if you want. In fact, the purpose of
is to provide an efficient means of
uniquely identifying user rows. However, numeric
IDs tend to be difficult to remember, and users really
like being able to make up their own usernames for
accessing personalized web applications. So it’s
more of a usability decision to allow Johan to be able
to log in as “jnettles” instead of “11”. No one wants to
be relegated to just being a number!

Finish writing an SQL statement to add the username and
password columns to the table positioned as shown, with
username able to hold 32 characters, password able to hold
16 characters, and neither of them allowing NULL data.

Few people would want to
try and remember a password
longer than 16 characters!

mismatch_user
user_id

username

password

join_date

first_name

last_name

gender

birthdate

city

state

picture

you are here 4   351

sharpen your pencil solution

Finish writing an SQL statement to add the username and
password columns to the table positioned as shown, with
username able to hold 32 characters, password able to hold
16 characters, and neither of them allowing NULL data.

ALTER TABLE is used to add
new columns to an existing table.

ALTER TABLE mismatch_user ADD username VARCHAR(32) NOT NULL AFTER user_id,
The username column is
ADD password VARCHAR(16) NOT NULL AFTER username
added first, so it’s OK
to
reference it here.
The AFTER statement controls where
in the table new columns are added.
mismatch_user

user_id

1

username

DONE

password

join_date

 se ALTER to add username and
U
password columns to the table.

first_name

last_name

gender

birthdate

city

state

picture

The position of columns in a table doesn’t necessarily
matter, although it can serve an organizational purpose
in terms of positioning the most important columns first.

Surely you don’t just store a password in
the database as-is... don’t you also need
to encrypt a password before storing it?

Good point... passwords require encryption.
Encryption in Mismatch involves converting a password into
an unrecognizable format when stored in the database. Any
application with user log-in support must encrypt passwords so
that users can feel confident that their passwords are safe and
secure. Exposing a user’s password even within the database itself
is not acceptable. So we need a means of encrypting a password
before inserting it into the mismatch_user table. Problem is,
encryption won’t help us much if we don’t have a way for users
to actually enter a username and password to log in...
352

Chapter 7

building personalized web apps

Constructing a log-in user interface
With the database altered to hold user log-in data, we still need a way for
users to enter the data and actually log in to the application. This log-in
user interface needs to consist of text edit fields for the username and
password, as well as a button for carrying out the log-in.

An application login requires a user
interface for entering
the username and
password.

The password field is
protected so that the
password isn’t readable.

Username:

jnettles

Password: ******
**
Log In

mismatch_user
user_id

username

password

*******

9

dierdre

10

baldpaul

******

11

jnettles

********

...

...

Clicking the Log In buttonck
makes the application che
the username and password
against the database.

Q:
A:

So asterisks aren’t actually stored in the database, right?

That’s correct. The asterisks displayed in a password form
field simply provide visual security, preventing someone from
looking over your shoulder as you enter the password. When the
form is submitted, the password itself is submitted, not the asterisks.
That’s why it’s important for the password to be encrypted before
inserting it into the database.

If the username and
password check out, the
user is successfully logged in.

		If you’re worried about
how users will be able to
log in when we haven’t
assigned them user names
and passwords yet... don’t
sweat it.
We’ll get to creating user names and passwords
for users in just a bit. For now it’s important to lay
the groundwork for log-ins, even if we still have
more tasks ahead before it all comes together.

you are here 4   353

the sha() function

Encrypt passwords with SHA()
The log-in user interface is pretty straightforward, but we didn’t address
the need to encrypt the log-in password. MySQL offers a function called
SHA() that applies an encryption algorithm to a string of text. The
result is an encrypted string that is exactly 40 hexadecimal characters
long, regardless of the original password length. So the function actually
generates a 40-character code that uniquely represents the password.
Since SHA() is a MySQL function, not a PHP function, you call it as
part of the query that inserts a password into a table. For example, this
code inserts a new user into the mismatch_user table, making sure to
encrypt the password with SHA() along the way.

The MySQL SHA()
function encrypts
a piece of text
into a unique 40character code.

INSERT INTO mismatch_user
(username, password, join_date) VALUES ('jnettles', SHA('tatlover'), NOW())

The SHA() function encrypts the password into a
40-character hexadecimal code that gets stored
in the password column of the mismatch_user table.

This is the actual
password as entered into
the password form field.

The same SHA() function works on the other end of the log-in equation
by checking to see that the password entered by the user matches up with
the encrypted password stored in the database.

Username:

The actual password

jnettles

SHA('tatlover')

Password: ******
**
Log In

The SHA() function turns
an 8-character password
into a 40-character
encrypted string of text.

'e511d793f532dbe0e0483538e11977f7b7c33b28'

mismatch_user
user_id

username

password

9

dierdre

08447b...

10

baldpaul

230dcb...

11

jnettles

e511d7...

...

354

Chapter 7

...

Instead of storing the actual passwo
we
store the 40-character encrypted rd,
code.

building personalized web apps

Comparing
Decrypting passwords
Once you’ve encrypted a piece of information, the natural instinct is to
think in terms of decrypting it at some point. But the SHA() function is
a one-way encryption with no way back. This is to preserve the security
of the encrypted data—even if someone hacked into your database and
stole all the passwords, they wouldn’t be able to decrypt them. So how is it
possible to log in a user if you can’t decrypt their password?

The SHA()
function provides
one-way
encryption—you
can’t decrypt
data that has
been encrypted.

You don’t need to know a user’s original password to know if they’ve
entered the password correctly at log-in. This is because SHA() generates
the same 40-character code as long as you provide it with the same
string of text. So you can just encrypt the log-in password entered by
the user and compare it to the value in the password column of the
mismatch_user table. This can be accomplished with a single SQL
query that attempts to select a matching user row based on a password.

This is the password entered
by the user in order to log in.

SELECT * FROM mismatch_user
WHERE password = SHA('tatlover')

The SHA() function is called to
encrypt the password so that it
can appear in the WHERE clause.

This SELECT query selects all rows in the mismatch_user
table whose password column matches the entered password,
'tatlover' in this case. Since we’re comparing encrypted versions of
the password, it isn’t necessary to know the original password. A query
to actually log in a user would use SHA(), but it would also need to
SELECT on the user ID, as we see in just a moment.

Making room for the encrypted password
The SHA() function presents a problem for Mismatch since encrypted
passwords end up being 40 characters long, but our newly created
password column is only 16 characters long. An ALTER is in order to
expand the password column for storing encrypted passwords.
ALTER TABLE mismatch_user
CHANGE password password VARCHAR(40) NOT NULL

The size of the password column is changed
to 40 so that encrypted passwords will fit.

Q:
A:

What does SHA() stand for?

The SHA() function stands for Secure
Hash Algorithm. A “hash” is a programming
term that refers to a unique, fixed-length string
that uniquely represents a string of text. In the
case of SHA(), the hash is the 40-character
hexadecimal encrypted string of text, which
uniquely represents the original password.

Q:

Are there any other ways to encrypt
passwords?

A:SHA()

Yes. MySQL offers another function similar
to
called MD5() that carries out
a similar type of encryption. But the SHA()
algorithm is considered a little more secure than
MD5(), so it’s better to use SHA() instead.
PHP also offers equivalent functions (sha1()
and md5()) if you need to do any encryption in
PHP code, as opposed to within an SQL query.
you are here 4   355

modifying mismatch_user

Test Drive
Add the username and password columns to the
mismatch_user table, and then try them out.
Using a MySQL tool, execute the ALTER statement to add the
username and password columns to the mismatch_user table.
ALTER TABLE mismatch_user ADD username VARCHAR(32) NOT NULL AFTER user_id,
ADD password VARCHAR(16) NOT NULL AFTER username

But our password column actually needs to be able to hold a 40character encrypted string, so ALTER the table once more to make room
for the larger password data.
ALTER TABLE mismatch_user
CHANGE password password VARCHAR(40) NOT NULL

Now, to test out the new columns, let’s do an INSERT for a new user.

Don’t forget to encrypt
the password by calling
the SHA() function.

INSERT INTO mismatch_user
(username, password, join_date) VALUES ('jimi', SHA('heyjoe'), NOW())

To double-check that the password was indeed encrypted in the database, take
a look at it by running a SELECT on the new user.
SELECT password FROM mismatch_user WHERE username = 'jimi'

And finally, you can simulate a log-in check by doing a SELECT on the
username and using the SHA() function with the password in a WHERE clause.

For a successful log-in,
this must be the same
password used when
inserting the row.

SELECT username FROM mismatch_user WHERE password = SHA('heyjoe')

File Edit Window Help OppositesAttract

mysql> SELECT username FROM mismatch_user WHERE password = SHA('heyjoe');
+----------+
| username |
+----------+
| jimi
|
+----------+

Only one user matches the
encrypted password.

1 row in set (0.0005 sec)

356

Chapter 7

building personalized web apps

So the password is now encrypted,
but we still need to build a log-in form.
Could we just use HTTP authentication
since it requires a username and
password to access protected pages?

Yes! HTTP authentication will certainly
work as a simple user log-in system.
If you recall from the Guitar Wars high score application
in the last chapter, HTTP authentication was used
to restrict access to certain parts of an application by
prompting the user for a username and password. That’s
roughly the same functionality required by Mismatch,
except that now we have an entire database of possible
username/password combinations, as opposed to one
application-wide username and password. Mismatch
users could use the same HTTP authentication window;
however, they’ll just be entering their own personal
username and password.

The standard HTTP
authentication window, which is
browser-specific, can serve as
a simple log-in user interface.

you are here 4   357

http authentication for mismatch

Authorizing users with HTTP
As Guitar Wars illustrated, two headers must be sent in order to restrict
access to a page via an HTTP authentication window. These headers
result in the user being prompted for a username and password in order to
gain access to the Admin page of Guitar Wars.
authorized
HTTP/1.1 401 Un

WWW-Authenticate: Basic realm="Guitar Wars"

These two headers must be sent
in order to restrict access to a
page via HTTP authentication.
Sending the headers for HTTP authentication amounts to two lines of
PHP code—a call to the header() function for each header being sent.
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Mismatch"');

HTTP authentication
requires us to send
two headers.

This is the realm for the
authentication, which applies
to the entire application.
Unless a user enters the
correct username and
password, they cannot
see or use this page.

358

Chapter 7

A username and password are
required in order to access
restricted pages in the Guitar
Wars application.

building personalized web apps

Circle the different parts of the Mismatch application that are impacted by the Log-In script
(login.php) and its usage of HTTP authentication to control access. Then annotate how
those application pieces are impacted.

Here’s the
Log-In script.

login.php

viewprofile.php

index.php

mismatch_user

editprofile.php

you are here 4   359

exercise solution

Circle the different parts of the Mismatch application that are impacted by the Log-In script
(login.php) and its usage of HTTP authentication to control access. Then annotate how
those application pieces are impacted.

The home page plays no
direct role in user log-ins
because it needs to remain
accessible by all.
login.php

When a user logs in, their
username and password
are checked against the
database to ensure they
are a registered user.

Viewing and
editing profiles
is restricted,
meaning that only
logged in users can
access these pages.

viewprofile.php

mismatch_user

If a row isn’t found that matches
the username and password, the Log
In script displays an error message
and prevents further access.

index.php

Q:
A:

Why isn’t it necessary to include the home page when
requiring user log-ins?

Because the home page is the first place a user lands when
visiting the site, and it’s important to let visitors glimpse the site
before requiring a log-in. So the home page serves as both a teaser
and a starting point—a teaser for visitors and a starting point for
existing users who must log in to go any deeper into the application.

Q:
A:

Can logged-in users view anyone’s profile?

Yes. The idea is that profiles are visible to all users who log
in, but remain private to guests. In other words, you have to be a
member of Mismatch in order to view another user’s profile.

360

Chapter 7

Q:
A:

The Edit Profile page not
only relies on the Log In editprofile.php
script for restricted access,
but it also needs the username
in order to determine which
profile to edit.

How does password encryption affect HTTP
authentication?
There are two different issues here: transmitting a password
and storing a password. The SHA() MySQL function focuses on
securely storing a password in a database in an encrypted form. The
database doesn’t care how you transmitted the password initially, so
this form of encryption has no impact on HTTP authentication.
However, an argument could be made that encryption should also
take place during the transmission of the password when the HTTP
authentication window submits it to the server. This kind of encryption
is outside the scope of this chapter and, ultimately, only necessary
when dealing with highly sensitive data.

building personalized web apps

Logging In Users with HTTP Authentication
The Log-In script (login.php) is responsible for requesting a username
and password from the user using HTTP authentication headers, grabbing
the username and password values from the $_SERVER superglobal, and
then checking them against the mismatch_user database before providing
access to a restricted page.
<?php
require_once('connectvars.php');

If the username and passw
haven’t been entered, sendord
the authentication head
to prompt the user. ers

if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
// The username/password weren't entered so send the authentication headers
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Mismatch"');
exit('<h3>Mismatch</h3>Sorry, you must enter your username and password to log in and access ' .
'this page.');
}
// Connect to the database
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);

Grab the username
and password
entered by the user.

// Grab the user-entered log-in data
$user_username = mysqli_real_escape_string($dbc, trim($_SERVER['PHP_AUTH_USER']));
$user_password = mysqli_real_escape_string($dbc, trim($_SERVER['PHP_AUTH_PW']));

Perform a query to
see if any rows match
the username and
encrypted password.

// Look up the username and password in the database
$query = "SELECT user_id, username FROM mismatch_user WHERE username = '$user_username' AND " .
"password = SHA('$user_password')";
$data = mysqli_query($dbc, $query);
if (mysqli_num_rows($data) == 1) {
// The log-in is OK so set the user ID and username variables
$row = mysqli_fetch_array($data);
$user_id = $row['user_id'];
$username = $row['username'];
}
else {
// The username/password are incorrect so send the authentication headers
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Mismatch"');
exit('<h2>Mismatch</h2>Sorry, you must enter a valid username and password to log in and ' .
'access this page.');
}

If a row matches, it means the
log-in is OK, and we can set the
$user_id and $username variables.

?>

If no database row matches the
username and password, send
the authentication headers
again to re-prompt the user.

// Confirm the successful log-in
echo('<p class="login">You are logged in as ' . $username . '.</p>');

All is well at this point, so
confirm the successful log-in.

2

DONE

Build a new Log-In script that
prompts the user to enter their
user name and password.
you are here 4   361

test drive mismatch

Test Drive
Create the new Log-In script, and include it in the View Profile
and Edit Profile scripts.
Create a new text file named login.php, and enter the code for the Log-In script in
it (or download the script from the Head First Labs site at www.headfirstlabs.
com/books/hfphp). Then add PHP code to the top of the viewprofile.php
and editprofile.php scripts to include the new Log-In script.
Upload all of the scripts to your web server, and then open the main Mismatch page
in a web browser. Click the View Profile or Edit Profile link to log in and access the
personalized pages. Of course, this will only work if you’ve already added a user with a
username and password to the database.

These two links lead to the protected
pages, which invoke the Log-In script
if a user isn’t logged in.

This password is SHA() encrypted
and compared with the password
in the database to determine if
the log-in is allowed.

The Log-In script uses HTTP
authentication to prevent
unauthorized access to the View
Profile and Edit Profile pages.

The home page is not protected by
the Log-In script, but it does serve
as the starting point for navigating
deeper into the application.
362

Chapter 7

building personalized web apps

Any Mismatch page that
requires log-in support only
has to include the login.php
script at the very beginning
of its code.

<?php
require_once('login.php');
?>
<html>
<head>
<title>Mismatch - View Profile</title>
<link rel="stylesheet" type="text/css" href="sty
le.css" />
</head>
<body>
<h3>Mismatch - View Profile</h3>
<?php
require_once('appvars.php');
require_once('connectvars.php');
// Connect to the database
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWO
RD, DB_NAME);
// Grab the profile data from the database
if (!isset($_GET['user_id'])) {
...

The Log-In script is included
first thing in the View Profile
and Edit Profile scripts to
enforce user log-ins.

viewprofile.php
<?php
require_once('login.php');
?>

Both pages signify the log-in
with a confirmation that is
provided by the Log-In script.

<html>
<head>
le>
<title>Mismatch - Edit Profile</tit
ss" href="style.css" />
<link rel="stylesheet" type="text/c
</head>
<body>
<h3>Mismatch - Edit Profile</h3>
<?php
require_once('appvars.php');
);
require_once('connectvars.php'
// Connect to the database
DB_USER, DB_PASSWORD, DB_NAME);
$dbc = mysqli_connect(DB_HOST,
if (isset($_POST['submit'])) {
the POST
// Grab the profile data from
...

If the username and password
check out, then the user is
logged in, and the rest of the
page is allowed to load.

Each user is now presented
with their very own customized
Mismatch experience.

editprofile.php

3

DONE

Connect the Log-In script to the
.
rest of the Mismatch application

you are here 4   363

mismatch needs a sign-up form

Ruby loves horror movies, cube puzzles,
and spicy food, but hates Mismatch
at the moment for not letting her
sign up and use the system.

I’d love to log in and start
working on my profile, but I
can’t figure out how to sign up.

New Mismatch users need a way to sign up.
The new Mismatch Log-In script does a good job of using
HTTP authentication to allow users to log in. Problem is,
users don’t have a way to sign up—logging in is a problem
when you haven’t even created a username or password yet.
Mismatch needs a Sign-Up form that allows new users to
join the site by creating a new username and password.

Username?

Password?

364

Chapter 7

building personalized web apps

A form for signing up new users
What does this new Sign-Up form look like? We know it needs to allow the
user to enter their desired username and password... anything else? Since
the user is establishing their password with the new Sign-Up form, and
passwords in web forms are typically masked with asterisks for security
purposes, it’s a good idea to have two password form fields. So the user enters
the password twice, just to make sure there wasn’t a typo.
So the job of the Sign-Up page is to retrieve the username and password
from the user, make sure the username isn’t already used by someone else,
and then add the new user to the mismatch_user database.

Username:

rubyr

Password: ******
**
Password: ******
**
(retype)
Sign Up

The password is double-entered to
help eliminate the risk of an incorrect
password getting set for the user.

Clicking the Sign Up button
results in the application
adding the username and
password to the database.

mismatch_user

...

password

user_id

username

10

baldpaul

11

jnettles

e511d7...

rubyr

062e4a...

...

12

d8a011...

...

One potential problem with the Sign-Up script involves the user attempting
to sign up for a username that already exists. The script needs to be smart
enough to catch this problem and force the user to try a different username.
So the job of the Sign-Up page is to retrieve the username and password
from the user, make sure the username isn’t already used by someone else,
and then add the new user to the mismatch_user database.

Since the passwords are now
encrypted, they're secure even
when viewing the database.

you are here 4   365

finish signup.php

PHP & MySQL Magnets

The Mismatch Sign-Up script uses a custom form to prompt the user
for their desired username and password. Problem is, the script code
is incomplete. Use the magnets below to finish up the script so new
users can sign up and join the Mismatch community.

Here’s the
Sign-Up form.

<?php
require_once('appvars.php');
require_once('connectvars.php');
// Connect to the database
SWORD, DB_NAME);
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PAS
if (isset($_POST['submit'])) {
// Grab the profile data from the POST

']));

'
= mysqli_real_escape_string($dbc, trim($_POST[

']));

'
= mysqli_real_escape_string($dbc, trim($_POST[

']));

'
= mysqli_real_escape_string($dbc, trim($_POST[
&& !empty($password2) &&
if (!empty($username) && !empty($password1)
==

(

)) {

using this username
// Make sure someone isn't already registered
me = '
$query = "SELECT * FROM mismatch_user WHERE userna

'";

$data = mysqli_query($dbc, $query);
if (mysqli_num_rows($data) == 0) {
into the database
// The username is unique, so insert the data
password, join_date) VALUES " .
ame,
(usern
r
ch_use
mismat
INTO
T
"INSER
=
$query
"('

', SHA('

mysqli_query($dbc, $query);

'), NOW())";

Don’t forget, you have to
escape an apostrophe if it
appears inside of single quotes.

// Confirm success with the user
created. You\'re now ready to log in and ' .
echo '<p>Your new account has been successfully
e</a>.</p>';
'<a href="editprofile.php">edit your profil
mysqli_close($dbc);
exit();
}

366

Chapter 7

building personalized web apps

else {
so display an error message
// An account already exists for this username,
for this username. Please use a different ' .
exists
y
alread
t
accoun
echo '<p class="error">An
'address.</p>';
= "";
}
}
else {
rd ' .
the sign-up data, including the desired passwo
echo '<p class="error">You must enter all of
'twice.</p>';
}
}
mysqli_close($dbc);
?>

rd to sign up to Mismatch.</p>
<p>Please enter your username and desired passwo
P_SELF']; ?>">
ER['PH
$_SERV
echo
p
="<?ph
action
<form method="post"
<fieldset>
<legend>Registration Info</legend>
<label for="username">Username:</label>
"

" name="

<input type="text" id="

; ?>" /><br />

)) echo

value="<?php if (!empty(

">Password:</label>

<label for="

" /><br />

" name="

" id="

<input type="

">Password (retype):</label>

<label for="

" /><br />

" name="

" id="

<input type="

</fieldset>
submit" />
<input type="submit" value="Sign Up" name="
>
</form

signup.php
$password1

password

password

$password1

$password1

username

password2

password2

password2

password2

username
username

password1
password1

password1

password1

$username
$username
$username
$username
$username
$username

$password2

$password2

you are here 4   367

the completed signup.php

PHP & MySQL Magnets Solution

The Mismatch Sign-Up script uses a custom form to prompt the user
for their desired username and password. Problem is, the script code
is incomplete. Use the magnets below to finish up the script so new
users can sign up and join the Mismatch community.

Here’s the
Sign-Up form.

<?php
require_once('appvars.php');
require_once('connectvars.php');
// Connect to the database
SWORD, DB_NAME);
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PAS
if (isset($_POST['submit'])) {
// Grab the profile data from the POST

Grab all of the user-entered data,
making sure to clean it up first.
'

_real_escape_string($dbc, trim($_POST[
$username = mysqli

$password1

'
= mysqli_real_escape_string($dbc, trim($_POST[

$password2

'
= mysqli_real_escape_string($dbc, trim($_POST[

username

password1 ']));

password2

&& !empty($password2) &&
if (!empty($username) && !empty($password1)
(

$password1

==

$password2

)) {

using this username
// Make sure someone isn't already registered
me = '
$query = "SELECT * FROM mismatch_user WHERE userna

']));

']));

Check to make sure that
none of the form fields
are empty and that
both passwords match.

$username '";

Perform a query to see if
any existing rows match the
username entered.

$data = mysqli_query($dbc, $query);
if (mysqli_num_rows($data) == 0) {
into the database
// The username is unique, so insert the data
password, join_date) VALUES " .
ame,
(usern
r
ch_use
mismat
INTO
T
"INSER
=
$query
"(' $username

', SHA('

If no match is found, the
username is unique, so we can
carry
out the INSERT.
Either password could be used here
since they must be equal to get to this point.

$password1

mysqli_query($dbc, $query);

'), NOW())";

// Confirm success with the user
created. You\'re now ready to log in and ' .
echo '<p>Your new account has been successfully
e</a>.</p>';
'<a href="editprofile.php">edit your profil
mysqli_close($dbc);
exit();
}

368

Chapter 7

Confirm the successful
sign-up with the user,
and exit the script.

building personalized web apps
else {
so display an error message
// An account already exists for this username,
for this username. Please use a different ' .
exists
y
alread
t
accoun
">An
"error
class=
'<p
echo
'address.</p>';

$username

= "";

The username is not
unique, so display an
error message.

Clear the $username
variable so that the
form field is cleared.

}
}
else {
rd ' .
the sign-up data, including the desired passwo
echo '<p class="error">You must enter all of
'twice.</p>';
}
}

One or more of the form fields are
empty, so display an error message.

mysqli_close($dbc);
?>

rd to sign up to Mismatch.</p>
<p>Please enter your username and desired passwo
P_SELF']; ?>">
ER['PH
<form method="post" action="<?php echo $_SERV
<fieldset>
<legend>Registration Info</legend>
<label for="username">Username:</label>
<input type="text" id="
value="<?php if (!empty(
<label for="
<input type="
<label for="
<input type="

password1

password

username " name="

$username ; ?>" /><br />

$username )) echo

">Password:</label>
" id="

password1

" name="

e):</label>
password2 ">Password (retyp

password

"

username

" id="

password2

" name="

1
password
password2

" /><br />

" /><br />

</fieldset>
submit" />
<input type="submit" value="Sign Up" name="
</form>

signup.php

Q:
A:

Why couldn’t you just use HTTP authentication for signing
up new users?

Because the purpose of the Sign-Up script isn’t to restrict
access to pages. The Sign-Up script’s job is to allow the user to
enter a unique username and password, and then add them to the
user database. Sure, it’s possible to use the HTTP authentication
window as an input form for the username and password, but the
authentication functionality is overkill for just signing up a new user.
It’s better to create a custom form for sign-ups—then you get the
benefit of double-checking the password for data entry errors.

Q:
A:

So does the Sign-Up script log in users after they sign up?

No. And the reason primarily has to do with the fact that the
Log-In script already handles the task of logging in a user, and
there’s no need to duplicate the code in the Sign-Up script. The
Sign-Up script instead presents a link to the Edit Profile page, which
is presumably where the user would want to go after signing in. And
since they aren’t logged in yet, they are presented with the Log-In
window as part of attempting to access the Edit Profile page. So the
Sign-Up script leads the user to the Log-In window via the Edit Profile
page, as opposed to logging them in automatically.
you are here 4   369

adding a sign-up link

Give users a chance to sign up
We have a Sign-Up script, but how do users get to it? We need to let users
know how to sign up. One option is to put a “Sign Up” link on the main
Mismatch page. That’s not a bad idea, but we would ideally need to be able
to turn it on and off based on whether a user is logged in. Another possibility
is to just show a “Sign Up” link as part of the Log-In script.
When a new user clicks the “View Profile” or “Edit Profile” links on the main
page, for example, they’ll be prompted for a username and password by the
Log-In script. Since they don’t yet have a username or password, they will
likely click Cancel to bail out of the log-in. That’s our chance to display a link
to the Sign-Up script by tweaking the log-in failure message displayed by the
Log-In script so that it provides a link to signup.php.
Here’s the original log-in failure code:

This code just shows a log-in
error message with no mention
of how to sign up for Mismatch.

exit('<h3>Mismatch</h3>Sorry, you must enter your username and password to log in and access ' .
'this page.');

This code actually appears in two different places in the Log-In script: when
no username or password are entered and when they are entered incorrectly.
It’s probably a good idea to go ahead and provide a “Sign Up” link in both
places. Here’s what the new code might look like:

This code is much more
helpful since it generates a
link to the Sign-Up script
so that the user can sign up.
exit('<h2>Mismatch</h2>Sorry, you must enter a valid username and password to log in and ' .
'access this page. If you aren\'t a registered member, please <a href="signup.php">sign up</a>.');

Nothing fancy here, just
a normal HTML link to
the signup.php script.

370

Chapter 7

building personalized web apps

Test Drive
Add Sign-Up functionality to Mismatch.
Create a new text file named signup.php, and enter the code for the Sign-Up script in
it (or download the script from the Head First Labs site at www.headfirstlabs.com/
books/hfphp). Then modify the login.php script to add links to the Sign-Up script for
users who can’t log in.
Upload the scripts to your web server, and then open the Sign-Up page in a web browser.
Sign up as a new user and then log in. Then edit your profile and view your profile to
confirm that the sign-up and log-in worked correctly. The application now has that
personalized touch that’s been missing.

HTTP authentication
is used to log in Ruby
based on her sign-up
information.

Sign-ups and log-ins turn an
impersonal application into a
community of interested users.

Cool! I can log in
to Mismatch and
then edit and view my
personal profile.

Ruby’s profile is
only accessible
after logging in.

you are here 4   371

mismatch also needs to let users log out

I share a computer with two
roommates, and I’d rather
they not have access to my
Mismatch profile. I need to
be able to log out!

Community web sites must allow users to log out so
that others can’t access their personal data from a
shared computer.
Allowing users to log out might sound simple enough, but it presents
a pretty big problem with HTTP authentication. The problem is that
HTTP authentication is intended to be carried out once for a given page
or collection of pages—it’s only reset when the browser is shut down. In
other words, a user is never “logged out” of an HTTP authenticated web
page until the browser is shut down or the user manually clears the HTTP
authenticated session. The latter option is easier to carry out in some
browsers (Firefox, for example) than others (Safari).

Once you log in, you
stay in until you
close the browser.

A log-out feature
would allow Sidney to
carefully control access
to her personal profile.

372

Chapter 7

Even though HTTP authentication presents a handy and simple way to
support user log-ins in the Mismatch application, it doesn’t provide any
control over logging a user out. We need to be able to both remember
users and also allow them to log out whenever they want.

building personalized web apps

Wouldn't it be dreamy if we could
remember the user without keeping them
logged in forever. Am I just a hopeless
PHP romantic?

you are here 4   373

introducing cookies

Sometimes you just need a cookie
The problem originally solved by HTTP authentication is twofold: there
is the issue of limiting access to certain pages, and there is the issue of
remembering that the user entered information about themselves. The
second problem is the tricky one because it involves an application
remembering who the user is across multiple pages (scripts). Mismatch
accomplishes this feat by checking the username and password stored
in the $_SERVER superglobal. So we took advantage of the fact that
PHP stores away the HTTP authentication username and password in a
superglobal that persists across multiple pages.

HTTP authentication
stores data persistently
on the client but doesn’t
allow you to delete it
when you’re done.

$_SERVER['PHP_AUTH_USER']
$_SERVER['PHP_AUTH_PW']

Cookies allow you to
persistently store small
pieces of data on the
client that can outlive
any single script... and
can be deleted at will!

Client web
browser
374

Chapter 7

The $_SERVER
superglobal stores
the username and
password persistently.

But we don’t have the luxury of HTTP authentication anymore because
it can’t support log-outs. So we need to look elsewhere for user persistence
across multiple pages. A possible solution lies in cookies, which are
pieces of data stored by the browser on the user’s computer. Cookies are
a lot like PHP variables except that cookies hang around after you close
the browser, turn off your computer, etc. More importantly, cookies can
be deleted, meaning that you can eliminate them when you’re finished
storing data, such as when a user indicates they want to log out.
Store
cookie data

Web server

Retrieve
cookie data

Cookie data is stored on the user’s computer by their web browser. You
have access to the cookie data from PHP code, and the cookie is capable
of persisting across not only multiple pages (scripts), but even multiple
browser sessions. So a user closing their browser won’t automatically log
them out of Mismatch. This isn’t a problem for us because we can delete
a cookie at any time from script code, making it possible to offer a log-out
feature. We can give users total control over when they log out.

building personalized web apps

What’s in a cookie?
A cookie stores a single piece of data under a unique name, much
like a variable in PHP. Unlike a variable, a cookie can have an expiration
date. When this expiration date arrives, the cookie is destroyed. So cookies
aren’t exactly immortal—they just live longer than PHP variables. You can
create a cookie without an expiration date, in which case it acts just like a
PHP variable—it gets destroyed when the browser closes.

Name
The unique name of the cookie
user_id = 1
12/08/2009

Value
The value stored in the cookie

Expiration date
The date when the cookie
expires... and meets its demise
Cookies allow you to store a string of text under a certain name, kind of
like a PHP text variable. It’s the fact that cookies outlive normal script
data that makes them so powerful, especially in situations where an
application consists of multiple pages that need to remember a few pieces
of data, such as log-in information.

username = sidneyk
01/01/3000

Setting a cookie’s
expiration date far
into the future makes
it more permanent.

user_id = 1

Not providing an
expiration date at
all causes a cookie to
be deleted when the
browser is closed.

So Mismatch can mimic the persistence provided by the $_SERVER
superglobal by setting two cookies—one for the username and one for the
password. Although we really don’t need to keep the password around, it
might be more helpful to store away the user ID instead.

Q:

What’s the big deal about cookies
being persistent? Isn’t data stored in a
MySQL database persistent too?

A:

Yes, database data is most certainly
persistent. In fact, it’s technically much more
persistent than a cookie because there is
no expiration date involved—if you stick
data in a database, it stays there until you
explicitly remove it. The real issue in regard
to cookies and persistence is convenience.
We don’t need to store the current user’s ID
or username for all eternity just to allow them
to access their profile; we just need a quick
way to know who they are. What we really
need is temporary persistence, which might
seem like an oxymoron until you consider the
fact that we need data to hang around longer
than a page (persistent), but not forever.
you are here 4   375

the setcookie() function

Use
Bake cookies with PHP
PHP provides access to cookies through a function called setcookie()
and a superglobal called $_COOKIE. The setcookie() function is
used to set the value and optional expiration date of a cookie, and the
$_COOKIE superglobal is used to retrieve the value of a cookie.

setcookie('username', 'sidneyk');

The first argument
to setcookie() is the
name of the cookie.

username = sidneyk

The value to be stored
in the cookie is passed
as the second argument.

echo('<p class="login">You are logged in as ' . $_COOKIE['username'] . '.</p>');

The name of the cookie is used
to reference the cookie value
in the $_COOKIE superglobal.

The power of setting a cookie is that the cookie data persists across
multiple scripts, so we can remember the username without having to
prompt the user to log in every time they move from one page to another
within the application. But don’t forget, we also need to store away the
user’s ID in a cookie since it serves as a primary key for database queries.

setcookie('user_id', '1');

Cookies are always stored as
text, so even though the user
ID is a number, we store it in
a cookie as the string ‘1’.
The setcookie() function also accepts an optional third argument
that sets the expiration date of the cookie, which is the date upon which
the cookie is automatically deleted. If you don’t specify an expiration
date, as in the above example, the cookie automatically expires when the
browser is closed.
376

Chapter 7

The PHP
setcookie() function
allows you to store
data in cookies.
user_id = 1

building personalized web apps

Switching Mismatch to use cookies involves more than just writing a new
Log-Out script. We must first revisit the Log-In script and change it to use
cookies instead of HTTP authentication. Circle and annotate the parts of
the Log-In code that you think need to change to accommodate cookies.

<?php
require_once('connectvars.php');
if (!isset($_SERVER['PHP_AUTH_USER']) || !isset
($_SERVER['PHP_AUTH_PW'])) {
// The username/password weren't entered so
send the authentication headers
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Misma
tch"');
exit('<h3>Mismatch</h3>Sorry, you must enter
your username and password to ' .
'log in and access this page. If you aren\'
t a registered member, please ' .
'<a href="signup.php">sign up</a>.');
}
// Connect to the database
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PAS
SWORD, DB_NAME);

// Grab the user-entered log-in data
$user_username = mysqli_real_escape_string($dbc
, trim($_SERVER['PHP_AUTH_USER']));
$user_password = mysqli_real_escape_string($dbc
, trim($_SERVER['PHP_AUTH_PW']));
// Look up the username and password in the
database
$query = "SELECT user_id, username FROM mismat
ch_user WHERE username = " .
"'$user_username' AND password = SHA('$user_p
assword')";
$data = mysqli_query($dbc, $query);
if (mysqli_num_rows($data) == 1) {
// The log-in is OK so set the user ID and
username variables
$row = mysqli_fetch_array($data);
$user_id = $row['user_id'];
$username = $row['username'];
}
else {
// The username/password are incorrect so send
the authentication headers
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Misma
tch"');
exit('<h2>Mismatch</h2>Sorry, you must enter
a valid username and password ' .
'to log in and access this page. If you aren\'
t a registered member, ' .
'please <a href="signup.php">sign up</a>.');
}
// Confirm the successful log-in
echo('<p class="login">You are logged in as
' . $username . '.</p>');
?>

login.php

you are here 4   377

sharpen your pencil solution

We need to check for the
existence of a cookie to
see if the user is logged in
or not.

Switching Mismatch to use cookies involves more than just writing a new
Log-Out script. We must first revisit the Log-In script and change it to use
cookies instead of HTTP authentication. Circle and annotate the parts of
the Log-In code that you think need to change to accommodate cookies.

Instead of getting the username and
password from an authentication window,
we need to use a form with POST data.

<?php
require_once('connectvars.php');
if (!isset($_SERVER['PHP_AUTH_USER']) || !isset
($_SERVER['PHP_AUTH_PW'])) {
// The username/password weren't entered so
send the authentication headers
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Misma
tch"');
exit('<h3>Mismatch</h3>Sorry, you must enter
your username and password to ' .
'log in and access this page. If you aren\'
t a registered member, please ' .
'<a href="signup.php">sign up</a>.');
}

We no longer
need to
send HTTP
authentication
headers.

// Connect to the database
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PAS
SWORD, DB_NAME);

// Grab the user-entered log-in data
$user_username = mysqli_real_escape_string($dbc
, trim($_SERVER['PHP_AUTH_USER']));
$user_password = mysqli_real_escape_string($dbc
, trim($_SERVER['PHP_AUTH_PW']));
// Look up the username and password in the
database
$query = "SELECT user_id, username FROM mismat
ch_user WHERE username = " .
"'$user_username' AND password = SHA('$user_p
assword')";
$data = mysqli_query($dbc, $query);

The query doesn’t
have to change at all!

if (mysqli_num_rows($data) == 1) {
// The log-in is OK so set the user ID and
username variables
$row = mysqli_fetch_array($data);
$user_id = $row['user_id'];
$username = $row['username'];
}
else {
// The username/password are incorrect so send
the authentication headers
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Misma
tch"');
exit('<h2>Mismatch</h2>Sorry, you must enter
a valid username and password ' .
'to log in and access this page. If you aren\'
t a registered member, ' .
'please <a href="signup.php">sign up</a>.');
}

Here we need to set
two cookies instead of
setting script variables.

// Confirm the successful log-in
echo('<p class="login">You are logged in as
' . $username . '.</p>');
?>

Since we can’t rely on the HTTP authentication window
for entering the username and password, we need to
create an HTML Log-In form for entering them.
378

Chapter 7

login.php

building personalized web apps

Rethinking the flow of log-ins
Using cookies instead of HTTP authentication for Mismatch log-ins
involves more than just rethinking the storage of user data. What about
the log-in user interface? The cookie-powered log-in must provide its
own form since it can’t rely on the authentication window for entering a
username and password. Not only do we have to build this form, but we
need to think through how it changes the flow of the application as users
log in and access other pages.

Clicking the new
“Log In” link leads
to the Log-In page,
where a user can
enter their log-in
information to log in.

Username:

A new form takes the
place of the HTTP
authentication window
for entering the username
and password for log-ins.

sidneyk

Password: ****
**
Log In

After successfully logging
in, the user is redirected
back to the home page,
where the menu now reveals
that they are logged in.

When not logged in, the
latest members are
displayed as static names.

index.php

The Log-Out script is
accessible via a link that is
part of the log-in status.

Restricted pages are
now accessible since
the user is logged in.

After logging in, the
latest member names
change to links to their
respective profile views.

The main
navigation menu
includes a Log
Out link that
also shows the
username of the
logged in user.

index.php

viewprofile.php
you are here 4   379

login.php—now cookie-powered!

A cookie-powered log-in
The new version of the Log-In script that relies on cookies for log-in
persistence is a bit more complex than its predecessor since it must
provide its own form for entering the username and password. But it’s
more powerful in that it provides log-out functionality.
<?php
require_once('connectvars.php');

Error messages are now stored
in a variable and displayed, if
login.php
// Clear the error message
necessary, later in the script.
$error_msg = "";
Here’s the new
Log-In form.
_id cookie to
// If the user isn't logged in, try to log them in Check the user
in.
ed
logg
is
user
if (!isset($_COOKIE['user_id'])) {
see if the
if (isset($_POST['submit'])) {
If the user isn’t logged
// Connect to the database
in,
see if they’ve
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
submitted log-in data.
// Grab the user-entered log-in data
The user-entered data
$user_username = mysqli_real_escape_string($dbc, trim($_POST['username']));
$user_password = mysqli_real_escape_string($dbc, trim($_POST['password']));
now comes from form
POST data instead of
if (!empty($user_username) && !empty($user_password)) {
an authentication window.
// Look up the username and password in the database
$query = "SELECT user_id, username FROM mismatch_user WHERE username = '$user_username' AND " .
"password = SHA('$user_password')";
$data = mysqli_query($dbc, $query);

Log in the user by setting
user_id and username cookies.

if (mysqli_num_rows($data) == 1) {
// The log-in is OK so set the user ID and username cookies, and redirect to the home page
$row = mysqli_fetch_array($data);
setcookie('user_id', $row['user_id']);
setcookie('username', $row['username']);
$home_url = 'http://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/index.php';
header('Location: ' . $home_url);
}
else {
// The username/password are incorrect so set an error message
$error_msg = 'Sorry, you must enter a valid username and password to log in.';
}

?>

}

}

}
else {
// The username/password weren't entered so set an error message
$error_msg = 'Sorry, you must enter your username and password to log in.';
}

<html>
<head>
<title>Mismatch - Log In</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<h3>Mismatch - Log In</h3>

continues on the facing pa

ge...

380

Chapter 7

Redirect the user
to the Mismatch
home page upon a
successful log-in.

Set the error message
variable if anything is
wrong with the log-in data.
The Log-In script is now a full
web page, so it requires all the
standard HTML elements.

building personalized web apps
<?php
// If the cookie is empty, show any error message and the log-in form; otherwise confirm the log-in
if (empty($_COOKIE['user_id'])) {
echo '<p class="error">' . $error_msg . '</p>';
?>

If the user still isn’t logged
in at this point, go ahead
and show the error message.

<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<fieldset>
<legend>Log In</legend>
<label for="username">Username:</label>
<input type="text" id="username" name="username"
value="<?php if (!empty($user_username)) echo $user_username; ?>" /><br />
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
</fieldset>
<input type="submit" value="Log In" name="submit" />
</form>

Everything prior to this curly brace
is still part of the first if clause.

These two form fields are
used to enter the username
and password for logging in.

<?php
}
else {
// Confirm the successful log in
echo('<p class="login">You are logged in as ' . $_COOKIE['username'] . '.</p>');
}
?>
</body>
</html>

Finish the HTML code to
complete the Log-In web page.

Q:

Why is it necessary to store both
the user ID and username in cookies?

A:

If the user is logged in at
this point, just tell them so.

Since both pieces of information
uniquely identify a user within the Mismatch
user database, you could use either one for
the purpose of keeping up with the current
user. However, user_id is a better (more
efficient) user reference with respect to the
database because it is a numeric primary
key. On the other hand, user_id is fairly
cryptic and doesn’t have any meaning to the
user, so username comes in handy for letting
the user know they are logged in, such as
displaying their name on the page. Since
multiple people sometimes share the same
computer, it is important to not just let the
user know they are logged in, but also who
they are logged in as.

Q:

Then why not also store the
password in a cookie as part of the log-in
data?

A:

The password is only important for
initially verifying that a user is who they
claim to be. Once the password is verified as
part of the log-in process, there is no reason
to keep it around. Besides, passwords
are very sensitive data, so it’s a good idea
to avoid storing them temporarily if at all
possible.

Q:

It looks as if the form in the
Log-In script is actually inside the if
statement? Is that possible?

A:

Yes. In fact it’s quite common for PHP
code to be “broken up” around HTML code,
as is the case with the Log-In script. Just
because you close a section of PHP code
with ?>, doesn’t mean the logic of the code
is closed. When you open another section of
PHP code with <?php, the logic continues
right where it left off. In the Log-In script, the
HTML form is contained within the first if
branch, while the else branch picks up
after the form code. Breaking out of PHP
code into HTML code like this keeps you
from having to generate the form with a
bunch of messy echo statements.
you are here 4   381

mismatch's dynamic menu

Navigating the Mismatch application
The new Log-In script changes the flow of the Mismatch application,
requiring a simple menu that appears on the home page (index.php).
This menu is important because it provides access to the different major
parts of the application, currently the View Profile and Edit Profile pages,
as well as the ability for users to log in, sign up, and log out depending on
their current log-in state. The fact that the menu changes based on the
user’s log-in state is significant and is ultimately what gives the menu its
power and usefulness.

This menu appears when a
user is not logged in, giving
them an opportunity to
either log in or sign up.

A different menu is shown
depending on whether the
username cookie is set.

username =

?

The index.php script
knows to show the
limited menu when
it can’t find the
username cookie.
The menu is generated by PHP code within the index.php script, and
this code uses the $_COOKIE superglobal to look up the username cookie
and see if the user is logged in or not. The user ID cookie could have also
been used, but the username is actually displayed in the menu, so it makes
more sense to check for it instead.
382

Chapter 7

building personalized web apps

The user_id cookie isn’t
used for the different
menus but is still
important for Mismatch
user persistence.
The username cookie
determines which
menu is displayed

user_id = 1

username = sidneyk

The username
cookie also lets
the user know
who is logged in.

Menu for logged
in users
// Generate the navigation menu
if (isset($_COOKIE['username'])) {
echo '&#10084; <a href="viewprofile.php">View Profile</a><br />';
echo '&#10084; <a href="editprofile.php">Edit Profile</a><br />';
echo '&#10084; <a href="logout.php">Log Out (' . $_COOKIE['username'] . ')</a>';
}
else {
echo '&#10084; <a href="login.php">Log In</a><br />';
echo '&#10084; <a href="signup.php">Sign Up</a>';
}
Menu for visitors

The little heart symbols next to
each menu item are made possible
by this HTML entity, which is
supported on most browsers.

(users who aren’t
logged in)

you are here 4   383

log out users by deleting cookies

Hello, remember
me? I still really,
really need to log out.

We really need to let users log out.
Cookies have made logging into Mismatch and navigating the site a bit
cleaner, but the whole point of switching from HTTP authentication to
cookies was to allow users to log out. We need a new Log-Out script that
deletes the two cookies (user ID and username) so that the user no longer
has access to the application. This will prevent someone from getting on
the same computer later and accessing a user’s private profile data.
Since there is no user interface component involved in actually logging
out a user, it’s sufficient to just redirect them back to the home page after
logging them out.

Sidney is
still waiting
to log out...

logout.php

The Log-Out script
deletes the user
log-in cookies and
redirects back to
the home page.

384

Chapter 7

building personalized web apps

Logging out means deleting cookies
Logging out a user involves deleting the two cookies that keep track of the
user. This is done by calling the setcookie() function, and passing an
expiration date that causes the cookies to get deleted at that time.

The current time

Seconds

Minutes

setcookie('username', 'sidneyk', time() + (60 * 60 * 8));

This code sets an expiration date 8 hours into the future, which means the
cookie will be automatically deleted in 8 hours. But we want to delete a
cookie immediately, which requires setting the expiration date to a time in
the past. The amount of time into the past isn’t terribly important—just
pick an arbitrary amount of time, such as an hour, and subtract it from
the current time.
setcookie('username', 'sidneyk', time() - 3600);

60 seconds * 60 minutes = 3600
seconds, which is 1 hour into the past.

Hours
Together, this expression
sets an expiration date
that is 8 hours from
the current time.

To delete a
cookie, just set its
expiration date to
a time in the past.

The Log-Out script for Mismatch is missing a few pieces of code. Write the missing code, making
sure that the log-in cookies get deleted before the Log-Out page is redirected to the home page.

<?php
// If the user is logged in, delete the cookie to log them out
if (

) {

// Delete the user ID and username cookies by setting their expirations to an hour ago (3600)

}
// Redirect to the home page
$home_url = 'http://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '

';

header('Location: ' . $home_url);
?>

you are here 4   385

the complete logout.php script

The Log-Out script for Mismatch is missing a few pieces of code. Write the missing code, making
sure that the log-in cookies get deleted before the Log-Out page is redirected to the home page.

<?php
// If the user is logged in, delete the cookie to log them out
if (

isset($_COOKIE[‘user_id’])

) {

Only log out a user if they
are already logged in.

// Delete the user ID and username cookies by setting their expirations to an hour ago (3600)

}

setcookie(‘user_id’, ‘’, time() - 3600);
setcookie(‘username’, ‘’, time() - 3600);

Set each cookie to an hour
in the past so that they
are deleted by the system.

// Redirect to the home page

Redirect to the
Mismatch home page,
which is constructed as
an absolute URL.

$home_url = 'http://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '
header('Location: ' . $home_url);
?>

/index.php

A location header results in the
browser redirecting to another page.

Test Drive
Use cookies to add Log-Out functionality to Mismatch.
Modify the Mismatch scripts so that they use cookies to allows users to log in and out (or
download the scripts from the Head First Labs site at www.headfirstlabs.com/
books/hfphp. The cookie modifications involve changes to the index.php, login.php,
logout.php, editprofile.php, and viewprofile.php scripts. The changes
to the latter two scripts are fairly minor, and primarily involve changing $user_id and
$username global variable references so that they use the $_COOKIE superglobal instead.
Upload the scripts to your web server, and then open the main Mismatch page (index.php)
in a web browser. Take note of the navigation menu, and then click the “Log In” link and log
in. Notice how the Log-In script leads you back to the main page, while the menu changes to
reflect your logged in status. Now click “Log Out” to blitz the cookies and log out.

386

Chapter 7

';

building personalized web apps

Cookies are
created to
remember the
user and log in.

Logging in and
out of Mismatch
is now controlled
entirely by cookies.

Cookies are thrown
away to forget the
user and log out.

Q:
A:
Q:

So simply deleting the cookies is all that is required to log out?

I’m outta here
for now. See ya!

Sidney is pleased
that she can log
out and know that
her Mismatch
profile can’t be
edited by anyone
while she’s away.

Yes. Cookies are responsible for storing all of the log-in information for Mismatch (user ID and
username), so deleting them results in a complete log-out.

Why are the cookies set to an hour in the past in order to be deleted? Is there
something significant about an hour?

A:

No. A cookie is automatically deleted by the web browser once its expiration date/time passes.
So deleting a cookie involves setting the expiration to any time in the past. An hour (3600 seconds)
is just an arbitrary amount of time chosen to consistently indicate that we’re deleting a cookie.
you are here 4   387

storing user data on the server, instead of the client

Mismatch user Jason, lover of
hiking, body piercings, and Howard
Stern, has cookies disabled in his
browser, which presents a problem
for logging in.

The attempted
log-in starts
here.

Uh-oh. I have cookies
disabled in my browser,
and I can’t log in. What
am I supposed to do?

, the
Since cookies are disabdledjust
an
ils
Log-In script fa
e home
sends the user back togethd in.
log
page without being

$_COOKIE

Client web
browser

Web server

The browser rejects the
cookies, preventing the Log-In
script from setting them.

The server attempts
to set the user ID
and username cookies
on the browser.

Who cares about
Jason? Don’t most people
have cookies enabled?

Yes, but web applications should be as accessible to
as many people as possible.
Some people just aren’t comfortable using cookies, so they opt for the
added security of having them disabled. Knowing this, it’s worth trying to
accommodate users who can’t rely on cookies to log in. But there’s more.
It turns out that there’s another option that uses the server to store
log-in data, as opposed to the client. And since our scripts are already
running on the server, it only makes sense to store log-in data there as well.
388

Chapter 7

building personalized web apps

Sessions aren’t dependent on the client
Cookies are powerful little guys, but they do have their limitations, such
as being subject to limitations beyond your control. But what if we didn’t
have to depend on the browser? What if we could store data directly
on the server? Sessions do just that, and they allow you to store away
individual pieces of information just like with cookies, but the data gets
stored on the server instead of the client. This puts session data outside of
the browser limitations of cookies.
Web server

Store session data

Unlike cookies,
sessions store their
data on the server.
ssion data
Retrieve se

Sessions allow you to
persistently store small
pieces of data on the
server, independently
of the client.

The browser doesn’t
factor directly
into the storage of
session data since
everything is stored
on the server.
Client web
browser

Sessions store data in session variables, which are logically equivalent
to cookies on the server. When you place data in a session variable using
PHP code, it is stored on the server. You can then access the data in the
session variable from PHP code, and it remains persistent across multiple
pages (scripts). Like with cookies, you can delete a session variable at any
time, making it possible to continue to offer a log-out feature with sessionbased code.

user_id = 1

username = sidneyk

Surely there’s a catch, right? Sort of. Unlike cookies, sessions don’t
offer as much control over how long a session variable stores data.
Session variables are automatically destroyed as soon as
a session ends, which usually coincides with the user shutting
down the browser. So even though session variables aren’t stored
on the browser, they are indirectly affected by the browser since
they get deleted when a browser session ends.

Since session data is
stored on the server,
it is more secure and
more reliable than
data stored in cookies.
A user can’t manually delete
session data using their
browser, which can be a
problem with cookies.

There isn’t an expiration date associated
with session variables because they are
automatically deleted when a session ends.

you are here 4   389

the session_start() and session_destroy() functions

The life and times of sessions
Sessions are called sessions for a reason—they have a very clear start
and finish. Data associated with a session lives and dies according to the
lifespan of the session, which you control through PHP code. The only
situation where you don’t have control of the session life cycle is when the
user closes the browser, which results in a session ending, whether you like
it or not.
You must tell a session when you’re ready to start it up by calling the
session_start() PHP function.

This PHP function
starts a session.

session_start();

The PHP session_start()
function starts a session
and allows you to begin
storing data in session
variables.

Calling the session_start() function doesn’t set any data—its job
is to get the session up and running. The session is identified internally
by a unique session identifier, which you typically don’t have to concern
yourself with. This ID is used by the web browser to associate a session
with multiple pages.

Web server

This is the unique
session ID, which
is automatically
generated as part
of a new session.

Client web
browser

tksf820j9hq7
f9p
t7vdt5o1ceb2
login.ph
lo
gout.php

index.php

When a session is
started, a session ID
is set that uniquely
identifies the session.

The session ID is used
behind the scenes to allow
multiple pages to share
access to session data.

viewprofile.php
editprofile.php

The session ID isn’t destroyed until the session is closed, which
happens either when the browser is closed or when you call the
session_destroy() function.

The session_destroy()
function closes a session.
390

Chapter 7

session_destroy();

This PHP function
ends a session.

If you close a session yourself with this function, it doesn’t
automatically destroy any session variables you’ve stored. Let’s take
a closer look at how sessions store data to uncover why this is so.

building personalized web apps

Keeping up with session data
The cool thing about sessions is that they’re very similar to cookies in
terms of how you use them. Once you’ve started a session with a call to
session_start(), you can begin setting session variables, such as
Mismatch log-in data, with the $_SESSION superglobal.

The session variable
is created and
stored on the server.

$_SESSION['username'] = 'sidneyk';

The name of the session
variable is used as an index into
the $_SESSION superglobal.

username = sidneyk

The value to be stored
is just assigned to the
$_SESSION superglobal.

echo('<p class="login">You are logged in as ' . $_SESSION['username'] . '.</p>');
Unlike cookies, session variables don’t require any kind of special function
to set them—you just assign a value to the $_SESSION superglobal,
making sure to use the session variable name as the array index.

But we’re not quite done. Sessions can actually use cookies behind the
scenes. If the browser allows cookies, a session may possibly set a cookie
that temporarily stores the session ID. So to fully close a session via PHP
code, you must also delete any cookie that might have been automatically
created to store the session ID on the browser. Like any other cookie, you
destroy this cookie by setting its expiration to some time in the past. All
you need to know is the name of the cookie, which can be found using the
session_name() function.

If a session is using a er
cookie to help rememb e
the session ID, then thie
ID is stored in a cook n.
named after the sessio

tk
sf
82
0j
..
.

This code kills all of the session
variables in the current session.

=

$_SESSION = array();

Session variables
are not automatically
deleted when a
session is destroyed.

PH
PS
ES
SI
D

What about deleting session variables? Destroying a session via
session_destroy() doesn’t actually destroy session variables, so
you must manually delete your session variables if you want them to be
killed prior to the user shutting down the browser (log-outs!). A quick
and effective way to destroy all of the variables for a session is to set the
$_SESSION superglobal to an empty array.

To access the session variable, just
use the $_SESSION superglobal
and the session variable name.

if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 3600);
}

First check to see
if a session cookie
actually exists.

Destroy the session cookie
by setting its expiration
to an hour in the past.
you are here 4   391

how mismatch works with sessions

Start here!

Renovate Mismatch with sessions
Reworking the Mismatch application to use a session to store log-in data
isn’t as dramatic as it may sound. In fact, the flow of the application
remains generally the same—you just have to take care of a little extra
bookkeeping involved in starting the session, destroying the
session, and then cleaning up after the session.

session_start();

The session_start() function
gets things started by
opening a session.
If cookies are enabled, the
server creates one to hold
the session ID - otherwise
the ID is passed through
the URL of each page.
session_destroy();

The session_destroy()
function ends the session,
preventing it from being
used in another page.

The session variables
are destroyed by
clearing out the
$_SESSION array.

392

Chapter 7

If a cookie was used
to hold the session
ID, it is destroyed.
Log-in data is now
remembered using a session
instead of cookies.

Two session
variables are
created to store
the user ID and
username for the
log-in.

building personalized web apps

Log out with sessions
Logging a user out of Mismatch requires a little more work with sessions
than the previous version with its pure usage of cookies. These steps must
be taken to successfully log a user out of Mismatch using sessions.
1

Delete the session variables.

2

Check to see if a session cookie
exists, and if so, delete it.

3

Destroy the session.

4

Redirect the user to the home page.

You don’t know for certain
if a session cookie is being
used without checking.

OK, so this is a bonus step
that isn’t strictly required
to log the user out, but is
helpful nonetheless.

The Log-Out script for Mismatch is undergoing an overhaul to use sessions
instead of pure cookies for log-in persistence. Write the missing code to
“sessionize” the Log-Out script, and then annotate which step of the log-out
process it corresponds to.
<?php
// If the user is logged in, delete the session vars to log them out
session_start();
if (

){

// Delete the session vars by clearing the $_SESSION array

// Delete the session cookie by setting its expiration to an hour ago (3600)
if (isset($_COOKIE[session_name()])) {
}
// Destroy the session
}
// Redirect to the home page
$home_url = 'http://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/index.php';
header('Location: ' . $home_url);
?>

you are here 4   393

the "sessionized" logout.php

The Log-Out script for Mismatch is undergoing an overhaul to use sessions
instead of pure cookies for log-in persistence. Write the missing code to
“sessionize” the Log-Out script, and then annotate which step of the log-out
process it corresponds to.

1 Delete the session variables.
2 Check to see if a session cookie exists, and if so,
delete it.
3 Destroy the session.
4 Redirect the user to the home page.

Even when logging out, you have
to first start the session in order
to access the session variables.

<?php

// If the user is logged in, delete the session vars to log them out

Now a session variable is used to check
the log-in status instead of a cookie.

session_start();
if (

isset($_SESSION[‘user_id’])

){

// Delete the session vars by clearing the $_SESSION array

$_SESSION = array();

the
To clear out the session variables, assign
ay.
arr
ty
emp
an
$_SESSION superglobal

1

// Delete the session cookie by setting its expiration to an hour ago (3600)
if (isset($_COOKIE[session_name()])) {

setcookie(session_name(), ‘’, time() - 3600);

2

}
// Destroy the session

session_destroy();

}

3

If a session cookie exists, delete it by
setting its expiration to an hour ago.

Destroy the session with
a call to the built-in
session_destroy() function.

// Redirect to the home page
$home_url = 'http://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/index.php';
header('Location: ' . $home_url);
?>

394

Chapter 7

4

building personalized web apps

The move from cookies to sessions impacts more than just the Log-Out
script. Match the other pieces of the Mismatch application with how
they need to change to accommodate sessions.

appvars.php

connectvars.php

No change since the script has no direct dependence on
log-in persistence.
Sessions are required to remember who the user is. Call
the session_start() function to start the session,
and then change $_COOKIE references to $_SESSION.

login.php

signup.php

Sessions are required to control the navigation menu.
Call the session_start() function to start the
session, and then change $_COOKIE references to
$_SESSION.

index.php

viewprofile.php

editprofile.php

you are here 4   395

how do i change solution

Solution
The move from cookies to sessions impacts more than just the Log-Out
script. Match the other pieces of the Mismatch application with how
they need to change to accommodate sessions.

appvars.php

connectvars.php

No change since the script has no direct dependence on
log-in persistence.
Sessions are required to remember who the user is. Call
the session_start() function to start the session,
and then change $_COOKIE references to $_SESSION.

login.php

signup.php

index.php

viewprofile.php

editprofile.php

396

Chapter 7

Sessions are required to control the navigation menu.
Call the session_start() function to start the
session, and then change $_COOKIE references to
$_SESSION.

building personalized web apps







HTTP authentication is handy for restricting access to
individual pages, but it doesn’t offer a good way to “log
out” a user when they’re finished accessing a page.
Cookies let you store small pieces of data on the client
(web browser), such as the log-in data for a user.
All cookies have an expiration date, which can be far
into the future or as near as the end of the browser
session.

Q:

The session_start()
function gets called in a lot of different
places, even after a session has been
started. Are multiple sessions being
created with each call to session_
start()?

A:

No. The session_start()
function doesn’t just start a new session—it
also taps into an existing session. So when
a script calls session_start(),
the function first checks to see if a session
already exists by looking for the presence
of a session ID. If no session exists, it
generates a new session ID and creates the
new session. Future calls to session_
start() from within the same application
will recognize the existing session and use it
instead of creating another one.

Q:

So how does the session ID
get stored? Is that where sessions
sometimes use cookies?

A:

Yes. Even though session data gets
stored on the server and, therefore, gains
the benefit of being more secure and outside
of the browser’s control, there still has to be
a mechanism for a script to know about the
session data.







To delete a cookie, you just set its expiration to a time in
the past.
Sessions offer similar storage as cookies but are stored
on the server and, therefore, aren’t subject to the same
browser limitations, such as cookies being disabled.
Session variables have a limited lifespan and are always
destroyed once a session is over (for example, when the
browser is closed).

This is what the session ID is for—it uniquely
identifies a session and the data associated
with it. This ID must somehow persist on the
client in order for multiple pages to be part of
the same session. One way this session ID
persistence is carried out is through a cookie,
meaning that the ID is stored in a cookie,
which is then used to associate a script with
a given session.

Q:

If sessions are dependent on
cookies anyway, then what’s the big deal
about using them instead of cookies?

A:

Sessions are not entirely dependent
on cookies. It’s important to understand
that cookies serve as an optimization for
preserving the session ID across multiple
scripts, not as a necessity. If cookies are
disabled, the session ID gets passed from
script to script through a URL, similar to how
you’ve seen data passed in a GET request.
So sessions can work perfectly fine without
cookies. The specifics of how sessions
react in response to cookies being disabled
are controlled in the php.ini configuration
file on the web server via the session.
use_cookies, session.use_
only_cookies, and session.
use_trans_sid settings.

Q:

It still seems strange that sessions
could use cookies when the whole point
is that sessions are supposed to be
better than cookies. What gives?

A:

While sessions do offer some clear
benefits over cookies in certain scenarios,
they don’t necessarily have an either/or
relationship with cookies. Sessions certainly
have the benefit of being stored on the
server instead of the client, which makes
them more secure and dependable. So if you
ever need to store sensitive data persistently,
then a session variable would provide more
security than a cookie. Sessions are also
capable of storing larger amounts of data
than cookies. So there are clear advantages
to using sessions regardless of whether
cookies are available.
For the purposes of Mismatch, sessions offer
a convenient server-side solution for storing
log-in data. For users who have cookies
enabled, sessions provide improved security
and reliability while still using cookies as an
optimization. And in the case of users who
don’t have cookies enabled, sessions can
still work by passing the session ID through
a URL, foregoing cookies altogether.

you are here 4   397

migrating from cookies to sessions

Complete the session transformation
Even though the different parts of Mismatch affected by sessions use
them to accomplish different things, the scripts ultimately require similar
changes in making the migration from cookies to sessions. For one, they
all must call the session_start() function to get rolling with
sessions initially. Beyond that, all of the changes involve moving from
the $_COOKIE superglobal to the $_SESSION superglobal, which is
responsible for storing session variables.

<?php
session_start();
?>

All of the session-powered
scripts start out with a call
to session_start() to get
the session up and running.

to log them in
// If the user isn't logged in, try
{
if (!isset($_SESSION['user_id']))
if (isset($_POST['submit'])) {
// Connect to the database
R, DB_PASSWORD, DB_NAME);
$dbc = mysqli_connect(DB_HOST, DB_USE
// Grab the user-entered log-in data
);
_string($dbc, trim($_POST['username'])
$user_username = mysqli_real_escape
);
_string($dbc, trim($_POST['password'])
$user_password = mysqli_real_escape

($user_password)) {
if (!empty($user_username) && !empty
rd in the database
// Look up the username and passwo
= '$user_username' AND " .
FROM mismatch_user WHERE username
$query = "SELECT user_id, username
"password = SHA('$user_password')";
$data = mysqli_query($dbc, $query);

}

if (mysqli_num_rows($data) == 1) {
redirect to the home page
ID and username session vars, and
// The log-in is OK so set the user
$row = mysqli_fetch_array($data);
d'];
$_SESSION['user_id'] = $row['user_i
me'];
$_SESSION['username'] = $row['userna
LF']) . '/index.php';
TP_HOST'] . dirname($_SERVER['PHP_SE
$home_url = 'http://' . $_SERVER['HT
url);
$home_
.
'
tion:
('Loca
header
}
else {
ect so set an error message
// The username/password are incorr
in.';
a valid username and password to log
$error_msg = 'Sorry, you must enter
}

The Log-In script uses sessions to
remember the user ID and username
for log-in persistence, and it does
so by relying on the $_SESSION
superglobal instead of $_COOKIE.

398

Chapter 7

login.php

building personalized web apps

// Generate the navigati
on menu
if (isset($_SESSION['userna
me'])) {
echo '&#10084; <a href="vi
ewprofile.php">View Prof
ile</a><br />';
echo '&#10084; <a href="ed
itprofile.php">Edit Prof
ile</a><br />';
echo '&#10084; <a href="lo
gout.php">Log Out (' . $_SE
SSION['username'] . ')</
}
a>';
else {
echo '&#10084; <a href="lo
gin.php">Log In</a><br />';
echo '&#10084; <a href="si
gnup.php">Sign Up</a>';
}
...
// Loop through the arra
y of user data, formatti
ng it as HTML
echo '<h4>Latest members:
</h4>';
echo '<table>';
while ($row = mysqli_fetch
_array($data)) {
...
if (isset($_SESSION['user_i
d'])) {
echo '<td><a href="viewpr
ofile.php?user_id=' . $row
['user_id'] . '">' .
$row['first_name'] . '</a
></td></tr>';
}

The Mismatch home page uses
the $_SESSION superglobal
instead of $_COOKIE to access
log-in data while generating
the menu and choosing whether
or not to provide a link to the
“latest members” profiles.

else {
echo '<td>' . $row['first_
name'] . '</td></tr>';
}

}
echo '</table>';

Similar to the
Log-In and home
pages, the Edit
Profile script now
uses $_SESSION
to access log-in
data instead of
$_COOKIE.

index.php

in before going any further.
// Make sure the user is logged
{
id']))
'user_
SSION[
t($_SE
if (!isse
page.</p>';
login.php">log in</a> to access this
echo '<p class="login">Please <a href="
exit();
}
else {
in as ' . $_SESSION['username'] .
echo('<p class="login">You are logged
>.</p>');
'. <a href="logout.php">Log out</a
}
...

Although not shown,
the View Profile
script uses sessions
in much the same
way as Edit Profile.

($birthdate) &&
_name) && !empty($gender) && !empty
if (!empty($first_name) && !empty($last
{
e))
!empty($city) && !empty($stat
there is a new picture
// Only set the picture column if
if (!empty($new_picture)) {
ame = '$last_name', " .
first_name = '$first_name', last_n
$query = "UPDATE mismatch_user SET
state = '$state', " .
',
'$city
=
city
,
hdate'
'$birt
=
"gender = '$gender', birthdate
d = '" . $_SESSION['user_id'] . "'";
"picture = '$new_picture' WHERE user_i

}
else {
ame = '$last_name', " .
first_name = '$first_name', last_n
$query = "UPDATE mismatch_user SET
state = '$state' " .
',
'$city
=
city
,
hdate'
'$birt
=
"gender = '$gender', birthdate
ser_id'] . "'";
"WHERE user_id = '" . $_SESSION['u
}
mysqli_query($dbc, $query);

editprofile.php
viewprofile.php
you are here 4   399

fireside chat between cookie and session variable

Tonight’s talk: Cookie and session variable get down and
dirty about who has the best memory

Cookie:

Session variable:

There’s been a lot of talk around here among us
cookies about what exactly goes on over there on
the server. Rumor is you’re trying to move in on our
territory and steal data storage jobs. What gives?
Come on now, steal is a strong word. The truth is
sometimes it just makes more sense to store data on
the server.
That doesn’t make any sense to me. The browser is
a perfectly good place to store data, and I’m just the
guy to do it.
What about when the user disables you?
Uh, well, that’s a completely different issue. And
if the user decides to disable me, then clearly they
don’t have any need to store data.
Not true. The user often doesn’t even know a web
application is storing data because in many cases,
it is behind-the-scenes data, like a username. So if
you’re not available, they’re left with nothing.
So I suppose your answer is to store the data on the
server? How convenient.

Alright, Einstein. Since you seem to have it all
figured out, why is it that you still sometimes use me
to store your precious little ID on the browser?

Exactly. And the cool thing is that the user doesn’t
have the ability to disable anything on the server, so
you don’t have to worry about whether or not the
data is really able to be stored.

Er, well, most people really don’t know about that, so
there’s no need to get into it here. We can talk about
that off the record. The important thing is that I’m
always around, ready to store data on the server.
400

Chapter 7

building personalized web apps

Cookie:

Session variable:

Come on, tell me how much you need me!
Alright, I will admit that from time to time I do lean
on you a little to help me keep up with things across
multiple pages. But I can get by without you if I
need to.
Oh I know you can, but the truth is you’d rather not.
And maybe deep down you really kinda like me.
Look, I don’t have any problem with you. I just wish
you were a little more secure. And you have that size
limitation. You know, not every piece of persistent
data is bite-sized.
Ah, so you’re going to resort to picking on the little
guy. Sure, I may not be able to store quite as much
as you, and I’ll admit that living on the client makes
me a little less secure. But it sure is more exciting!
And I have something you can only dream about.
Is that so? Do tell.
Well, all that storage space and security you’re
so proud of comes at a cost... a short lifespan! I
didn’t want to be the one to have to tell you, but
your entire existence is hinging on a single browser
session. I think that’s how you got your name.
You mean you can go on living beyond a single
session? How is that possible?!
It’s simple. I don’t die with a session, I just expire.
So I can be set to live a long, full life, far beyond the
whim of some click-happy web surfer who thinks
it’s cute to open and close the browser every chance
he gets.
Wow. What a feeling that must be to experience
immortality. My only hope is that some slacker
scripter accidentally forgets to destroy me when he
closes a session... but the browser will still do me in
whenever it gets shut down.
Problem is, those same scripters often set my
expiration to such a short period that I don’t really
get to experience the long life I truly deserve. I
mean, I...
Hello? Are you there? Geez, expiration is harsh.
you are here 4   401

test drive the "sessionized" mismatch

Test Drive
Change Mismatch to use sessions instead of cookies.
Modify the Mismatch scripts so that they use sessions instead of cookies to support
log-in persistence (or download the scripts from the Head First Labs site at www.
headfirstlabs.com/books/hfphp). The session modifications involve changes to the
index.php, login.php, logout.php, editprofile.php, and viewprofile.php
scripts, and primarily involve starting the session with a call to the session_start()
function and changing $_COOKIE superglobal references to use $_SESSION instead.
Upload the scripts to your web server, and then open the main Mismatch page (index.php) in
a web browser. Try logging in and out to make sure everything works the same as before. Unless
you had cookies disabled earlier, you shouldn’t notice any difference—that’s a good thing!

Very cool. It’s nice
being able to log in
even without cookies
turned on.

402

Chapter 7

Thanks to sessions, users
with cookies disabled can
still log in and access
their personal profiles.

building personalized web apps

Sessions without cookies may not work if your
PHP settings in php.ini aren’t configured
properly on the server.
In order for sessions to work with cookies disabled, there
needs to be another mechanism for passing the session
ID among different pages. This mechanism involves appending the
session ID to the URL of each page, which takes place automatically if
the session.use_trans_id setting is set to 1 (true) in the php.ini
file on the server. If you don’t have the ability to alter this file on your
web server, you’ll have to manually append the session ID to the URL of
session pages if cookies are disabled with code like this:
<a href="viewprofile.php?<?php echo SID; ?>">view your profile</a>

The SID superglobal holds the session
ID, which is being passed along
through the URL so that the View
Profile page knows about the session.

you are here 4   403

why the automatic logout?

Users aren’t feeling welcome
Despite serving as a nice little improvement over cookies, something
about the new session-powered Mismatch application isn’t quite right.
Several users have reported getting logged out of the application despite
never clicking the “Log Out” link. The application doesn’t exactly feel
personal anymore... this is a big problem.

Frustrated
users are never
a good thing.

Hey, we were logged in last time
we checked, and suddenly we’re all
logged out! What gives?

Users are being logged out
of Mismatch without ever
clicking the “Log Out” link.

404

Chapter 7

This isn’t the
message we
want Mismatch
to send its users.

The home page is
presented to the
registered users as
if they are visitors
even though they
never logged out.

building personalized web apps

What do you think is causing users to be
automatically logged out of Mismatch? Is
it something they’ve done inadvertently?

you are here 4   405

the lifespan of cookies and sessions

Sessions are short-lived...
The problem with the automatic log-outs in Mismatch has to do with
the limited lifespan of sessions. If you recall, sessions only last as long as
the current browser instance, meaning that all session variables are killed
when the user closes the browser application. In other words, closing the
browser results in a user being logged out whether they like it or not. This
is not only inconvenient, but it’s also a bit confusing because we already
have a log-out feature. Users assume they aren’t logged out unless they’ve
clicked the Log Out link.

Whether sessions or cookies
used, logging in is what sets are
the persistent wheels in motion.

Logging in with sessions
results in the creation
of two session variables.
user_id = 1

Once the session
variables are
destroyed, the user is
logged out...whether
they like it or not!

username = sidneyk

The session
variables are used
to remember the
identity of the user.

The user closes the
browser but may not
realize that they just
logged themselves out.

The session variables
are destroyed along
with the session when
the browser is closed.

Poof!

Even though you can destroy a session when you’re finished with it, you
can’t prolong it beyond a browser instance. So sessions are more of a shortterm storage solution than cookies, since cookies have an expiration date
that can be set hours, days, months, or even years into the future. Does
that mean sessions are inferior to cookies? No, not at all. But it does mean
that sessions present a problem if you’re trying to remember information
beyond a single browser instance... such as log-in data!
406

Chapter 7

Session variables are
destroyed when the
user ends a session by
closing the browser.

building personalized web apps

... but cookies can last forever!
Unlike sessio