PHP 101 (PART 5): Rank and File [July 21, 2004]

advertisement
PHP Tutorial
Laboratory Guide in IT 208
Web Development
Compiled by:
A. JONATHAN S. PAGATPAN
1
PHP 101: PHP For the Absolute Beginner
Vikram Vaswani
http://devzone.zend.com/article/627
This area is intended for everyone new to PHP. It opens with a series of informal, entertaining
tutorials written by Vikram Vaswani, founder and CEO of Melonfire. These tutorials build on a
previously-published 5-part series which has now been updated and extended to embrace PHP 5,
making parts of it suitable for those of you who already have worked with PHP 4 in the past.
If you came here to learn about elementary PHP 4 or basic PHP 5, this is for you. Enjoy!
PHP 101 (part 1): Down the Rabbit Hole [July 17, 2004]
An introduction to PHP’s variables and operators.
PHP 101 (part 2): Calling All Operators [July 18, 2004]
The rest of the PHP operators (there are many), and simple form processing.
PHP 101 (PART 3): Looping the Loop [July 19, 2004]
Basic control structures explained.
PHP 101 (PART 4): The Food Factor [July 20, 2004]
Arrays, PHP array functions, and what it all means.
PHP 101 (PART 5): Rank and File [July 21, 2004]
Everything you’re ever likely to need to know about dealing with external files from a PHP
script.
PHP 101 (PART 6): Functionally Yours [July 28, 2004]
All about functions, arguments, passing by reference, globals and scope.
PHP 101 (PART 7): The Bear Necessities [August 07, 2004]
A gentle introduction to object oriented programming in PHP 4 and PHP 5.
PHP 101 (PART 8): Databases and Other Animals [August 31, 2004]
All about connecting to a MySQL database from PHP, using the mysql or mysqli extensions.
PHP 101 (PART 9): SQLite My Fire! [September 16, 2004]
Introducing another database: SQLite.
PHP 101 (part 10): A Session In The Cookie Jar [October 3, 2004]
Sessions and cookies – how to keep track of visitors to your site.
PHP 101 (part 11): Sinfully Simple [October 3, 2004]
An introduction to PHP’s easiest method for dealing with XML.
2
PHP 101 (part 12): Bugging Out [January 30, 2005]
Basic error handling.
PHP 101 (part 13): The Trashman Cometh [February 27, 2005]
A primer in basic security.
PHP 101 (part 14): Going to the Polls [March 8, 2005]
Putting the pieces together – a first Web application.
PHP 101 (part 15): No News is Good News [June 4, 2005]
Creating a simple RSS news aggregator.
3
PHP 101 (part 1): Down the Rabbit Hole
Vikram Vaswani | 61 comments | Saturday, July 17, 2004
The Only Acronym You'll Ever Need
The Right Environment
Start Me Up
A Case of Identity
An Equal Music
Not My Type
Market Value
Stringing Things Along
The Only Acronym You'll Ever Need
If you're new to Web development, you could be forgiven for thinking that it consists of no more
than a mass of acronyms, each one more indecipherable than the last. ASP, CGI, SOAP, XML,
HTTP - the list seems never-ending, and the sheer volume of information on each of these can
discourage the most avid programmer. But before you put on your running shoes and flee, there's
a little secret you should know. To put together a cutting-edge Web site, chock full of all the
latest bells and whistles, there's only one acronym you really need to know:
PHP
Now, while you have almost certainly heard of PHP, you may not be aware of just how powerful
the language is, and how much it can do for you. Today, PHP has the enviable position of being
the only open-source server-side scripting language that's both fun and easy to learn. This is not
just advertising: recent surveys show that more than 16,000,000 Web sites use PHP as a server
side scripting language, and the language also tops the list of most popular Apache modules.
Why, you ask? The short answer: it's powerful, it's easy to use, and it's free. Extremely robust
and scalable, PHP can be used for the most demanding of applications, and delivers excellent
performance even at high loads. Built-in database support means that you can begin creating
data-driven applications immediately, XML support makes it suitable for the new generation of
XML-enabled applications, and the extensible architecture makes it easy for developers to use it
as a framework to build their own custom modules. Toss in a great manual, a knowledgeable
developer community and a really low price (can you spell f-r-e-e?) and you've got the makings
of a winner!
My goal in this series of tutorials is very simple: I'll be teaching you the basics of using PHP, and
showing you why I think it's the best possible tool for Web application development today. I'll be
making no assumptions about your level of knowledge, other than that you can understand basic
HTML and have a sense of humor. And before you ask... Yes, this series covers both PHP 4 and
PHP 5, with new PHP 5 features flagged for easy reference.
4
Let's get going!
The Right Environment
PHP is typically used in combination with a Web server like Apache. Requests for PHP scripts
are received by the Web server, and are handled by the PHP interpreter. The results obtained
after execution are returned to the Web server, which takes care of transmitting them to the client
browser. Within the PHP script itself, the sky's the limit - your script can perform calculations,
process user input, interact with a database, read and write files... Basically, anything you can do
with a regular programming language, you can do inside your PHP scripts.
From the above, it is clear that in order to begin using PHP, you need to have a proper
development environment set up.
This series will focus on using PHP with the Apache Web server on Linux, but you can just as
easily use PHP with Apache on Windows, UNIX and Mac OS. Detailed instructions on how to
set up this development environment on each platform are available in the online manual, at
http://www.php.net/manual/en/installation.php - or you can just download a copy of PHP 5 from
http://www.php.net and read the installation instructions.
Go do that now, and come back when you've successfully installed and tested PHP.
Start Me Up
There's one essential concept that you need to get your mind around before we proceed further.
Unlike CGI scripts, which require you to write code to output HTML, PHP lets you embed PHP
code in regular HTML pages, and execute the embedded PHP code when the page is requested.
These embedded PHP commands are enclosed within special start and end tags, like this:
<?php
... PHP code ...
?>
Here's a simple example that demonstrates how PHP and HTML can be combined:
<html>
<head></head>
<body>
Agent: So who do you think you are, anyhow?
<br />
<?php
// print output
echo 'Neo: I am Neo, but my people call me The One.';
5
?>
</body>
</html>
Not quite your traditional "Hello, World" program... but then again, I always thought tradition
was over-rated.
Save the above script to a location under your Web server document root, with a .php extension,
and browse to it. You'll see something like this:
Look at the HTML source:
<html>
<head></head>
<body>
Agent: So who do you think you are, anyhow?
<br />
Neo: I am Neo, but my people call me The One.
</body>
</html>
What just happened? When you requested the script above, Apache intercepted your request and
handed it off to PHP. PHP then parsed the script, executing the code between the <?php...?>
marks and replacing it with the output of the code run. The result was then handed back to the
server and transmitted to the client. Since the output contained valid HTML, the browser was
able to render it for display to the user.
6
A close look at the script will reveal the basic syntactical rules of PHP. Every PHP statement
ends in a semi-colon. This convention is identical to that used in Perl, and omitting the semicolon is one of the most common mistakes newbies make. That said, it is interesting to note that
a semi-colon is not needed to terminate the last line of a PHP block. The PHP closing tag
includes a semi-colon, therefore the following is perfectly valid PHP code:
<?php
// print output
echo 'Neo: I am Neo, but my people call me The One.'
?>
It's also possible to add comments to your PHP code, as I've done in the example above. PHP
supports both single-line and multi-line comment blocks:
<?php
// this is a single-line comment
/* and this is a
multi-line
comment */
?>
Blank lines within the PHP tags are ignored by the parser. Everything outside the tags is also
ignored by the parser, and returned as-is. Only the code between the tags is read and executed.
A Case of Identity
Variables are the bread and butter of every programming language... and PHP has them too. A
variable can be thought of as a programming construct used to store both numeric and nonnumeric data; the contents of a variable can be altered during program execution. Finally,
variables can be compared with each other, and you - the programmer - can write code that
performs specific actions on the basis of this comparison.
PHP supports a number of different variable types: integers, floating point numbers, strings and
arrays. In many languages, it's essential to specify the variable type before using it: for example,
a variable may need to be specified as type integer or type array. Give PHP credit for a little
intelligence, though: it automagically determines variable type by the context in which it is being
used!
Every variable has a name. In PHP, a variable name is preceded by a dollar ($) symbol and must
begin with a letter or underscore, optionally followed by more letters, numbers and/or
underscores. For example, $popeye, $one and $INCOME are all valid PHP variable names, while
$123 and $48hrs are invalid.
7
Note that variable names in PHP are case sensitive, so $me is different from $Me or $ME.
Here's a simple example that demonstrates PHP's variables:
<html>
<head></head>
<body>
Agent: So who do you think you are, anyhow?
<br />
<?php
// define variables
$name = 'Neo';
$rank = 'Anomaly';
$serialNumber = 1;
// print output
echo "Neo: I am <b>$name</b>, the <b>$rank</b>. You can call me by my serial
number, <b>$serialNumber</b>.";
?>
</body>
</html>
Here, the variables $name, $rank and $serialNumber are first defined with string and numeric
values, and then substituted in the echo() function call. The echo() function, along with the
print() function, is commonly used to print data to the standard output device (here, the
browser). Notice that I've included HTML tags within the call to echo(), and those have been
rendered by the browser in its output. You can do this too. Really.
An Equal Music
To assign a value to a variable, you use the assignment operator: the = symbol. This is used to
assign a value (the right side of the equation) to a variable (the left side). The value being
assigned need not always be fixed; it could also be another variable, an expression, or even an
expression involving other variables, as below:
<?php
$age = $dob + 15;
?>
Interestingly, you can also perform more than one assignment at a time. Consider the following
example, which assigns three variables the same value simultaneously:
<?php
$angle1 = $angle2 = $angle3 = 60;
8
?>
Not My Type
Every language has different types of variable - and PHP is no exception. The language supports
a wide variety of data types, including simple numeric, character, string and Boolean types, and
more complex arrays and objects. Here's a quick list of the basic ones, with examples:

Boolean: The simplest variable type in PHP, a Boolean variable, simply specifies a true
or false value.
<?php
$auth = true;
?>

Integer: An integer is a plain-vanilla whole number like 75, -95, 2000 or 1.
<?php
$age = 99;
?>

Floating-point: A floating-point number is typically a fractional number such as 12.5 or
3.141592653589. Floating point numbers may be specified using either decimal or
scientific notation.
<?php
$temperature = 56.89;
?>

String: A string is a sequence of characters, like "hello" or "abracadabra". String values
may be enclosed in either double quotes ("") or single quotes(''). (Quotation marks within
the string itself can be "escaped" with a backslash (\) character.) String values enclosed in
double quotes are automatically parsed for special characters and variable names; if these
are found, they are replaced with the appropriate value. Here's an example:
<?php
$identity = 'James Bond';
$car = 'BMW';
// this would contain the string "James Bond drives a BMW"
$sentence = "$identity drives a $car";
echo $sentence;
9
?>
To learn more about PHP's data types, visit http://www.php.net/manual/en/language.types.php.
Market Value
If variables are the building blocks of a programming language, operators are the glue that let
you build something useful with them. You've already seen one example of an operator - the
assignment operator -, which lets you assign a value to a variable. Since PHP believes in spoiling
you, it also comes with operators for arithmetic, string, comparison and logical operations.
A good way to get familiar with operators is to use them to perform arithmetic operations on
variables, as in the following example:
<html>
<head>
</head>
<body>
<?php
// set quantity
$quantity = 1000;
// set original and current unit price
$origPrice = 100;
$currPrice = 25;
// calculate difference in price
$diffPrice = $currPrice - $origPrice;
// calculate percentage change in price
$diffPricePercent = (($currPrice - $origPrice) * 100)/$origPrice
?>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td>Quantity</td>
<td>Cost price</td>
<td>Current price</td>
<td>Absolute change in price</td>
<td>Percent change in price</td>
</tr>
<tr>
<td><?php echo $quantity ?></td>
<td><?php echo $origPrice ?></td>
<td><?php echo $currPrice ?></td>
<td><?php echo $diffPrice ?></td>
<td><?php echo $diffPricePercent ?>%</td>
</tr>
</table>
10
</body>
</html>
Looks complex? Don't be afraid - it's actually pretty simple. The meat of the script is at the top,
where I've set up variables for the unit cost and the quantity. Next, I've performed a bunch of
calculations using PHP's various mathematical operators, and stored the results of those
calculations in different variables. The rest of the script is related to the display of the resulting
calculations in a neat table.
If you'd like, you can even perform an arithmetic operation simultaneously with an assignment,
by using the two operators together. The two code snippets below are equivalent:
<?php
// this...
$a = 5;
$a = $a + 10;
// ... is the same as this
$a = 5;
$a += 10;
?>
If you don't believe me, try echoing them both.
Stringing Things Along
Why stop with numbers? PHP also allows you to add strings with the string concatenation
operator, represented by a period (.). Take a look:
<?php
//
$a
$b
$c
$d
set up some string variables
= 'the';
= 'games';
= 'begin';
= 'now';
// combine them using the concatenation operator
// this returns 'the games begin now<br />'
$statement = $a.' '.$b.' '.$c.' '.$d.'<br />';
print $statement;
// and this returns 'begin the games now!'
$command = $c.' '.$a.' '.$b.' '.$d.'!';
print $command;
?>
As before, you can concatenate and assign simultaneously, as below:
11
<?php
// define string
$str = 'the';
// add and assign
$str .= 'n';
// str now contains "then"
echo $str;
?>
To learn more about PHP's arithmetic and string operators, visit
http://www.php.net/manual/en/language.operators.arithmetic.php and
http://www.php.net/manual/en/language.operators.string.php.
That's about it for this tutorial. You now know all about the basic building blocks and glue of
PHP - its variables and operators. In Part Two of this series, I'll be using these fundamental
concepts to demonstrate PHP's powerful form processing capabilities.
12
PHP 101 (part 2): Calling All Operators
Vikram Vaswani | 47 comments | Saturday, July 17, 2004
Not What You Expected
Form...
...And Function
Operating With Extreme Caution
A Question of Logic
Older But Not Wiser
If Not This, Then What?
Spreading Confusion
The Daily Special
Not What You Expected
In Part One of this series, I gave you a brief introduction to PHP, and how it fits into your Web
application development environment. I also taught you the basics of PHP variables, and showed
you how to add, multiply and concatenate them together.
Now that you know the basics, it's time to focus in on one of PHP's nicer features - its ability to
automatically receive user input from a Web form and convert it into PHP variables. If you're
used to writing Perl code to retrieve form values in your CGI scripts, PHP's simpler approach is
going to make you weep with joy. So get that handkerchief out, and scroll on down.
Form...
Forms have always been one of quickest and easiest ways to add interactivity to your Web site.
A form allows you to ask customers if they like your products, casual visitors for comments on
your site, and pretty girls for their phone numbers. And PHP can simplify the task of processing
the data generated from a Web-based form substantially, as this first example demonstrates. This
example contains two scripts, one containing an HTML form (named form.htm) and the other
containing the form processing logic (message.php). Here's form.htm:
<html>
<head></head>
<body>
<form action="message.php" method="post">
Enter your message: <input type="text" name="msg" size="30">
<input type="submit" value="Send">
</form>
</body>
</html>
The critical line in this page is the <form> tag
13
<form action="message.php" method="post">
...
</form>
As you probably already know, the "action" attribute of the <form> tag specifies the name of the
server-side script (message.php in this case) that will process the information entered into the
form. The "method" attribute specifies how the information will be passed.
...And Function
Now for the other half of the puzzle: the message.php script. This script reads the data
submitted by the user and "does something with it". Here is message.php:
<html>
<head></head>
<body>
<?php
// retrieve form data
$input = $_POST['msg'];
// use it
echo "You said: <i>$input</i>";
?>
</body>
</html>
When you enter some data into form.htm (let's say "Boo"), and submit it, the form processor
message.php will read it and display it to you ("You said: Boo"). Thus, whenever a form is
submitted to a PHP script, all variable-value pairs within that form automatically become
available for use within the script, through a special PHP container variable: $_POST. You can
then access the value of the form variable by using its "name" inside the $_POST container, as I
did in the script above.
Obviously, PHP also supports the GET method of form submission. All you need to do is change
the "method" attribute to "get", and retrieve values from $_GET instead of $_POST. The $_GET
and $_POST variables are actually a special type of PHP animal called an array, which I'll be
teaching you about shortly. Don't worry too much about it at the moment, just make sure you're
comfortable with retrieving simple values from a form with PHP, and then scroll on down to
learn about some more operators that are useful in this context.
Operating With Extreme Caution
Thus far, the scripts we've discussed have been pretty dumb. All they've done is add numbers
and strings, and read back to you the data you typed in yourself - not exactly overwhelming. In
order to add some intelligence to your scripts, you need to know how to construct what geeks
14
call a "conditional statement" - a statement which lets your script perform one of a series of
possible actions based on the result of a comparison test. And since the basis of a conditional
statement is comparison, you first need to know how to compare two variables and determine
whether they're identical or different.
You've already seen some of PHP's arithmetic and string operators. However, the language also
comes with operators designed specifically to compare two values: the so-called "comparison
operators". Here's an example that demonstrates them in action:
<?php
/* define some variables */
$mean = 9;
$median = 10;
$mode = 9;
// less-than operator
// returns true if left side is less than right
// returns true here
$result = ($mean < $median);
print "result is $result<br />";
// greater-than operator
// returns true if left side is greater than right
// returns false here
$result = ($mean > $median);
print "result is $result<br />";
// less-than-or-equal-to operator
// returns true if left side is less than or equal to right
// returns false here
$result = ($median <= $mode);
print "result is $result<br />";
// greater-than-or-equal-to operator
// returns true if left side is greater than or equal to right
// returns true here
$result = ($median >= $mode);
print "result is $result<br />";
// equality operator
// returns true if left side is equal to right
// returns true here
$result = ($mean == $mode);
print "result is $result<br />";
// not-equal-to operator
// returns true if left side is not equal to right
// returns false here
$result = ($mean != $mode);
print "result is $result<br />";
// inequality operator
// returns true if left side is not equal to right
15
// returns false here
$result = ($mean <> $mode);
print "result is $result";
?>
The result of a comparison test is always Boolean: either true (1) or false (0 - which doesn't print
anything). This makes comparison operators an indispensable part of your toolkit, as you can use
them in combination with a conditional statement to send a script down any of its multiple action
paths.
PHP 4.0 also introduced a new comparison operator, which allows you to test both for equality
and type: the === operator. The following example demonstrates it:
<?php
/* define two variables */
$str = '10';
$int = 10;
/* returns true, since both variables contain the same value */
$result = ($str == $int);
print "result is $result<br />";
/* returns false, since the variables are not of the same type even though
they have the same value */
$result = ($str === $int);
print "result is $result<br />";
/* returns true, since the variables are the same type and value */
$anotherInt = 10;
$result = ($anotherInt === $int);
print "result is $result";
?>
Read more about PHP's comparison operators at
http://www.php.net/manual/en/language.operators.comparison.php.
A Question of Logic
In addition to the comparison operators I used so liberally above, PHP also provides four logical
operators, which are designed to group conditional expressions together. These four operators logical AND, logical OR, logical XOR and logical NOT - are illustrated in the following example:
<?php
/* define some variables */
$auth = 1;
$status = 1;
16
$role = 4;
/* logical AND returns true if all conditions are true */
// returns true
$result = (($auth == 1) && ($status != 0));
print "result is $result<br />";
/* logical OR returns true if any condition is true */
// returns true
$result = (($status == 1) || ($role <= 2));
print "result is $result<br />";
/* logical NOT returns true if the condition is false and vice-versa */
// returns false
$result = !($status == 1);
print "result is $result<br />";
/* logical XOR returns true if either of two conditions are true, or returns
false if both conditions are true */
// returns false
$result = (($status == 1) xor ($auth == 1));
print "result is $result<br />";
?>
Logical operators play an important role in building conditional statements, as they can be used
to link together related conditions simply and elegantly. View more examples of how they can be
used at http://www.php.net/manual/en/language.operators.logical.php.
Older But Not Wiser
Now that you've learnt all about comparison and logical operators, I can teach you about
conditional statements. As noted earlier, a conditional statement allows you to test whether a
specific condition is true or false, and perform different actions on the basis of the result. In PHP,
the simplest form of conditional statement is the if() statement, which looks something like
this:
if (condition) {
do this!
}
The argument to if()is a conditional expression, which evaluates to either true or false. If the
statement evaluates to true, all PHP code within the curly braces is executed; if it does not, the
code within the curly braces is skipped and the lines following the if() construct are executed.
Let me show you how the if() statement works by combining it with a form. In this example,
the user is asked to enter his or her age.
<html>
17
<head></head>
<body>
<form action="ageist.php" method="post">
Enter your age: <input name="age" size="2">
</form>
</body>
</html>
Depending on whether the entered age is above or below 21, a different message is displayed by
the ageist.php script:
<html>
<head></head>
<body>
<?php
// retrieve form data
$age = $_POST['age'];
// check entered value and branch
if ($age >= 21) {
echo 'Come on in, we have alcohol and music awaiting you!';
}
if ($age < 21) {
echo "You're too young for this club, come back when you're a little
older";
}
?>
</body>
</html>
If Not This, Then What?
In addition to the if() statement, PHP also offers the if-else construct, used to define a block
of code that gets executed when the conditional expression in the if() statement evaluates as
false.
The if-else construct looks like this:
if (condition) {
do this!
}
else {
do this!
}
This construct can be used to great effect in the last example: we can combine the two separate
if()statements into a single if-else statement.
18
<html>
<head></head>
<body>
<?php
// retrieve form data
$age = $_POST['age'];
// check entered value and branch
if ($age >= 21) {
echo 'Come on in, we have alcohol and music awaiting you!';
}
else {
echo "You're too young for this club, come back when you're a little
older";
}
?>
</body>
</html>
Spreading Confusion
If the thought of confusing people who read your code makes you feel warm and tingly, you're
going to love the ternary operator, represented by a question mark (?). This operator, which lets
you make your conditional statements almost unintelligible, provides shortcut syntax for creating
a single-statement if-else block. So, while you could do this:
<?php
if ($numTries > 10) {
$msg = 'Blocking your account...';
}
else {
$msg = 'Welcome!';
}
?>
You could also do this, which is equivalent (and a lot more fun):
<?php
$msg = $numTries > 10 ? 'Blocking your account...' : 'Welcome!';
?>
PHP also lets you "nest" conditional statements inside each other. For example, this is perfectly
valid PHP code:
19
<?php
if ($day == 'Thursday') {
if ($time == '0800') {
if ($country == 'UK') {
$meal = 'bacon and eggs';
}
}
}
?>
Another, more elegant way to write the above is with a series of logical operators:
<?php
if ($day == 'Thursday' && $time == '0800' && $country == 'UK') {
$meal = 'bacon and eggs';
}
?>
The Daily Special
PHP also provides you with a way of handling multiple possibilities: the if-elseif-else
construct. A typical if-elseif-else statement block would look like this:
if (first condition is true) {
do this!
}
elseif (second condition is true) {
do this!
}
elseif (third condition is true) {
do this!
}
... and so on ...
else {
do this!
}
And here's an example that demonstrates how to use it:
<html>
<head></head>
<body>
<h2>Today's Special</h2>
<p>
<form method="get" action="cooking.php">
20
<select name="day">
<option value="1">Monday/Wednesday
<option value="2">Tuesday/Thursday
<option value="3">Friday/Sunday
<option value="4">Saturday
</select>
<input type="submit" value="Send">
</form>
</body>
</html>
As you can see, this is simply a form which allows you to pick a day of the week. The real work
is done by the PHP script cooking.php:
<html>
<head></head>
<body>
<?php
// get form selection
$day = $_GET['day'];
// check value and select appropriate item
if ($day == 1) {
$special = 'Chicken in oyster sauce';
}
elseif ($day == 2) {
$special = 'French onion soup';
}
elseif ($day == 3) {
$special = 'Pork chops with mashed potatoes and green salad';
}
else {
$special = 'Fish and chips';
}
?>
<h2>Today's special is:</h2>
<?php echo $special; ?>
</body>
</html>
In this case, I've used the if-elseif-else control structure to assign a different menu special to
each combination of days. Note that as soon as one of the if() branches within the block is
found to be true, PHP will execute the corresponding code, skip the remaining if() statements
in the block, and jump immediately to the lines following the entire if-elseif-else block.
And that's it for now. To view more examples of conditional statements in action, visit
http://www.php.net/manual/en/language.control-structures.php. In Part Three, I'll be bringing
you more control structures, more operators and more strange and wacky scripts - so make sure
you don't miss it!
21
PHP 101 (part 3): Looping The Loop
Vikram Vaswani | 33 comments | Monday, July 19, 2004
Going Deeper
Switching Things Around
Creative Conditionals
One by One
Being Square
Loop First, Ask Questions Later
Doing it by Numbers
Turning the Tables
Going Deeper
If you've been paying attention, you remember that in Part Two I gave you a quick crash course
in PHP's basic control structures and operators. I also showed you how PHP can be used to
process the data entered into a Web form. In this tutorial, I'm going to delve deeper into PHP's
operators and control structures, showing you two new operators, an alternative to the ifelse() family of conditional statements, and some of PHP's more interesting loops. So keep
reading... this is just about to get interesting!
Switching Things Around
An alternative to the if-else() family of control structures is PHP's switch-case() statement,
which does almost the same thing. It looks like this:
switch (decision-variable) {
case first condition is true:
do this!
case second condition is true:
do this!
... and so on...
}
Depending on the value of the decision variable, the appropriate case() block is executed. A
default block can also be created, to handle all those occasions when the value of the decision
variable does not match any of the listed case() conditions.
I'll make this a little clearer by re-writing one of my earlier examples in terms of the switch()
statement:
<html>
<head></head>
22
<body>
<?php
// get form selection
$day = $_GET['day'];
// check value and select appropriate item
switch ($day) {
case 1:
$special = 'Chicken in oyster sauce';
break;
case 2:
$special = 'French onion soup';
break;
case 3:
$special = 'Pork chops with mashed potatoes and green salad';
break;
default:
$special = 'Fish and chips';
break;
}
?>
<h2>Today's special is:</h2>
<?php echo $special ?>
</body>
</html>
There are a couple of important keywords here:


The break keyword is used to break out of the switch() statement block and move
immediately to the lines following it.
The default keyword is used to execute a default set of statements when the variable
passed to switch() does not satisfy any of the conditions listed within the block.
A common newbie mistake here is to forget the break at the end of every case() block.
Remember that if you forget to break out of a case() block, PHP will continue executing the
code in all the subsequent case() blocks it encounters.
For more on the switch() statement, see http://www.php.net/manual/en/controlstructures.switch.php.
Creative Conditionals
Normally, when creating and processing forms in PHP, you would place the HTML form in one
file, and handle form processing through a separate PHP script. However, with the power of
conditional statements at your disposal, you can combine both pages into one.
How do you do this? Simple. All you need to do is assign a name to the form submit control,
and then check whether the special $_POST container variable contains that name when the script
23
first loads up. If it does, the form has already been submitted, and you can process the data; if it
does not, that the user has not submitted the form and you therefore need to generate the initial,
unfilled form. Thus, by testing for the presence or absence of this submit variable, a clever PHP
programmer can use a single PHP script to generate both the initial form, and the output after it
has been submitted, as appropriate.
Here's a simple example:
<html>
<head></head>
<body>
<?php
/* if the "submit" variable does not exist, the form has not been submitted display initial page */
if (!isset($_POST['submit'])) {
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
Enter your age: <input name="age" size="2">
<input type="submit" name="submit" value="Go">
</form>
<?php
}
else {
/* if the "submit" variable exists, the form has been submitted - look for
and process form data */
// display result
$age = $_POST['age'];
if ($age >= 21) {
echo 'Come on in, we have alcohol and music awaiting you!';
}
else {
echo 'You're too young for this club, come back when you're a little
older';
}
}
?>
</body>
</html>
As you can see, the script contains two pages: the initial, empty form and the result page
generated after hitting the submit button. In order to decide which page to display, the script first
tests for the presence of the $_POST['submit'] variable. If it doesn't find it, it assumes that the
form has yet to be submitted, and displays the initial list of days. Once the form has been
submitted, the same script will be called to process the form input. This time, however, the
$_POST['submit'] variable will be set, and so PHP will not display the initial page, but rather
the page containing the result message.
24
Note that for this to work, your submit button must have a value assigned to its "name" attribute,
and you must check for that value in the primary conditional statement. And in case you were
wondering, the $_SERVER array is a special PHP variable which always holds server information,
including the path and name of the currently executing script.
Next up, loops.
One by One
For those of you unfamiliar with the term, a loop is a control structure that enables you to repeat
the same set of php
statements or commands over and over again (the actual number of repetitions can be a number
you specify, or depend on the fulfillment of one or more conditions).
Now, last time out you saw a few comparison and logical operators, which help in building
conditional statements. Since this segment of the tutorial is going to focus on loops, this is an
appropriate time to introduce you to PHP's auto-increment and auto-decrement operators, which
see a lot of use in this context.
The auto-increment operator is a PHP operator designed to automatically increment the value of
the variable it is attached to by 1. It is represented by two "plus" signs (++). This snippet of code
should explain it:
<?php
// define $total as 10
$total = 10;
// increment it
$total++;
// $total is now 11
echo $total;
?>
Thus, $total++ is functionally equivalent to $total = $total + 1.
There's a corresponding auto-decrement operator (--), which does exactly the opposite:
<?php
// define $total as 10
$total = 10;
// decrement it
$total--;
// $total is now 9
echo $total;
?>
25
These operators are frequently used in loops, to update the value of the loop counter, speaking of
which...
Being Square
The first - and simplest - loop to learn in PHP is the so-called while() loop, which looks like
this:
while (condition is true) {
do this!
}
In this case, so long as the condition specified evaluates as true - remember what you learned in
Part Two? - the PHP statements within the curly braces will continue to execute. As soon as the
condition becomes false, the loop will be broken and the statements following it will be
executed.
Here's a quick example which demonstrates the while() loop:
<html>
<head></head>
<body>
<form action="squares.php" method="POST">
Print all the squares between 1 and <input type="text" name="limit" size="4"
maxlength="4">
<input type="submit" name="submit" value="Go">
</form>
</body>
</html>
This is a simple form which asks the user to enter a number. When the form is submitted, the
PHP script that is invoked should take this number and print the squares of all the numbers
between 1 and the entered value. With a while() loop, this is simplicity itself:
<html>
<head></head>
<body>
<?php
// set variables from form input
$upperLimit = $_POST['limit'];
$lowerLimit = 1;
// keep printing squares until lower limit = upper limit
while ($lowerLimit <= $upperLimit) {
echo ($lowerLimit * $lowerLimit).' ';
$lowerLimit++;
}
// print end marker
echo 'END';
26
?>
</body>
</html>
This script uses a while() loop to count forwards from 1 until the values of $lowerLimit and
$upperLimit are equal.
Loop First, Ask Questions Later
The while() loop executes a set of statements while a specified condition is true. But what
happens if the condition is true on the first iteration of the loop itself? In the previous example, if
you were to enter the value 0in the form, the while() loop would not execute even once. Try it
yourself and you'll see what I mean.
If you're in a situation where you need to execute a set of statements *at least* once, PHP offers
you the do-while() loop. Here's what it looks like:
do {
do this!
} while (condition is true)
Let's take a quick example to better understand the difference between while() and dowhile():
<?php
$x = 100;
// while loop
while ($x == 700) {
echo "Running...";
break;
}
?>
In this case, no matter how many times you run this PHP script, you will get no output at all,
since the value of $x is not equal to 700. But, if you ran this version of the script:
<?php
$x = 100;
// do-while loop
do {
echo "Running...";
break;
} while ($x == 700);
?>
27
you would see one line of output, as the code within the do() block would run once.
Let's now revise the previous PHP script so that it runs at least once, regardless of what value is
entered into the form:
<html>
<head></head>
<body>
<?php
// set variables from form input
$upperLimit = $_POST['limit'];
$lowerLimit = 1;
// keep printing squares until lower limit = upper limit
do {
echo ($lowerLimit * $lowerLimit).' ';
$lowerLimit++;
} while ($lowerLimit <= $upperLimit);
// print end marker
echo ' END';
?>
</body>
</html>
Thus, the construction of the do-while() loop is such that the statements within the loop are
executed first, and the condition to be tested is checked afterwards. This implies that the
statements within the curly braces would be executed at least once.
Read more about the while() and do-while() loops at http://www.php.net/manual/en/controlstructures.while.php and http://www.php.net/manual/en/control-structures.do.while.php.
Doing it by Numbers
Both the while() and do-while() loops continue to iterate for as long as the specified
conditional expression remains true. But what if you need to execute a certain set of statements a
specific number of times - for example, printing a series of thirteen sequential numbers, or
repeating a particular set of <td> cells five times? In such cases, clever programmers reach for
the for() loop...
The for() loop typically looks like this:
for (initial value of counter; condition; new value of counter) {
do this!
}
28
Looks like gibberish? Well, hang in there for a minute...the "counter" here is a PHP variable that
is initialized to a numeric value, and keeps track of the number of times the loop is executed.
Before each execution of the loop, the "condition" is tested. If it evaluates to true, the loop will
execute once more and the counter will be appropriately incremented; if it evaluates to false, the
loop will be broken and the lines following it will be executed instead.
Here's a simple example that demonstrates how this loop can be used:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
// define the number
$number = 13;
// use a for loop to calculate tables for that number
for ($x = 1; $x <= 10; $x++) {
echo "$number x $x = ".($number * $x)."<br />";
}
?>
</body>
</html>
The first thing I've done here is define the number to be used for the multiplication table. I've
used 13 here - for no reason other than that it rhymes with "green".
Next, I've constructed a for() loop with $x as the counter variable, initialized it to 1. and
specified that the loop should run no more than 10 times. The auto-increment operator (discussed
earlier) automatically increments the counter by 1 every time the loop is executed. Within the
loop, the counter is multiplied by the number, to create the multiplication table, and echo() is
used to display the result on the page.
Turning the Tables
As you just saw, a for() loop is a very interesting - and useful - programming construct. The
next example illustrates its usefulness in a manner that should endear it to any HTML
programmer.
<html>
<head></head>
<body>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
Enter number of rows <input name="rows" type="text" size="4"> and columns
<input name="columns" type="text" size="4"> <input type="submit"
name="submit" value="Draw Table">
</form>
29
<?php
if (isset($_POST['submit'])) {
echo "<table width = 90% border = '1' cellspacing = '5' cellpadding =
'0'>";
// set variables from form input
$rows = $_POST['rows'];
$columns = $_POST['columns'];
// loop to create rows
for ($r = 1; $r <= $rows; $r++) {
echo "<tr>";
// loop to create columns
for ($c = 1; $c <= $columns;$c++) {
echo "<td> </td> ";
}
echo "</tr> ";
}
echo "</table> ";
}
?>
</body>
</html>
As you'll see if you try coding the same thing by hand, PHP's for() loop just saved you a whole
lot of work! And it looks good too - take a look at the source code of the dynamically generated
table, and you'll see that it's nicely formatted, with line breaks at the end of every table cell and
row. This magic is accomplished by forcing a carriage return with in every call to echo().
For more examples of the for() loop in action, visit http://www.php.net/manual/en/controlstructures.for.php.
Loops are frequently used in combination with one of PHP's more complex data types, the
animal known as the array. That's a whole topic in itself, and in fact I'm going to discuss it in
detail in the next segment of this tutorial. Then I'm going to show you how arrays, loops and
forms all work together to make the creation of complex Web forms as easy as eating pie. All
that and more in Part Four!
30
PHP 101 (part 4): The Food Factor
Vikram Vaswani | 12 comments | Tuesday, July 20, 2004
A Big Mistake
Fruity Pizza
Eating Italian
Push And Pull
Looping the Loop
What's That Noise?
Music for the Masses
A Big Mistake
Having spent lots of time travelling around the outer landscape of PHP - learning all about
control structures, operators and variables - you're probably bored. You might even be thinking
of dropping out right now, and instead spending your time more constructively (or so you think)
in front of the idiot box.
That would be a big mistake. And when I say big, I mean humongous.
You see, if you forego this segment of the tutorial for the dubious charms of Ally McBeal, you're
going to miss out on one of PHP's coolest variable types. It's a little thing called an array, and I'm
not exaggerating when I tell you that once you're on speaking terms with it, you're never going to
look at a PHP script the same way again. But hey, don't take my word for it... toss that remote
aside and come see for yourself!
Fruity Pizza
Thus far, the variables we've discussed contained only a single value, such as:
<?php
$i = 5;
?>
However, array variables are a different kettle of fish altogether. An array is a complex variable
that allows you to store multiple values in a single variable (which is handy when you need to
store and represent related information). Think of the array variable as a "container" variable,
which can contain one or more values. For example:
<?php
31
// define an array
$pizzaToppings = array('onion', 'tomato', 'cheese', 'anchovies', 'ham',
'pepperoni');
print_r($pizzaToppings);
?>
Here, $pizzaToppings is an array variable, which contains the values 'onion', 'tomato',
'cheese', 'anchovies', 'ham' and 'pepperoni'. (Array variables are particularly useful for
grouping related values together.)
print_r()
is a special function that allows you to take a sneak peek inside an array. It's more
useful for debugging (finding out why your script doesn't work) than it is for display purposes,
but I'll use it here so you can see what's going on under the surface. You do have your server
running and your browser open, right?
The various elements of the array are accessed via an index number, with the first element
starting at zero. So, to access the element 'onion', you would use the notation
$pizzaToppings[0], while 'anchovies' would be $pizzaToppings[3] - essentially, the array
variable name followed by the index number enclosed within square braces.
PHP also allows you to replace indices with user-defined "keys", in order to create a slightly
different type of array. Each key is unique, and corresponds to a single value within the array.
<?php
// define an array
$fruits = array('red' => 'apple', 'yellow' => 'banana', 'purple' => 'plum',
'green' => 'grape');
print_r($fruits);
?>
In this case, $fruits is an array variable containing four key-value pairs. (The => symbol is used
to indicate the association between a key and its value.) In order to access the value 'banana',
you would use the notation $fruits['yellow'], while the value 'grape' would be accessible
via the notation $fruits['green'].
This type of array is sometimes referred to as a "hash" or "associative array". If you've ever used
Perl, you'll see the similarities to the Perl hash variable.
Eating Italian
The simplest was to define an array variable is the array() function. Here's how:
<?php
32
// define an array
$pasta = array('spaghetti', 'penne', 'macaroni');
?>
The rules for choosing an array variable name are the same as those for any other PHP variable:
it must begin with a letter or underscore, and can optionally be followed by more letters,
numbers and underscores.
Alternatively, you can define an array by specifying values for each element in the index
notation, like this:
<?php
// define
$pasta[0]
$pasta[1]
$pasta[2]
an array
= 'spaghetti';
= 'penne';
= 'macaroni';
?>
If you're someone who prefers to use keys rather than default numeric indices, you might prefer
the following example:
<?php
// define an array
$menu['breakfast'] = 'bacon and eggs';
$menu['lunch'] = 'roast beef';
$menu['dinner'] = 'lasagna';
?>
You can add elements to the array in a similar manner. For example, if you wanted to add the
element 'green olives' to the $pizzaToppings array, you would use something like this:
<?php
// add an element to an array
$pizzaToppings[3] = 'green olives';
?>
In order to modify an element of an array, simply assign a new value to the corresponding scalar
variable. If you wanted to replace 'ham' with 'chicken', you'd use:
<?php
33
// modify an array
$pizzaToppings[4] = 'chicken';
?>
You can do the same using keys. The following statement modifies the element with the key
'lunch' to a different value:
<?php
// modify an array
$menu['lunch'] = 'steak with mashed potatoes';
?>
Push And Pull
You can also add an element to the end of an existing array with the array_push() function:
<?php
// define an array
$pasta = array('spaghetti', 'penne', 'macaroni');
// add an element to the end
array_push($pasta, 'tagliatelle');
print_r($pasta);
?>
And you can remove an element from the end of an array using the interestingly-named
array_pop() function.
<?php
// define an array
$pasta = array('spaghetti', 'penne', 'macaroni');
// remove an element from the end
array_pop($pasta);
print_r($pasta);
?>
If you need to pop an element off the top of the array, you can use the array_shift() function:
34
<?php
// define an array
$pasta = array('spaghetti', 'penne', 'macaroni');
// take an element off the top
array_shift($pasta);
print_r($pasta);
?>
And the array_unshift() function takes care of adding elements to the beginning of the array.
<?php
// define an array
$pasta = array('spaghetti', 'penne', 'macaroni');
// add an element to the beginning
array_unshift($pasta, 'tagliatelle');
print_r($pasta);
?>
The array_push() and array_unshift() functions don't work with associative arrays; to add
elements to these arrays, it's better to use the $arr[$key] = $value notation to add new values to
the array.
The explode() function splits a string into smaller components, based on a user-specified
delimiter, and returns the pieces as elements as an array.
<?php
// define CSV string
$str = 'red, blue, green, yellow';
// split into individual words
$colors = explode(', ', $str);
print_r($colors);
?>
To do the reverse, you can use the implode() function, which creates a single string from all the
elements of an array by joining them together with a user-defined delimiter. Reversing the
example above, we have:
35
<?php
// define array
$colors = array ('red', 'blue', 'green', 'yellow');
// join into single string with 'and'
// returns 'red and blue and green and yellow'
$str = implode(' and ', $colors);
print $str;
?>
Finally, the two examples below show how the sort() and rsort()functions can be used to sort
an array alphabetically (or numerically), in ascending and descending order respectively:
<?php
// define an array
$pasta = array('spaghetti', 'penne', 'macaroni');
// returns the array sorted alphabetically
sort($pasta);
print_r($pasta);
print "<br />";
// returns the array sorted alphabetically in reverse
rsort($pasta);
print_r($pasta);
?>
Looping the Loop
So that takes care of putting data inside an array. Now, how about getting it out?
Retrieving data from an array is pretty simple: all you need to do is access the appropriate
element of the array using its index number. To read an entire array you simply loop over it,
using any of the loop constructs you learned about in Part Three of this tutorial.
How about a quick example?
<html>
<head></head>
<body>
My favourite bands are:
<ul>
<?php
36
// define array
$artists = array('Metallica', 'Evanescence', 'Linkin Park', 'Guns n Roses');
// loop over it and print array elements
for ($x = 0; $x < sizeof($artists); $x++) {
echo '<li>'.$artists[$x];
}
?>
</ul>
</body>
</html>
When you run this script, here's what you'll see:
My favourite bands are:




Metallica
Evanescence
Linkin Park
Guns n Roses
In this case, I've defined an array, and then used the for() loop to: run through it, extract the
elements using the index notation, and display them one after the other.
I'll draw your attention here to the sizeof() function. This function is one of the most important
and commonly used array functions. It returns the size of (read: number of elements within) the
array. It is mostly used in loop counters to ensure that the loop iterates as many times as there are
elements in the array.
If you're using an associative array, the array_keys() and array_values()functions come in
handy, to get a list of all the keys and values within the array.
<?php
// define an array
$menu = array('breakfast' => 'bacon and eggs', 'lunch' => 'roast beef',
'dinner' => 'lasagna');
/* returns the array ('breakfast', 'lunch', 'dinner') with numeric indices */
$result = array_keys($menu);
print_r($result);
print "<br />";
/* returns the array ('bacon and eggs', 'roast beef', 'lasagna') with numeric
indices */
$result = array_values($menu);
print_r($result);
?>
37
What's That Noise?
There is, however, a simpler way of extracting all the elements of an array. PHP 4.0 introduced a
spanking-new loop type designed specifically for the purpose of iterating over an array: the
foreach() loop. (It is similar in syntax to the Perl construct of the same name.) Here's what it
looks like:
foreach ($array as $temp) {
do this!
}
A foreach() loop runs once for each element of the array passed to it as argument, moving
forward through the array on each iteration. Unlike a for() loop, it doesn't need a counter or a
call to sizeof(), because it keeps track of its position in the array automatically. On each run,
the statements within the curly braces are executed, and the currently-selected array element is
made available through a temporary loop variable.
To better understand how this works, consider this rewrite of the previous example, using the
foreach() loop:
<html>
<head></head>
<body>
My favourite bands are:
<ul>
<?php
// define array
$artists = array('Metallica', 'Evanescence', 'Linkin Park', 'Guns n Roses');
// loop over it
// print array elements
foreach ($artists as $a) {
echo '<li>'.$a;
}
?>
</ul>
</body>
</html>
Each time the loop executes, it places the currently-selected array element in the temporary
variable $a. This variable can then be used by the statements inside the loop block. Since a
foreach() loop doesn't need a counter to keep track of where it is in the array, it is lowermaintenance and also much easier to read than a standard for() loop. Oh yeah... and it also
works with associative arrays, with no extra programming needed.
38
Music for the Masses
In addition to their obvious uses, arrays and loops also come in handy when processing forms in
PHP. For example, if you have a group of related checkboxes or a multi-select list, you can use
an array to capture all the selected form values in a single variable, to simplify processing.
Consider the following example, which illustrates this:
<html>
<head></head>
<body>
<?php
// check for submit
if (!isset($_POST['submit'])) {
// and display form
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
<input type="checkbox" name="artist[]" value="Bon Jovi">Bon Jovi
<input type="checkbox" name="artist[]" value="N'Sync">N'Sync
<input type="checkbox" name="artist[]" value="Boyzone">Boyzone
<input type="checkbox" name="artist[]" value="Britney Spears">Britney
Spears
<input type="checkbox" name="artist[]" value="Jethro Tull">Jethro Tull
<input type="checkbox" name="artist[]" value="Crosby, Stills &
Nash">Crosby, Stills & Nash
<input type="submit" name="submit" value="Select">
</form>
<?php
}
else {
// or display the selected artists
// use a foreach loop to read and display array elements
if (is_array($_POST['artist'])) {
echo 'You selected: <br />';
foreach ($_POST['artist'] as $a) {
echo "<i>$a</i><br />";
}
}
else {
echo 'Nothing selected';
}
}
?>
</body>
</html>
When the above form is submitted, PHP will automatically create an array variable, and populate
it with the items selected. This array can then be processed with a foreach() loop, and the
selected items retrieved from it.
39
You can do this with a multi-select list also, simply by using array notation in the select control's
"name" attribute. Try it out for yourself and see... and make sure you tune in for the next PHP
101 tutorial, same time, same channel.
PHP 101 (part 5): Rank and File
Vikram Vaswani | 29 comments | Wednesday, July 21, 2004
Back to School
Handle With Care
• Open the file and assign it a file handle
• Interact with the file via its handle and extract its contents into a PHP variable
• Close the file
Different Strokes
When Laziness is a Virtue
Writing to Ma
Information is Power
Breaking Eggs
Back to School
When you first started reading this series, I promised you that you'd have a whole lot of fun. If
you're the cynical type, you may be feeling that I didn't keep my promise. After all, how much
fun have you really had so far? All you've done is learn a bunch of theoretical rules, added and
subtracted numbers from each other, learnt primitive decision-making and gone round and round
in the circular funhouse of loops. Heck, if this wasn't a PHP tutorial, it would be kindergarten...
I hear you.
In this segment of our ongoing saga, I'm going to teach you how to do something that's definitely
not for kids. It involves getting down and dirty with files on the disk: meeting them (shock!),
reading their contents (shriek!) and (horror of horrors!) writing data to them. All of these exciting
activities will take place under the aegis of PHP's very cool file manipulation API, which allows
you to view and modify file attributes, read and list directory contents, alter file permissions,
retrieve file contents into a variety of native data structures, and search for files based on specific
patterns.
40
Let's get started!
Handle With Care
I'll begin with something simple: opening a file and reading its contents. Let's assume that
somewhere on your disk, hidden under /usr/local/stuff/that/should/be/elsewhere/recipes/, you
have a text file containing the recipe for the perfect Spanish omelette. You now wish to read the
contents of this file into a PHP script.
In order to do this, there are three distinct steps to be followed:



Open the file and assign it a file handle.
Interact with the file, via its handle, and extract its contents into a PHP variable.
Close the file.
Here's a PHP script that does just that:
<?php
// set file to read
$file = '/usr/local/stuff/that/should/be/elsewhere/recipes/omelette.txt' or
die('Could not open file!');
// open file
$fh = fopen($file, 'r') or die('Could not open file!');
// read file contents
$data = fread($fh, filesize($file)) or die('Could not read file!');
// close file
fclose($fh);
// print file contents
echo $data;
?>
Run this script through your Web browser, and PHP should return the contents of the file.
Now let me explain each of the three steps above in detail:
Open the file and assign it a file handle
PHP needs a file handle to read data from a file. This file handle can be created with the fopen()
function, which accepts two arguments: the name and path to the file, and a string indicating the
"mode" in which the file is to be opened ('r' for read).
Three different modes are available for use with the fopen() function. Here's the list:
'r'
- opens a file in read mode
41
'w'
- opens a file in write mode, destroying existing file contents
'a'
- opens a file in append mode, preserving existing file contents
Interact with the file via its handle and extract its contents into a PHP variable
If the fopen() function is successful, it returns a file handle, $fh, which can be used for further
interaction with the file. This file handle is used by the fread() function, which reads the file
and places its contents into a variable.
The second argument to fread() is the number of bytes to be read. You can usually obtain this
information through the filesize() function, which - who'd have guessed it?!- returns the size
of the file in bytes.
Close the file
This last step is not strictly necessary as PHP closes the file automatically once it reaches the end
of the script, but it's a good habit to develop. Explicitly closing the file with fclose() has two
advantages: it ties up loose ends in your script, and it wins you lots of good karma from the PHP
community.
You probably haven't see the die() function before, either. This function is mostly used as a
primitive error-handling mechanism. In the event of a fatal error, such as the file path being
invalid or the file permissions being such that PHP cannot read it, die() terminates script
processing and optionally displays a user-specified error message indicating why it committed
suicide.
Different Strokes
An alternative method of reading data from a file is the very cool file() function, which reads
the entire file into an array (remember them?) with one line of code. Each element of the array
then contains one line from the file. To display the contents of the file, simply iterate over the
array in a foreach() loop and print each element.
The following example demonstrates:
<?php
// set file to read
$file = '/usr/local/stuff/that/should/be/elsewhere/recipes/omelette.txt' or
die('Could not read file!');
// read file into array
$data = file($file) or die('Could not read file!');
// loop through array and print each line
foreach ($data as $line) {
echo $line;
42
}
?>
In this example, the file() command opens the file, reads it into an array and closes the file - all
in one, single, elegant movement. Each element of the array now corresponds to a line from the
file. It's easy to print the file's contents now - just reach for that mainstay of array processing, the
foreach() loop.
Don't want the data in an array? Try the file_get_contents() function, new in PHP 4.3.0 and
PHP 5.0, which reads the entire file into a string:
<?php
// set file to read
$file = '/usr/local/stuff/that/should/be/elsewhere/recipes/omelette.txt' ;
// read file into string
$data = file_get_contents($file) or die('Could not read file!');
// print contents
echo $data;
?>
Who am I kidding? I always use the one-line functions noted above instead of the three-line
sequence of fopen(), fread() and fclose(). Laziness conquers all.
When Laziness is a Virtue
PHP also offers two very useful functions to import files into a PHP script: the include() and
require()functions. These functions can be used to suck external files lock, stock and barrel
into a PHP script, which is very handy if, for example, you have a modular application which has
its code broken down across files in separate locations.
The best way to understand the utility of the include() and require() functions is with an
example. Assume that on your Web site you have a standard menu bar at the top of every page,
and a standard copyright notice in the bottom. Instead of copying and pasting the header and
footer code on each individual page, PHP gurus simply create separate files for the header and
footer, and import them at the top and bottom of each script. This also makes a change to the site
design easier to implement: instead of manually editing a gazillion files, you simply edit two,
and the changes are reflected across your entire site instantaneously.
Let's see a real live example of this in action. Create the header in one file, called header.php:
<html>
<head>
<title><?php echo $page['title'];?></title>
</head>
43
<body>
<!-- top menu bar -->
<table width="90%" border="0" cellspacing="5" cellpadding="5">
<tr>
<td><a href="#">Home</a></td>
<td><a href="#">Site Map</a></td>
<td><a href="#">Search</a></td>
<td><a href="#">Help</a></td>
</tr>
</table>
<!-- header ends -->
Next, create the footer with the copyright notice in a second file, footer.php:
<!-- footer begins -->
<br />
<center>Your usage of this site is subject to its published <a
href="tac.html">terms and conditions</a>. Data is copyright Big Company Inc,
1995-<?php echo date("Y", mktime()); ?></center>
</body>
</html>
Finally, create a script to display the main content of your site, and include() the header and
footer at appropriate places:
<?php
// create an array to set page-level variables
$page = array();
$page['title'] = 'Product Catalog';
/* once the file is imported, the variables set above will become available
to it */
// include the page header
include('header.php');
?>
<!-- HTML content here -->
<?php
// include the page footer
include('footer.php');
?>
Now, when you run the script above, PHP will automatically read in the header and footer files,
merge them with the HTML content, and display the complete page to you. Simple, isn't it?
Notice that you can even write PHP code inside the files being imported. When the file is first
read in, the parser will look for <?php...?> tags, and automatically execute the code inside it.
44
(If you're familiar with JavaScript, you can use this feature to replicate functionality similar to
that of the onLoad() page event handler in JavaScript.)
PHP also offers the require_once() and include_once()functions, which ensure that a file
which has already been read is not read again. This can come in handy if you have a situation in
which you want to eliminate multiple reads of the same include file, either for performance
reasons or to avoid corruption of the variable space.
A quick note on the difference between the include() and require()functions: the
require()function returns a fatal error if the named file cannot be found and halts script
processing, while the include() function returns a warning but allows script processing to
continue.
Writing to Ma
After everything you've just read, you've probably realized that reading a file is not exactly brain
surgery. So let's proceed to something slightly more difficult - writing to a file.
The steps involved in writing data to a file are almost identical to those involved in reading it:
open the file and obtain a file handle, use the file handle to write data to it, and close the file.
There are two differences: first, you must fopen() the file in write mode ('w' for write), and
second, instead of using the fread() function to read from the file handle, use the fwrite()
function to write to it. Take a look:
<?php
// set file to write
$file = '/tmp/dump.txt';
// open file
$fh = fopen($file, 'w') or die('Could not open file!');
// write to file
fwrite($fh, "Look, Ma, I wrote a file! ") or die('Could not write to file');
// close file
fclose($fh);
?>
When you run this script, it should create a file named dump.txt in /tmp, and write a line of text
to it, with a carriage return at the end. Notice that double quotes are needed to convert into a
carriage return.
The fopen(), fwrite() and fread() functions are all binary-safe, which means you can use
them on binary files without worrying about damage to the file contents. Read more about many
of the issues related to binary-safe file manipulation on different platforms at
http://www.php.net/manual/en/function.fopen.php.
45
If I've spoiled you by showing you the one-line shortcut functions for file reads, let me damage
you further by introducing you to the file_put_contents() function, new in PHP 5.0, which
takes a string and writes it to a file in a single line of code.
<?php
// set file to write
$filename = '/tmp/dump.txt';
// write to file
file_put_contents($filename, "Look, Ma, I wrote a file! ") or die('Could not
write to file');
?>
Bear in mind that the directory in which you're trying to create the file must exist before you can
write to it. Forgetting this important step is a common cause of script errors.
Information is Power
PHP also comes with a bunch of functions that allow you to test the status of a file - for example
to find out whether it exists, whether it's empty, whether it's readable or writable, and whether it's
a binary or text file. Of these, the most commonly used operator is the file_exists() function,
which is used to test for the existence of a specific file.
Here's an example which asks the user to enter the path to a file in a Web form, and then returns
a message displaying whether or not the file exists:
<html>
<head>
</head>
<body>
<?php
// if form has not yet been submitted
// display input box
if (!isset($_POST['file'])) {
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
Enter file path <input type="text" name="file">
</form>
<?php
}
// else process form input
else {
// check if file exists
// display appropriate message
if (file_exists($_POST['file'])) {
echo 'File exists!';
}
46
else {
echo 'File does not exist!';
}
}
?>
</body>
</html>
There are many more such functions. Here's a brief list, followed by an example that builds on
the previous one to provide more information on the file specified by the user.













is_dir() - returns a Boolean indicating whether the specified path is a directory
is_file() - returns a Boolean indicating whether the specified file is a regular file
is_link() - returns a Boolean indicating whether the specified file is a symbolic link
is_executable() - returns a Boolean indicating whether the specified file is executable
is_readable()- returns a Boolean indicating whether the specified file is readable
is_writable()- returns a Boolean indicating whether the specified file is writable
filesize() - gets size of file
filemtime() - gets last modification time of file
filamtime() - gets last access time of file
fileowner() - gets file owner
filegroup() - gets file group
fileperms() - gets file permissions
filetype() - gets file type
This script asks for a file name as input and uses the functions above to return information on it.
<html>
<head>
</head>
<body>
<?php
/* if form has not yet been submitted, display input box */
if (!isset($_POST['file'])) {
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
Enter file path <input type="text" name="file">
</form>
<?php
}
// else process form input
else {
echo 'File name: <b>'.$_POST['file'] .'</b><br />';
/* check if file exists and display appropriate message */
if (file_exists($_POST['file'])) {
// print file size
echo 'File size: '.filesize($_POST['file']).' bytes<br />';
// print file owner
echo 'File owner: '.fileowner($_POST['file']).'<br />';
47
// print file group
echo 'File group: '.filegroup($_POST['file']).'<br />';
// print file permissions
echo 'File permissions: '.fileperms($_POST['file']).'<br />';
// print file type
echo 'File type: '.filetype($_POST['file']).'<br />';
// print file last access time
echo 'File last accessed on: '.date('Y-m-d',
fileatime($_POST['file'])).'<br />';
// print file last modification time
echo 'File last modified on: '.date('Y-m-d',
filemtime($_POST['file'])).'<br />';
// is it a directory?
if (is_dir($_POST['file'])) {
echo 'File is a directory <br />';
}
// is it a file?
if (is_file($_POST['file'])) {
echo 'File is a regular file <br />';
}
// is it a link?
if (is_link($_POST['file'])) {
echo 'File is a symbolic link <br />';
}
// is it executable?
if (is_executable($_POST['file'])) {
echo 'File is executable <br />';
}
// is it readable?
if (is_readable($_POST['file'])) {
echo 'File is readable <br />';
}
// is it writable?
if (is_writable($_POST['file'])) {
echo 'File is writable <br />';
}
}
else {
echo 'File does not exist! <br />';
}
}
?>
</body>
</html>
And here's what the output might look like:
File
File
File
File
File
File
File
File
name: /usr/local/apache/logs/error_log
size: 53898 bytes
owner: 0
group: 0
permissions: 33188
type: file
last accessed on: 2004-05-26
last modified on: 2004-06-20
48
File is a regular file
File is readable
Breaking Eggs
So now you know how to read a file, write to it, and test its status. Let's look at some examples
of what you can do with this new-found power.
Let's go back to my Spanish omelette recipe. Let's suppose I'm feeling generous, and I decide
that I'd like to hear what people really think about my culinary skills. Since I have a bunch of
recipes that I'd like to share with people, and since they all look something like this:
SPANISH OMELETTE
INGREDIENTS:
- 1 chopped onion
- 1 chopped tomato
- 1/2 chopped green pepper
- 4 beaten eggs
- Salt and pepper to taste
METHOD:
1. Fry onions in a pan
2. Pour beaten eggs over onions and fry gently
3. Add tomatoes, green pepper, salt and pepper to taste
4. Serve with toast or bread
I need a quick way to convert them all into HTML so that they look presentable on my Web site.
We've already established that I'm lazy, so fuggedaboutme re-creating the recipes in HTML.
Instead, I'll have PHP do the heavy lifting for me:
<html>
<head></head>
<body>
<?php
// read recipe file into array
$data = file('/usr/local/stuff/that/should/be/elsewhere/omelette.txt') or
die('Could not read file!');
/* first line contains title: read it into variable */
$title = $data[0];
// remove first line from array
array_shift($data);
?>
<h2><?php echo $title; ?></h2>
<?php
/* iterate over content and print it */
foreach ($data as $line) {
echo nl2br($line);
}
?>
49
</body>
</html>
I've used the file() function to read the recipe into an array, and assign the first line (the title)
to a variable. That title is then printed at the top of the page. Since the rest of the data is fairly
presentable as is, I can simply print the lines to the screen one after the other. Line breaks are
automatically handled for me by the extremely cool nl2br() function, which converts regular
text linebreaks into the HTML equivalent, the <br /> tag. The end result: an HTML-ized
version of my recipe that the world can marvel at. Take a look:
<html>
<head></head><body>
<h2>SPANISH OMELETTE
</h2>
INGREDIENTS:<br />
- 1 chopped onion<br />
- 1 chopped tomato<br />
- 1/2 chopped green pepper<br />
- 4 beaten eggs<br />
- Salt and pepper to taste<br />
METHOD:<br />
1. Fry onions in a pan<br />
2. Pour beaten eggs over onions and fry gently<br />
3. Add tomatoes, green pepper, salt and pepper to taste<br />
4. Serve with toast or bread<br />
</body>
</html>
If the elegance and creative simplicity of my Spanish omelette recipe has left you speechless, I'm
not surprised - many people feel that way. Until you get your voice back: Ciao... and make sure
you come back to work through Part Six of PHP 101, which discusses creating your own
reusable functions.
PHP 101 (part 6): Functionally Yours
Vikram Vaswani | 10 comments | Saturday, August 7, 2004
A Little Knowledge
In Plain English
Monday Morning Blues
50
Having an Argument... or Two
Circles in the Sand
Marching Order
The Amazing Shrinking Argument List
Going Global
Checking References
A Little Knowledge
If you've been taking your regular dose of PHP 101, you know now enough about PHP to write
simple programs of your own. However, these programs will be "procedural" or linear - the
statements in them will be executed sequentially, one after another - simply because that's the
only programming style I've used so far.
You know what they say about a little knowledge being a dangerous thing... as your PHP scripts
become more and more complex, it's only a matter of time before you bump your head against
the constraints of the procedural method, and begin looking for a more efficient way of
structuring your PHP programs.
That's where Part Six of PHP 101 comes in. In this tutorial I'm going to introduce you to a new
way of doing things, where code doesn't run in a straight line, but twists, leaps and bounds across
the landscape of your script. Most of this activity is accomplished through a programming
construct called a "function", and this tutorial teaches you how to build them (once), use them
(many times), pass them arguments and have them return values, and generally make your scripts
more compact, efficient and maintainable.
In Plain English
Ask a geek to define the term "function", and he'll probably mumble something about a function
being "a block of statements that can be grouped together as a named entity." Since this is a
tutorial on PHP, not an introductory course in Greek, I'll translate that for you: a function is
simply a set of program statements which perform a specific task, and which can be "called", or
executed, from anywhere in your program.
Every programming language comes with its own built-in functions, and typically also allows
developers to define their own functions. For example, if I had a profit statement for the year on
my desk, and I wanted to inflate each number by 35%, I could call my neighborhood accounting
firm and get them to do it for me... or I could write a simple PHP function called
cheatTheShareholders() and have it do the work for me (it's faster, plus PHP doesn't bill by
the hour).
51
There are three important reasons why functions are a Good Thing™. First: user-defined
functions allow you to separate your code into easily identifiable subsections - which are easier
to understand and debug. Second: functions make your program modular, allowing you to write a
piece of code once and then re-use it multiple times within the same program. And third:
functions simplify code updates or changes, because the change needs only to be implemented in
a single place (the function definition). Functions thus save time, money and electrons... and I
know the electrons at least will thank you!
Monday Morning Blues
To see how a function works, look at the following example:
<?php
// define a function
function myStandardResponse() {
echo "Get lost, jerk!<br /><br />";
}
// on the bus
echo "Hey lady, can you spare a dime? <br />";
myStandardResponse();
// at the office
echo "Can you handle Joe's workload, in addition to your own, while he's in
Tahiti for a month? You'll probably need to come in early and work till
midnight, but we are confident you can handle it. Oh, and we can't pay you
extra because of budgetary constraints...<br />";
myStandardResponse();
// at the party
echo "Hi, haven't I seen you somewhere before?<br />";
myStandardResponse();
?>
Here's what the output might look like:
Hey lady, can you spare a dime?
Get lost, jerk!
Can you handle Joe's workload, in addition to your own, while he's in Tahiti
for a month?
You'll probably need to come in early and work till midnight, but we are
confident you can
handle it. Oh, and we can't pay you extra because of budgetary constraints...
Get lost, jerk!
Hi, haven't I seen you somewhere before?
Get lost, jerk!
(Sure it's rude, but it does demonstrate how a function allows you to reuse pieces of code.)
52
The first thing I've done in the script above is define a new function, with the function
keyword. This keyword is followed by the name of the function, which in this case is
myStandardResponse(). All the program code attached to that function is then placed within a
pair of curly braces - and this program code can contain loops, conditional statements, calls to
other user-defined functions, or calls to other PHP functions.
Of course, defining a function is only half of the puzzle; for it to be of any use at all, you need to
"invoke" it. In PHP, as in a million other languages, this is accomplished by calling the function
by its name, as I've done in the example above. Calling a user-defined function is identical to
calling a built-in PHP function like echo() or explode().
Here's the typical format for a function:
function function_name (optional function arguments) {
statement 1...
statement 2...
.
.
.
statement n...
}
Having an Argument... or Two
Functions like the one you saw in the previous section print the same value every time you
invoke them. While this is interesting the first six times, it can get boring on the seventh. What
we need to do, to make these boring, unintelligent functions a little more exciting, is get them to
return a different value each time they are invoked.
Enter arguments.
Arguments work by using a placeholder to represent a certain variable within a function. Values
for this variable are provided to the function at run-time from the main program. Since the input
to the function will differ at each invocation, so will the output.
To see how this works, look at the following function, which accepts a single argument and then
prints it back after a calculation:
<?php
// define a function
function getCircumference($radius) {
echo "Circumference of a circle with radius $radius is ".sprintf("%4.2f",
(2 * $radius * pi()))."<br />";
}
// call a function with an argument
getCircumference(10);
53
// call the same function with another argument
getCircumference(20);
?>
In this example, when the getCircumference() function is called with an argument, the
argument is assigned to the placeholder variable $radius within the function, and then acted
upon by the code within the function definition.
It's also possible to pass more than one argument to a function. This is done using a commaseparated list, as the following example shows:
<?php
// define a function
function changeCase($str, $flag) {
/* check the flag variable and branch the code */
switch($flag) {
case 'U':
print strtoupper($str)."<br />";
break;
case 'L':
print strtolower($str)."<br />";
break;
default:
print $str."<br />";
break;
}
}
// call the function
changeCase("The cow jumped over the moon", "U");
changeCase("Hello Sam", "L");
?>
Here, depending on the value of the second argument, program flow within the function moves
to the appropriate branch and manipulates the first argument.
Note that there is no requirement to specify the data type of the argument being passed to a
function. Since PHP is a dynamically-typed language, it automatically identifies the variable type
and acts on it appropriately.
Circles in the Sand
The functions on the previous page simply printed their output to the screen. But what if you
want the function to do something else with the result? Well, in PHP, you can have a function
return a value, such as the result of a calculation, to the statement that called it. This is done
using a return statement within the function, as shown below:
54
<?php
// define a function
function getCircumference($radius) {
// return value
return (2 * $radius * pi());
}
/* call a function with an argument and store the result in a variable */
$result = getCircumference(10);
/* call the same function with another argument and print the return value */
print getCircumference(20);
?>
Here, the argument passed to the getCircumference() function is processed, and the result is
returned to the main program, where it may be captured in a variable, printed, or dealt with in
other ways.
You can even use the result of a function inside another function, as illustrated in this minor
revision of the example above:
<?php
// define a function
function getCircumference($radius) {
// return value
return (2 * $radius * pi());
}
// print the return value after formatting it
print "The answer is ".sprintf("%4.2f", getCircumference(20));
?>
Return values need not be numbers or strings alone: a function can just as easily return an array
(remember them?), as demonstrated in the following example:
<?php
/* define a function that can accept a list of email addresses */
function getUniqueDomains($list) {
/* iterate over the list, split addresses and add domain part to another
array */
$domains = array();
foreach ($list as $l) {
$arr = explode("@", $l);
$domains[] = trim($arr[1]);
}
// remove duplicates and return
return array_unique($domains);
}
55
// read email addresses from a file into an array
$fileContents = file("data.txt");
/* pass the file contents to the function and retrieve the result array */
$returnArray = getUniqueDomains($fileContents);
// process the return array
foreach ($returnArray as $d) {
print "$d, ";
}
?>
Assuming the file looked like this,
test@test.com
a@x.com
zooman@deeply.bored.org
b@x.com
guess.me@where.ami.net
testmore@test.com
the output of the script above would look like this:
test.com, x.com, deeply.bored.org, where.ami.net,
Note that the return statement terminates program execution inside a function.
Marching Order
The order in which arguments are passed to a function can be important. The following example
requires that the name is passed as the first argument, and the place as the second.
<?php
// define a function
function introduce($name, $place) {
print "Hello, I am $name from $place";
}
// call function
introduce("Moonface", "The Faraway Tree");
?>
This is the output:
Hello, I am Moonface from The Faraway Tree
In this example, if you reversed the order in which arguments were passed to the function, this is what
you'd see:
Hello, I am The Faraway Tree from Moonface
And look what happens if you forget to pass a required argument altogether:
56
Warning: Missing argument 2 for introduce() in xx.php on line 3
Hello, I am Moonface from
In order to avoid such errors, PHP allows you to specify default values for all the arguments in a userdefined function. These default values are used if the function invocation is missing some arguments.
Here's an example:
<?php
// define a function
function introduce($name="John Doe", $place="London") {
print "Hello, I am $name from $place";
}
// call function
introduce("Moonface");
?>
In this case the output would be:
Hello, I am Moonface from London
Notice that the function has been called with only a single argument, even though the function
definition requires two. However, since default values are present for each argument in the function,
the missing argument is replaced by the default value for that argument, and no error is generated.
The Amazing Shrinking Argument List
All the examples on the previous page have one thing in common: the number of arguments in
the function definition is fixed. However, PHP 4.x also supports variable-length argument lists,
by using the func_num_args() and func_get_args() commands. For want of a better name,
these functions are called "function functions". Try wrapping your tongue around that while you
look at the next example, which demonstrates how they can be used:
<?php
// define a function
function someFunc() {
// get the arguments
$args = func_get_args();
// print the arguments
print "You sent me the following arguments:";
foreach ($args as $arg) {
print " $arg ";
}
print "<br />";
}
// call a function with different arguments
someFunc("red", "green", "blue");
57
someFunc(1, "soap");
?>
Hmmm... if you're sneaky, you might have tried to pass someFunc() an array, and found that
instead of displaying the elements of the array, it simply said "Array". You can fix this by adding
a quick test for array arguments inside the function, as in this rewrite:
<?php
// define a function
function someFunc() {
// get the number of arguments passed
$numArgs = func_num_args();
// get the arguments
$args = func_get_args();
// print the arguments
print "You sent me the following arguments: ";
for ($x = 0; $x < $numArgs; $x++) {
print "<br />Argument $x: ";
/* check if an array was passed and, if so, iterate and print
contents */
if (is_array($args[$x])) {
print " ARRAY ";
foreach ($args[$x] as $index => $element) {
print " $index => $element ";
}
}
else {
print " $args[$x] ";
}
}
}
// call a function with different arguments
someFunc("red", "green", "blue", array(4,5), "yellow");
?>
Going Global
Let's now talk a little bit about the variables used within a function, and their relationship with
variables in the outside world. Usually, the variables used within a function are "local" - meaning
that the values assigned to them, and the changes made to them, are restricted to the function
space alone.
Consider this simple example:
<?php
// define a variable in the main program
58
$today = "Tuesday";
// define a function
function getDay() {
// define a variable inside the function
$today = "Saturday";
// print the variable
print "It is $today inside the function<br />";
}
// call the function
getDay();
// print the variable
print "It is $today outside the function";
?>
When you run this script, here is what you will see:
It is Saturday inside the function
It is Tuesday outside the function
In other words, the variable inside the function is insulated from the identically-named variable in the
main program. Variables inside a function are thus aptly called "local" variables because they only exist
within the function in which they are defined.
The reverse is also true: variables defined inside a function cannot be "seen" outside it. To
illustrate, take a look at the next example and its output (or the lack of it):
<?php
// define a function
function getDay() {
// define a variable inside the function
$today = "Saturday";
}
getDay();
print "Today is $today";
?>
Here is the output:
Today is
Depending on the error_reporting you have set up in php.ini, you might also see an error message:
Notice: Undefined variable: today in x1.php on line 10
However, I didn't say this can't be overcome. To have variables within a function accessible from
outside it (and vice-versa), all you need to do is first declare them "global" with the - you
guessed it! - global keyword.
59
Here is a rewrite of the earlier example, this time declaring the $today variable global:
<?php
// define a variable in the main program
$today = "Tuesday";
// define a function
function getDay() {
// make the variable global
global $today;
// define a variable inside the function
$today = "Saturday";
// print the variable
print "It is $today inside the function<br />";
}
// print the variable
print "It is $today before running the function<br />";
// call the function
getDay();
// print the variable
print "It is $today after running the function";
?>
And here is the output:
It is Tuesday before running the function
It is Saturday inside the function
It is Saturday after running the function
Thus, once a variable is declared global, it is available at the global level, and can be manipulated both
inside and outside a function.
PHP also comes with so-called superglobal variables - variables that are always available,
regardless of whether you're inside a function or outside it. You've already seen some of these
special variables in action: the $_SERVER, $_POST and $_GET variables are all superglobals,
which is why you can access things like the currently-executing script's name or form values
even inside a function.
Superglobals are a Good Thing™, because they're always there when you need them, and you
don't need to jump through any hoops to use the data stored inside them. Read more about
superglobals and variable scope at
http://www.php.net/manual/en/language.variables.predefined.php and
http://www.php.net/manual/en/language.variables.scope.php.
Checking References
60
Any discussion about variables in and out of functions would be incomplete without some
mention of the difference between "passing by reference" and "passing by value". So far, all the
examples you've seen have involved passing arguments to a function "by value" - meaning that a
copy of the variable was passed to the function, while the original variable remained untouched.
However, PHP also allows you to pass "by reference" - meaning that instead of passing a value
to a function, you pass a reference to the original variable, and have the function act on that
instead of a copy.
Confusing? Well, this is probably easier to understand with an example. Let's start with this:
<?php
// create a variable
$today = "Saturday";
// function to print the value of the variable
function setDay($day) {
$day = "Tuesday";
print "It is $day inside the function<br />";
}
// call function
setDay($today);
// print the value of the variable
print "It is $today outside the function";
?>
You've already seen this before, and you already know what the output is going to say:
It is Tuesday inside the function
It is Saturday outside the function
This is because when the getDay() function is invoked, it passes the value "Saturday" to the function
("passing by value"). The original variable remains untouched; only its content is sent to the function.
The function then acts on the content, modifying and displaying it.
Now, look at how "passing by reference" works:
<?php
// create a variable
$today = "Saturday";
// function to print the value of the variable
function setDay(&$day) {
$day = "Tuesday";
print "It is $day inside the function<br />";
}
// call function
setDay($today);
61
// print the value of the variable
print "It is $today outside the function";
?>
Notice the ampersand (&) before the argument in the function definition. This tells PHP to use
the variable reference instead of the variable value. When such a reference is passed to a
function, the code inside the function acts on the reference, and modifies the content of the
original variable (which the reference is pointing to) rather than a copy. If you then try retrieving
the value of the original variable outside the function, it returns the modified value:
.
It is Tuesday inside the function
It is Tuesday outside the function
Now you understand why I said no discussion about variables would be complete without mentioning
the two ways of passing variables. This, of course, is what the global keyword does inside a function:
use a reference to ensure that changes to the variable inside the function also reflect outside it. The PHP
manual puts it best when it says "...when you declare a variable as global $var you are in fact
creating a reference to a global variable". For more examples, read all about references at
http://www.zend.com/manual/language.references.php.
And that just about concludes this tutorial. This time you've taken a big step towards better
software design by learning how to abstract parts of your PHP code into reusable functions. You
now know how to add flexibility to your functions by allowing them to accept different
arguments, and how to obtain one (or more) return values from them. Finally, you've learned a
little bit about how PHP treats variables inside and outside functions.
In Part Seven, I'll be showing you how to group related functions together into classes, and also
telling you all about the cool new features in the PHP 5 object model. You definitely don't want
to miss that one!
62
PHP 101 (part 7): The Bear Necessities
Vikram Vaswani | 20 comments | Saturday, August 7, 2004
Alphabet Soup
Back To Class
Animal Antics
Going Deeper
This And That
Under Construction
Hands Off
Extending Yourself
Part 2
Alphabet Soup
So now you know how to create your own functions in PHP, and you've spent the last few days
busily inspecting your applications and turning repeated code fragments into functions. But
functions are just the tip of the software abstraction iceberg. Lurking underneath is a three-letter
acronym that strikes fear into the hearts of most newbie programmers.
OOP.
If you've been programming for a while, you've probably heard the term OOP before - it stands
for Object Oriented Programming, and refers to a technique whereby you create program
"objects" and then use these objects to build the functionality you need into your program. PHP 5
is very big on OOP - it comes with a brand-spanking-new object model which finally brings PHP
objects into conformance with standard OOP principles and offers OO programmers a whole
bunch of new goodies to play with.
Wondering how you can get in on this? Well, wonder no more. Your prayers have been
answered.
Over the course of this tutorial, I'm going to take a brief look at PHP's OO capabilities (both PHP
4 and PHP 5), together with examples and explanations to demonstrate just how powerful it
really is. I'll be covering most of the basics - classes, objects, attributes and methods - and a
couple of more advanced concepts - constructors, destructors, private methods and properties,
and inheritance. And if you're new to object-oriented programming, or just apprehensive about
what lies ahead, don't worry - I promise this will be a lot less painful than you think. And unlike
dentists, I don't lie.
63
Back To Class
Before beginning, though, let's make sure that you have a clear idea of the concepts involved
here.
In PHP, a class is simply a set of program statements which perform a specific task. A typical
class definition contains both variables and functions, and serves as the template from which to
spawn specific instances of that class.
These specific instances of a class are referred to as objects. Every object has certain
characteristics, or properties, and certain pre-defined functions, or methods. These properties
and methods of the object correspond directly with the variables and functions within the class
definition.
Once a class has been defined, PHP allows you to spawn as many instances of the class as you
like. Each of these instances is a completely independent object, with its own properties and
methods, and can therefore be manipulated independently of other objects. This comes in handy
in situations where you need to spawn more than one instance of an object - for example, two
simultaneous database links for two simultaneous queries, or two shopping carts.
Classes also help you keep your code modular - you can define a class in a separate file, and
include that file only in the scripts where you plan to use the class - and simplify code changes,
since you only need to edit a single file to add new functionality to all your spawned objects.
Animal Antics
To understand this better, pick an animal, any animal. I pick the bear, because I like bears. Now
ask yourself, can you consider this bear, within the framework of OOP, as an "object"?
Why not? After all, every bear has certain characteristics - age, weight, sex - which are
equivalent to object properties. And every bear can perform certain activities - eat, sleep, walk,
run, mate - all of which are equivalent to object methods.
Let's take it a little further. Since all bears share certain characteristics, it is possible to conceive
of a template Bear(), which defines the basic characteristics and abilities of every bear on the
planet. Once this Bear() ("class") is used to create a new $bear ("object"), the individual
characteristics of the newly-created Bear can be manipulated independently of other Bears that
may be created from the template.
Now, if you sat down to code this class in PHP 5, it would probably look something like this:
<?php
// PHP 5
// class definition
class Bear {
64
// define properties
public $name;
public $weight;
public $age;
public $sex;
public $colour;
// define methods
public function eat() {
echo $this->name." is eating... ";
}
public function run() {
echo $this->name." is running... ";
}
public function kill() {
echo $this->name." is killing prey... ";
}
public function sleep() {
echo $this->name." is sleeping... ";
}
}
?>
Given this class, it's now simple to spawn as many Bears as you like, and adjust the individual
properties of each. Take a look:
<?php
// my first bear
$daddy = new Bear;
// give him a name
$daddy->name = "Daddy Bear";
// how old is he
$daddy->age = 8;
// what sex is he
$daddy->sex = "male";
// what colour is his coat
$daddy->colour = "black";
// how much does he weigh
$daddy->weight = 300;
// give daddy a wife
$mommy = new Bear;
$mommy->name = "Mommy Bear";
$mommy->age = 7;
$mommy->sex = "female";
$mommy->colour = "black";
$mommy->weight = 310;
// and a baby to complete the family
$baby = new Bear;
$baby->name = "Baby Bear";
65
$baby->age = 1;
$baby->sex = "male";
$baby->colour = "black";
$baby->weight = 180;
// a nice evening in the Bear family
// daddy kills prey and brings it home
$daddy->kill();
// mommy eats it
$mommy->eat();
// and so does baby
$baby->eat();
// mommy sleeps
$mommy->sleep();
// and so does daddy
$daddy->sleep();
// baby eats some more
$baby->eat();
?>
As the illustration above shows, once new objects are defined, their individual methods and
variables can be accessed and modified independent of each other. This comes in very handy, as
the rest of this tutorial will show.
Going Deeper
Now that you've got the concepts straight, let's take a look at the nitty-gritty of a class definition.
<?php
// PHP 5
// class definition
class Bear {
// define public properties
public $name;
public $age;
// more properties
// define public methods
public function eat() {
echo $this->name." is eating... ";
// more code
}
// more methods
}
?>
66
Every class definition begins with the keyword class, followed by a class name. You can give
your class any name that strikes your fancy, so long as it doesn't collide with a reserved PHP
word. A pair of curly braces encloses all class variables and functions, which are written as you
would normally code them.
PHP 5 also introduces the concept of visibility to the object model. Visibility controls the extent
to which object properties and methods can be manipulated by the caller, and plays an important
role in defining how open or closed your class is. Three levels of visibility exist, ranging from
most visible to least visible: public, private and protected. Within the class definition, you can
mark the visibility of a property or method by preceding it with one of the keywords - public,
private, or protected .
By default, class methods and properties are public; this allows the calling script to reach inside
your object instances and manipulate them directly. If you don't like the thought of this intrusion,
you can mark a particular property or method as private or protected, depending on how
much control you want to cede over the object's internals (more on this shortly).
Since the PHP 4 object model does not include support for visibility, the class definition above
would not work in PHP 4. Instead, you would need to use the following:
<?php
// PHP 4
// class definition
class Bear {
// define properties
var $name;
var $weight;
var $age;
var $sex;
var $colour;
// define methods
function eat() {
echo $this->name." is eating... ";
}
function run() {
echo $this->name." is running... ";
}
function kill() {
echo $this->name." is killing prey... ";
}
function sleep() {
echo $this->name." is sleeping... ";
}
}
?>
67
From the above, it should be clear that class properties and methods in PHP 4 are always public
...and there ain't nuttin' you can do about that!
In order to create a new instance of a class, you use the new keyword to assign the newly created
object to a PHP variable.
<?php
$daddy = new Bear;
?>
In English, the above would mean "create a new object of class Bear() and assign it to the
variable $daddy ".
You can now access all the methods and properties of the class via this variable. For example,
the code
<?php
$daddy->name = "Daddy Bear";
?>
would mean "assign the value Daddy Bear to the variable $name of this specific instance of the
class Bear()", while the statement
<?php
$daddy->sleep();
?>
would mean "execute the function sleep() for this specific instance of the class Bear()".
Note the -> symbol used to connect objects to their properties or methods, and the fact that the $
symbol is omitted when accessing properties of a class instance.
This And That
In case you need to access functions or variables within the class definition itself, both PHP 4
and PHP 5 offer the $this keyword, which is used to refer to "this" class. To see how this
works, let's alter the eat() method to accept a number of food units and then add that to the
bear's weight.
<?php
// PHP 5
// class definition
68
class Bear {
// define properties
public $name;
public $weight;
// define methods
public function eat($units) {
echo $this->name." is eating ".$units." units of food... ";
$this->weight += $units;
}
}
?>
In this case, the $this prefix indicates that the variable to be modified exists within the class or, in English, "add the argument provided to eat() to the variable $weight within this object".
The $this prefix thus provides a convenient way to access variables and functions which are
"local" to the class.
Here's an example of how it works:
<?php
// create instance
$baby = new Bear;
$baby->name = "Baby Bear";
$baby->weight = 1000;
// now create another instance
// this one has independent values for each property
$brother = new Bear;
$brother->name = "Brother Bear";
$brother->weight = 1000;
// retrieve properties
echo $baby->name." weighs ".$baby->weight." units ";
echo $brother->name." weighs ".$brother->weight." units ";
// call eat()
$baby->eat(100);
$baby->eat(50);
$brother->eat(11);
// retrieve new values
echo $baby->name." now weighs ".$baby->weight." units ";
echo $brother->name." now weighs ".$brother->weight." units ";
?>
The output of this will read:
Baby Bear weighs 1000 units
Brother Bear weighs 1000 units
Baby Bear is eating 100 units of food...
69
Baby Bear is eating 50 units of food...
Brother Bear is eating 11 units of food...
Baby Bear now weighs 1150 units
Brother Bear now weighs 1011 units
Under Construction
It's also possible to automatically execute a function when the class is called to create a new
object. This is referred to in geek lingo as a constructor and, in order to use it, your PHP 5 class
definition must contain a special function, __construct().
For example, if you'd like all newly born bears to be brown and weigh 100 units, you could add
this to your class definition:
<?php
// PHP 5
// class definition
class Bear {
// define properties
public $name;
public $weight;
public $age;
public $colour;
// constructor
public function __construct() {
$this->age = 0;
$this->weight = 100;
$this->colour = "brown";
}
// define methods
}
?>
In PHP 4, your constructor must have the same name as the class. Here's the equivalent code for
PHP 4:
<?php
// PHP 4
// class definition
class Bear {
// define properties
var $name;
var $weight;
var $age;
var $colour;
// constructor
function Bear() {
70
$this->age = 0;
$this->weight = 100;
$this->colour = "brown";
}
// define methods
}
?>
Now, try creating and using an instance of the class:
<?php
// create instance
$baby = new Bear;
$baby->name = "Baby Bear";
echo $baby->name." is ".$baby->colour." and weighs ".$baby->weight." units at
birth";
?>
Here, the constructor automatically sets default properties every time an object of the class is
instantiated. Therefore, when you run the script above, you will see this:
Baby Bear is brown and weighs 100 units at birth
Hands Off
As noted previously, PHP 5 makes it possible to mark class properties and methods as private,
which means that they cannot be manipulated or viewed outside the class definition. This is
useful to protect the inner workings of your class from manipulation by object instances.
Consider the following example, which illustrates this by adding a new private variable,
$_lastUnitsConsumed, to the Bear() class:
<?php
// PHP 5
// class definition
class Bear {
// define properties
public $name;
public $age;
public $weight;
private $_lastUnitsConsumed;
// constructor
public function __construct() {
$this->age = 0;
$this->weight = 100;
$this->_lastUnitsConsumed = 0;
}
71
// define methods
public function eat($units) {
echo $this->name." is eating ".$units." units of food... ";
$this->weight += $units;
$this->_lastUnitsConsumed = $units;
}
public function getLastMeal() {
echo "Units consumed in last meal were ".$this->_lastUnitsConsumed."
";
}
}
?>
Now, since the $_lastUnitsConsumed variable is declared as private, any attempt to modify it
from an object instance will fail. Here is an example:
<?php
$bob = new Bear;
$bob->name = "Bobby Bear";
$bob->eat(100);
$bob->eat(200);
echo $bob->getLastMeal();
// the next line will generate a fatal error
$bob->_lastUnitsConsumed = 1000;
?>
In a similar way, class methods can also be marked as private - try it out for yourself and see.
Extending Yourself
Two of the best things about OOP, whether in PHP 4 or in PHP 5, are extensibility and
inheritance. Very simply, this means that you can create a new class based on an existing class,
add new features (read: properties and methods) to it, and then create objects based on this new
class. These objects will contain all the features of the original parent class, together with the
new features of the child class.
As an illustration, consider the following PolarBear() class, which extends the Bear() class
with a new method.
<?php
// PHP 5
// class definition
class Bear {
// define properties
public $name;
public $weight;
72
public $age;
public $sex;
public $colour;
// constructor
public function __construct() {
$this->age = 0;
$this->weight = 100;
}
// define methods
public function eat($units) {
echo $this->name." is eating ".$units." units of food... ";
$this->weight += $units;
}
public function run() {
echo $this->name." is running... ";
}
public function kill() {
echo $this->name." is killing prey... ";
}
public function sleep() {
echo $this->name." is sleeping... ";
}
}
// extended class definition
class PolarBear extends Bear {
// constructor
public function __construct() {
parent::__construct();
$this->colour = "white";
$this->weight = 600;
}
// define methods
public function swim() {
echo $this->name." is swimming... ";
}
}
?>
The extends keyword is used to extend a parent class to a child class. All the functions and
variables of the parent class immediately become available to the child class. This is clearly
visible in the following code snippet:
<?php
// create instance of Bear()
$tom = new Bear;
$tom->name = "Tommy Bear";
73
// create instance of PolarBear()
$bob = new PolarBear;
$bob->name = "Bobby Bear";
// $bob can use all the methods of Bear() and PolarBear()
$bob->run();
$bob->kill();
$bob->swim();
// $tom can use all the methods of Bear() but not PolarBear()
$tom->run();
$tom->kill();
$tom->swim();
?>
In this case, the final call to $tom->swim() will fail and cause an error, because the Bear() class
does not contain a swim() method. However, none of the calls to $bob->run() or $bob>kill() will fail, because as a child of the Bear() class, PolarBear() inherits all the methods
and properties of its parent.
Note how the parent class constructor has been called in the PolarBear() child class constructor
- it's a good idea to do this so that all necessary initialization of the parent class is carried out
when a child class is instantiated. Child-specific initialization can then be done in the child class
constructor. Only if a child class does not have a constructor, is the parent class constructor
automatically called.
You can do this in PHP 4, too. Here's a PHP 4 version of the PolarBear class definition:
<?php
// PHP 4
// extended class definition
class PolarBear extends Bear {
// constructor
function PolarBear() {
parent::Bear();
$this->colour = "white";
$this->weight = 600;
}
// define methods
function swim() {
echo $this->name." is swimming... ";
}
}
?>
74
To prevent a class or its methods from being inherited, use the final keyword before the class or
method name (this is new in PHP 5 and will not work in older versions of PHP). Here's an
example, which renders the Bear() class un-inheritable (if that's actually a word):
<?php
// PHP 5
// class definition
final class Bear {
// define properties
// define methods
}
// extended class definition
// this will fail because Bear() cannot be extended
class PolarBear extends Bear {
// define methods
}
// create instance of PolarBear()
// this will fail because Bear() could not be extended
$bob = new PolarBear;
$bob->name = "Bobby Bear";
echo $bob->weight;
?>
75
PHP 101 (part 7): The Bear Necessities - Part
2
Vikram Vaswani | 9 comments | Saturday, August 7, 2004
PHP 101 (part 7): The Bear Necessities - Part 1
Ending On A High Note
Discovering New Things
Access Denied
Ending On A High Note
Just as there are constructors, so also are there destructors. Destructors are object methods
which are called when the last reference to an object in memory is destroyed, and they are
usually tasked with clean-up work - for example, closing database connections or files,
destroying a session and so on. Destructors are only available in PHP 5, and must be named
__destruct(). Here's an example:
<?php
// PHP 5
// class definition
class Bear {
// define properties
public $name;
public $weight;
public $age;
public $sex;
public $colour;
// constructor
public function __construct() {
$this->age = 0;
$this->weight = 100;
$this->colour = "brown";
}
// destructor
public function __destruct() {
echo $this->name." is dead. He was ".$this->age." years old and
".$this->weight." units heavy. Rest in peace! ";
}
// define methods
public function eat($units) {
76
echo $this->name." is eating ".$units." units of food... ";
$this->weight += $units;
}
public function run() {
echo $this->name." is running... ";
}
public function kill() {
echo $this->name." is killing prey... ";
}
}
// create instance of Bear()
$daddy = new Bear;
$daddy->name = "Daddy Bear";
$daddy->age = 10;
$daddy->kill();
$daddy->eat(2000);
$daddy->run();
$daddy->eat(100);
?>
Here, once the script ends, no reference will exist for $daddy, and so the destructor will be called
automatically. The output would look like this:
Daddy
Daddy
Daddy
Daddy
Daddy
Bear
Bear
Bear
Bear
Bear
is
is
is
is
is
killing prey...
eating 2000 units of food...
running...
eating 100 units of food...
dead. He was 10 years old and 2200 units heavy. Rest in peace!
Discovering New Things
PHP 4 and PHP 5 come with a bunch of functions designed to let you discover object properties
and methods, and find out which class an object belongs to. The first two of these are the
get_class() and get_parent_class() functions, which tell you the name of the classes which
spawned a particular object. Consider the following class definition:
<?php
// PHP 5
// base class
class Bear {
public $name;
public $weight;
// constructor
public function __construct() {
}
// define methods
77
public function eat() {
}
public function run() {
}
public function sleep() {
}
}
// derived class
class GrizzlyBear extends Bear {
public function kill() {
}
}
?>
And now consider the following script, which uses get_class() and get_parent_class() to
retrieve the class name from an instance:
<?php
$joe = new GrizzlyBear;
$joe->name = "Joe Bear";
$joe->weight = 1000;
echo "Class: " . get_class($joe);
echo "Parent class: " . get_parent_class(get_class($joe));
?>
You can view all the properties exposed by a class with get_class_vars(), and all its methods
with get_class_methods() function. To view properties of the specific object instance, use
get_object_vars() instead of get_class_vars(). Here is an example:
<?php
// create instance
$joe = new GrizzlyBear;
$joe->name = "Joe Bear";
$joe->weight = 1000;
// get class name
$className = get_class($joe);
// get class properties
echo "Class properties: ";
print_r(get_class_vars($className));
// get class methods
echo " Class methods: ";
print_r(get_class_methods($className));
// get this instance's properties
echo " Instance properties: ";
78
print_r(get_object_vars($joe));
?>
and here is some sample output:
Class properties:
Array
(
[name] =>
[weight] =>
)
Class methods:
Array
(
[0] => kill
[1] => __construct
[2] => eat
[3] => run
[4] => sleep
)
Instance properties:
Array
(
[name] => Joe Bear
[weight] => 1000
)
As noted in one of the previous segments of this tutorial, the print_r() function allows you to
look inside any PHP variable, including an object. It's extremely useful, so note it down for
future reference.
Access Denied
And now that you know the basics of how objects work in PHP, let's wrap this up with a realworld example. Consider the following userAuth() class, which exposes methods to validate a
user login using an encrypted password file such as /etc/passwd or .htaccess, both of which are
used on Unix systems (i.e. most of the Internet). I'll assume here that the passwords in the
password file are encrypted with MD5, and use a 12-character salt beginning with $1$:
<?php
// PHP 5
// class definition
class userAuth {
// define properties
public $username;
private $passwd;
private $passwdFile;
private $_resultCode;
79
// constructor
// must be passed username and password
public function __construct($username, $password) {
$this->username = $username;
$this->passwd = $password;
$this->_resultCode = -1;
}
// used to set file to read for password data
public function setPasswdFile($file) {
$this->passwdFile = $file;
}
// returns: -1 if user does not exist
//
0 if user exists but password is incorrect
//
1 if username and password are correct
public function getResultCode() {
return $this->_resultCode;
}
public function authenticateUser() {
// make sure that the script has permission to read this file!
$data = file($this->passwdFile);
// iterate through file
foreach ($data as $line) {
$arr = explode(":", $line);
// if username matches
// test password
if ($arr[0] == $this->username) {
// if match, user/pass combination is correct
// return 1
if ($arr[1] == crypt($this->passwd, $arr[1])) {
$this->_resultCode = 1;
break;
}
// otherwise return 0
else {
$this->_resultCode = 0;
break;
}
}
}
}
// end class definition
}
?>
Most of this should be clear to you from the examples in previous pages. In case it isn't, the
following script should help you understand what's happening:
<?php
80
// create instance
$ua = new userAuth("joe", "secret");
// set password file
$ua->setPasswdFile("passwd.txt");
// perform authentication
$ua->authenticateUser();
// check result code and display message
switch ($ua->getResultCode()) {
case -1:
echo "Could not find your user account";
break;
case 0:
echo "Your password was incorrect";
break;
case 1:
echo "Welcome, ".$ua->username;
break;
}
?>
Here, the username and password is passed to the object constructor, as is the name and path of
the file containing authentication credentials. The authenticateUser() method takes care of
parsing the password file and checking if the user exists and the password is correct. Depending
on what it finds, a result code is generated and stored in the private variable $_resultCode. This
variable can be read through the getResultCode() method, and an appropriate message
displayed. And since this entire thing is neatly encapsulated in a class, I can take it anywhere, use
it in any script - even inside another application - and extend it to support different types of
authentication schemes and containers.
There's a lot more you can do with objects, especially in PHP 5; I've restrained myself here
because I didn't want to confuse you too much with talk of overloading, abstract classes and
static methods. If you're interested, however, drop by
http://www.php.net/manual/en/language.oop.php for more. And make sure you come back for
Part Eight of PHP 101, because I'm going to show you how to hook your scripts up to a MySQL
database.
81
PHP 101 (part 8): Databases and Other
Animals - Part 1
Vikram Vaswani | 35 comments | Tuesday, August 31, 2004
Mix and Match
Building Blocks
Animal Magnetism
Hello Database!
Different Strokes...
...for Different Folks
PHP 101 (part 8): Databases and Other Animals - Part 2
Mix and Match
One of the most compelling things PHP has going for it is its support for a variety of database
management systems, including MySQL, PostgreSQL, Oracle and Microsoft Access. By virtue
of this support, PHP developers can create sophisticated data-driven Web applications at a
fraction of the time and cost required by competing alternatives. And nowhere is this more clear
than in PHP's longtime support of MySQL, the very fast, very reliable and very feature-rich
open-source RDBMS.
By using PHP and MySQL together, developers can benefit from huge savings on the licensing
costs of commercial alternatives, and also leverage off the tremendous amount of thought PHP
and MySQL developers have put into making sure that the two packages work together
seamlessly and smoothly. And since both PHP and MySQL are open-source projects, when you
use the two of them together you know you're getting the most up-to-date technology available.
And that's always a good thought to go to bed with.
OK. Enough of the marketing talk. Let's get down to business.
In this issue of PHP 101, I'm going to show you how to use PHP to extract data from a database,
and use that data to dynamically build a Web page. In order to try out the examples in this
tutorial, you'll need a working MySQL installation, which you can obtain from the MySQL Web
site at http://www.mysql.com/. If you have some knowledge of SQL (Structured Query
Language, the language used to interact with a database server) you'll find it helpful, but it's not
essential.
Building Blocks
82
In order to use MySQL and PHP together, your PHP build must include support for MySQL. On
UNIX, this is accomplished by adding the --with-mysql option to the configure script when
building PHP on UNIX, and pointing PHP to the MySQL client libraries. On Windows, the
MySQL client libraries are built in to PHP 4 and activated by default. In PHP 5, pre-built .dll
files are included with the Windows distribution. Read more about this at
http://www.php.net/manual/en/ref.mysql.php.
Unix users should note that PHP 4 ships with a set of MySQL client libraries, which are
activated by default; however, PHP 5 no longer bundles these libraries due to licensing issues, so
you need to obtain, install and activate them yourself. They're included with the MySQL
distribution, and are installed automatically when you install MySQL. To activate the MySQL
extension, ext/mysql, add the --with-mysql option to PHP's configure script. For more
information on this change, read
http://www.php.net/manual/en/faq.databases.php#faq.databases.mysql.php5.
And finally (as if all that wasn't quite confusing enough) PHP 5 also comes with a new MySQL
extension, called ext/mysqli (MySQL Improved). You can use this new extension to access the
new features in MySQL 4.1.2 or better, and to gain the benefits of improved speed and security.
To activate this extension on UNIX, add the --with-mysqli option to PHP's configure script,
and point PHP to the mysql_config program that comes with MySQL 4.1 and above. For
Windows users, a pre-built version of ext/mysqli is included in the win32 PHP distribution.
Read more about this at http://www.php.net/manual/en/ref.mysqli.php.
To figure out which extension you need, use the following rule of thumb:


If you need the new features in MySQL 4.1.2 or better, or if you're using an older version of
MySQL but still want to benefit from the speed/security improvements in the new extension,
use ext/mysqli.
If you don't fall into either of the categories above, or don't know what I'm talking about, use
regular ext/mysql.
In case you were wondering, this tutorial covers both ext/mysql and ext/mysqli, so you
actually get two for the price of one. Keep reading, and let me introduce you to MySQL.
Animal Magnetism
Every MySQL database is composed of one or more tables. These tables, which structure data
into rows and columns, are what lend organization to the data.
Here's an example of what a typical table looks like:
+----+-----------+----------+
| id | country
| animal
|
+----+-----------+----------+
| 1 | America
| eagle
|
| 2 | China
| dragon
|
| 3 | England
| lion
|
83
| 4 | India
| tiger
|
| 5 | Australia | kangaroo |
| 6 | Norway
| elk
|
+----+-----------+----------+
As you can see, a table divides data into rows, with a new entry (or record) on every row. The data in
each row is further broken down into cells (or fields), each of which contains a value for a particular
attribute of the data. For example, if you consider the record for the country "India", you'll see that the
record is clearly divided into separate fields for record number, country name and national animal.
The rows within a table are not arranged in any particular order - they can be sorted
alphabetically, by number, by name, or by any other criteria you choose to specify. It is therefore
necessary to have some method of identifying a specific record in a table. In the example above,
each record is identified by a unique number; this unique field is referred to as the primary key
for that table.
You use the Structured Query Language, SQL, to interact with the MySQL server and tell it to
create a table, mark a field as primary, insert records, edit records, retrieve records... basically,
anything that involves manipulating the data or the database. To see how this works, examine the
following SQL, which creates the table above:
CREATE DATABASE testdb;
CREATE TABLE `symbols` (
`id` int(11) NOT NULL auto_increment,
`country` varchar(255) NOT NULL default '',
`animal` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`)
) TYPE=MyISAM;
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT
INTO
INTO
INTO
INTO
INTO
INTO
`symbols`
`symbols`
`symbols`
`symbols`
`symbols`
`symbols`
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
(1,
(2,
(3,
(4,
(5,
(6,
'America', 'eagle');
'China', 'dragon');
'England', 'lion');
'India', 'tiger');
'Australia', 'kangaroo');
'Norway', 'elk');
You can enter these commands either interactively or non-interactively through the MySQL
commandline client program, which you run by navigating to the mysql/bin directory from your
shell or DOS box and typing - with no ; because this is a shell command - either mysql, or
mysql db_name if you want to choose an existing database to work with. Read
http://dev.mysql.com/doc/mysql/en/mysql.html for more information on how to use the MySQL
commandline client, and the tutorial at
http://www.melonfire.com/community/columns/trog/article.php?id=39 to understand what each
of the SQL commands above does. SQL is a lot like spoken English, so it won't take you very
long to pick it up. Just don't try to turn those backticks into single quotation marks.
Once the data has been imported, run a quick SELECT query to check that everything is working
as it should be:
84
mysql> SELECT * FROM `symbols`;
+----+-----------+----------+
| id | country
| animal
|
+----+-----------+----------+
| 1 | America
| eagle
|
| 2 | China
| dragon
|
| 3 | England
| lion
|
| 4 | India
| tiger
|
| 5 | Australia | kangaroo |
| 6 | Norway
| elk
|
+----+-----------+----------+
6 rows in set (0.06 sec)
In English, the query above means "show me all the records from the table named symbols". If you saw
the same output as above, you're good to go!
Hello Database!
Now, let's use PHP to do exactly the same thing. You could use PHP to set up the database from
the start, but as ours already exists we'll simply fire a SELECT query at the database 'testdb', and
display the results in an HTML page:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
// set database server access variables:
$host = "localhost";
$user = "test";
$pass = "test";
$db = "testdb";
// open connection
$connection = mysql_connect($host, $user, $pass) or die ("Unable to
connect!");
// select database
mysql_select_db($db) or die ("Unable to select database!");
// create query
$query = "SELECT * FROM symbols";
// execute query
$result = mysql_query($query) or die ("Error in query: $query.
".mysql_error());
// see if any rows were returned
if (mysql_num_rows($result) > 0) {
// yes
// print them one after another
echo "<table cellpadding=10 border=1>";
85
while($row = mysql_fetch_row($result)) {
echo "<tr>";
echo "<td>".$row[0]."</td>";
echo "<td>" . $row[1]."</td>";
echo "<td>".$row[2]."</td>";
echo "</tr>";
}
echo "</table>";
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
mysql_free_result($result);
// close connection
mysql_close($connection);
?>
</body>
</html>
Here's what the result looks like:
As you can see, using PHP to get data from a database involves several steps, each of which is
actually a pre-defined PHP function. Let's dissect each step:
1. The first thing to do is specify some important information needed to establish a connection to
the database server. This information includes the server name, the username and password
required to gain access to it, and the name of the database to query. These values are all set up
in regular PHP variables.
<?php
$host = "localhost";
$user = "test";
86
$pass = "test";
$db = "testdb";
?>
2. To begin communication with a MySQL database server, you need to open a connection to that
server. All communication between PHP and the database server takes place through this
connection.
In order to initialize this connection, PHP offers the mysql_connect() function:
<?php
$connection = mysql_connect($server, $user, $pass);
?>
All the parameters in mysql_connect()are optional, but there are three you will
generally need to use anywhere beyond your own machine: the database server name,
username and password. If the database server and the Web server are running on the
same physical machine, you can use localhost as the database server name this is in fact
the default value supplied by PHP.
mysql_connect() returns a "link identifier", which is stored in the variable
$connection. This identifier is used when communicating with the database.
3. Once you have a connection to the database, you must select a database for use with the
mysql_select_db() function:
<?php
mysql_select_db($db) or die ("Unable to select database!");
?>
This function must be passed the name of the database to be used for all subsequent
queries. An optional second argument here is the link identifier; if no identifier is
specified, the last opened link is assumed. If you have two or more database connections
open simultaneously, it's a good idea to specify the link identifier as the second argument
to mysql_select_db() - and indeed to all other mysql_* functions in the script, so that
PHP doesn't get confused about which connection to use where.
4. The next step is to create the query and execute it. This is accomplished with the
mysql_query() function.
<?php
$query = "SELECT * FROM symbols";
$result = mysql_query($query) or die ("Error in query: $query.
".mysql_error());
87
?>
This function also needs two parameters: the query string and the link identifier for the
connection. Again, if no link identifier is specified, the last opened link is used.
Depending on whether or not the query was successful, the function returns true or false;
a failure can be caught via the ...or die() clause of the statement, and the
mysql_error() function can be used to display the corresponding error message.
5. If mysql_query() is successful, the result set returned by the query is stored in the variable
$result. This result set may contain one or more rows or columns of data, depending on your
query. You can retrieve specific subsets of the result set with different PHP functions, including
the one used here - the mysql_fetch_row() function - which fetches a single row of data as
an array called $row. Fields in that row can then be accessed using standard PHP array notation.
Each time you call mysql_fetch_row(), the next record in the result set is returned. This
makes mysql_fetch_row() very suitable for use in a while() or for() loop.
<?php
if (mysql_num_rows($result) > 0) {
while($row = mysql_fetch_row($result)) {
echo "<td>".$row[0]."</td>";
echo "<td>".$row[1]."</td>";
echo "<td>".$row[2]."</td>";
}
}
?>
Notice that the call to mysql_fetch_row() is wrapped in a conditional test, which first
checks to see if any rows were returned at all. This information is provided by the
mysql_num_rows() function, which contains the number of rows returned by the query.
Obviously, you can only use this function with queries that return data, like SELECT or
SHOW.It is not appropriate for use with INSERT, UPDATE, DELETE or similar queries.
There are several other alternatives to mysql_fetch_row(), which will be explained a
little later.
6. Finally, since each result set returned after a query occupies memory, it's a good idea to use the
mysql_free_result() function to free up the used memory. After the result set is freed, if
no further queries are to be run, you can close the connection to the MySQL server with
mysql_close().
<?php
mysql_free_result($result);
mysql_close($connection);
?>
88
Different Strokes...
You can also use PHP's mysql_fetch_row() and list() functions to obtain a simple array of
values, and then assign these values to different variables - a variation of the technique in the
previous section. Take a look (only the while() loop changes):
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "testdb";
// open connection
$connection = mysql_connect($host, $user, $pass) or die ("Unable to
connect!");
// select database
mysql_select_db($db) or die ("Unable to select database!");
// create query
$query = "SELECT * FROM symbols";
// execute query
$result = mysql_query($query) or die ("Error in query: $query.
".mysql_error());
// see if any rows were returned
if (mysql_num_rows($result) > 0) {
// yes
// print them one after another
echo "<table cellpadding=10 border=1>";
while(list($id, $country, $animal) = mysql_fetch_row($result)) {
echo "<tr>";
echo "<td>$id</td>";
echo "<td>$country</td>";
echo "<td>$animal</td>";
echo "</tr>";
}
echo "</table>";
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
89
mysql_free_result($result);
// close connection
mysql_close($connection);
?>
</body>
</html>
In this case, the list() function is used to assign different elements of the result set to PHP
variables, which are then used when rendering the page.
You can use PHP's mysql_fetch_assoc() function to represent each row as an associative array
of field-value pairs - a minor variation of the technique used above:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "testdb";
// open connection
$connection = mysql_connect($host, $user, $pass) or die ("Unable to
connect!");
// select database
mysql_select_db($db) or die ("Unable to select database!");
// create query
$query = "SELECT * FROM symbols";
// execute query
$result = mysql_query($query) or die ("Error in query: $query.
".mysql_error());
// see if any rows were returned
if (mysql_num_rows($result) > 0) {
// yes
// print them one after another
echo "<table cellpadding=10 border=1>";
while($row = mysql_fetch_assoc($result)) {
echo "<tr>";
echo "<td>".$row['id']."</td>";
echo "<td>".$row['country']."</td>";
echo "<td>".$row['animal']."</td>";
echo "</tr>";
90
}
echo "</table>";
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
mysql_free_result($result);
// close connection
mysql_close($connection);
?>
</body>
</html>
Notice that in this case, field values are accessed using the field name instead of the index.
Of all the alternatives, however, the function I like the most is the mysql_fetch_object()
function, which returns each row as an object (remember them from Part Seven?) with properties
corresponding to the field names:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "testdb";
// open connection
$connection = mysql_connect($host, $user, $pass) or die ("Unable to
connect!");
// select database
mysql_select_db($db) or die ("Unable to select database!");
// create query
$query = "SELECT * FROM symbols";
// execute query
$result = mysql_query($query) or die ("Error in query: $query.
".mysql_error());
// see if any rows were returned
91
if (mysql_num_rows($result) > 0) {
// yes
// print them one after another
echo "<table cellpadding=10 border=1>";
while($row = mysql_fetch_object($result)) {
echo "<tr>";
echo "<td>".$row->id."</td>";
echo "<td>".$row->country."</td>";
echo "<td>".$row->animal."</td>";
echo "</tr>";
}
echo "</table>";
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
mysql_free_result($result);
// close connection
mysql_close($connection);
?>
</body>
</html>
Here, each $row object is created with properties corresponding to the field names in that row.
Row values can thus be accessed using standard object->property notation.
If you're the type that likes to have your cake and eat it too, you will probably enjoy the
mysql_fetch_array() function, which returns both an associative array and a numericallyindexed array, a combination of the mysql_fetch_row() and mysql_fetch_assoc() functions.
Read about it at http://www.php.net/manual/en/function.mysql-fetch-array.php.
...for Different Folks
If you're using PHP 5, you can do the same thing using the new ext/mysqli extension, which
offers a number of new features. This extension can be used in two ways: procedural (using
functions), and object-oriented (using class methods and properties). Consider the next script,
which uses ext/mysqli in a procedural manner:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
92
// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "testdb";
// open connection
$connection = mysqli_connect($host, $user, $pass, $db) or die ("Unable to
connect!");
// create query
$query = "SELECT * FROM symbols";
// execute query
$result = mysqli_query($connection, $query) or die ("Error in query: $query.
".mysqli_error());
// see if any rows were returned
if (mysqli_num_rows($result) > 0) {
// yes
// print them one after another
echo "<table cellpadding=10 border=1>";
while($row = mysqli_fetch_row($result)) {
echo "<tr>";
echo "<td>".$row[0]."</td>";
echo "<td>".$row[1]."</td>";
echo "<td>".$row[2]."</td>";
echo "</tr>";
}
echo "</table>";
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
mysqli_free_result($result);
// close connection
mysqli_close($connection);
?>
</body>
</html>
As you can see, this looks a lot like the code written for ext/mysql. The only real difference - at
least to the naked eye - is the fact that function names now begin with mysqli_* instead of
mysql_*. Of course, there are a whole bunch of differences under the hood: ext/mysqli is
faster, more secure and more powerful than regular ext/mysql, and also includes support for
prepared statements, bound result sets, multiple simultaneous queries, transactions and a whole
bunch of other cool stuff.
93
You can also use ext/mysqli in an object-oriented way, where each task - connecting, querying,
fetching - is actually a method of the mysqli() object:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "testdb";
// create mysqli object
// open connection
$mysqli = new mysqli($host, $user, $pass, $db);
// check for connection errors
if (mysqli_connect_errno()) {
die("Unable to connect!");
}
// create query
$query = "SELECT * FROM symbols";
// execute query
if ($result = $mysqli->query($query)) {
// see if any rows were returned
if ($result->num_rows > 0) {
// yes
// print them one after another
echo "<table cellpadding=10 border=1>";
while($row = $result->fetch_array()) {
echo "<tr>";
echo "<td>".$row[0]."</td>";
echo "<td>".$row[1]."</td>";
echo "<td>".$row[2]."</td>";
echo "</tr>";
}
echo "</table>";
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
$result->close();
}
else {
// print error message
94
echo "Error in query: $query. ".$mysqli->error;
}
// close connection
$mysqli->close();
?>
</body>
</html>
Here, the new keyword is used to instantiate an object of class mysqli, and pass the object
constructor connection information (including the database name). The resulting object, stored in
the variable $mysqli, then exposes methods and properties to perform the tasks of querying,
fetching and processing rows, and handling errors.
If you look closely at the two scripts above, you'll notice the numerous similarities between the
function and method names, and the structure of the script. Of the two, though, the objectoriented method is recommended, especially in light of the new object model in PHP 5.
A couple of other important differences to keep in mind:


With ext/mysqli, you can include the database name in the arguments passed to the
mysqli_connect() function or to the mysqli()constructor.
When calling mysqli_query() or the mysqli object's query() method, the link identifier is
mandatory, not optional.
PHP 101 (part 8): Databases and Other
Animals - Part 2
Vikram Vaswani | 5 comments | Tuesday, August 31, 2004
PHP 101 (part 8): Databases and Other Animals - Part 1
Surgical Insertion
Wiping Out
Looking Inside
Oops!
95
Surgical Insertion
So now you know how to execute a SELECT query to retrieve a result set from the database.
However, you can also use PHP's MySQL API for queries that don't return a result set - for
example, an INSERT or UPDATE query. Consider the following example, which demonstrates this
by asking for user input through a form and then INSERT-ing that data into the database:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
if (!isset($_POST['submit'])) {
// form not submitted
?>
<form action="<?=$_SERVER['PHP_SELF']?>" method="post">
Country: <input type="text" name="country">
National animal: <input type="text" name="animal">
<input type="submit" name="submit">
</form>
<?php
}
else {
// form submitted
// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "testdb";
// get form input
// check to make sure it's all there
// escape input values for greater safety
$country = empty($_POST['country']) ? die ("ERROR: Enter a country") :
mysql_escape_string($_POST['country']);
$animal = empty($_POST['animal']) ? die ("ERROR: Enter an animal") :
mysql_escape_string($_POST['animal']);
// open connection
$connection = mysql_connect($host, $user, $pass) or die ("Unable to
connect!");
// select database
mysql_select_db($db) or die ("Unable to select database!");
// create query
$query = "INSERT INTO symbols (country, animal) VALUES ('$country',
'$animal')";
96
// execute query
$result = mysql_query($query) or die ("Error in query: $query.
".mysql_error());
// print message with ID of inserted record
echo "New record inserted with ID ".mysql_insert_id();
// close connection
mysql_close($connection);
}
?>
</body>
</html>
Here, the user is first presented with a form asking for a country and its national animal.
Once the form is submitted, the form input is used inside to create an INSERT query, which is
then sent to the database with the mysql_query() method. Since mysql_query() returns a
Boolean value indicating whether the query was successful or not, it is possible to check whether
the INSERT took place and return an appropriate message:
There are two new functions in the example above. The mysql_escape_string() function
escapes special characters (like quotes) in the user input so that it can be safely entered into the
database; while the mysql_insert_id() returns the ID generated by the previous INSERT query
(useful only if the table into which the INSERT occurs contains an AUTO_INCREMENT field). Both
these functions are also available in ext/mysqli.
Wiping Out
Obviously, you can also do the same thing with other data manipulation statements. This next
example demonstrates how to use a DELETE statement with PHP to selectively delete items from
the table. For variety, I'm going to use ext/mysqli this time around:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
97
// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "testdb";
// create mysqli object
// open connection
$mysqli = new mysqli($host, $user, $pass, $db);
// check for connection errors
if (mysqli_connect_errno()) {
die("Unable to connect!");
}
// if id provided, then delete that record
if (isset($_GET['id'])) {
// create query to delete record
$query = "DELETE FROM symbols WHERE id = ".$_GET['id'];
// execute query
if ($mysqli->query($query)) {
// print number of affected rows
echo $mysqli->affected_rows." row(s) affected";
}
else {
// print error message
echo "Error in query: $query. ".$mysqli->error;
}
}
// query to get records
$query = "SELECT * FROM symbols";
// execute query
if ($result = $mysqli->query($query)) {
// see if any rows were returned
if ($result->num_rows > 0) {
// yes
// print them one after another
echo "<table cellpadding=10 border=1>";
while($row = $result->fetch_array()) {
echo "<tr>";
echo "<td>".$row[0]."</td>";
echo "<td>".$row[1]."</td>";
echo "<td>".$row[2]."</td>";
echo "<td><a
href=".$_SERVER['PHP_SELF']."?id=".$row[0].">Delete</a></td>";
echo "</tr>";
}
}
// free result set memory
$result->close();
}
else {
// print error message
echo "Error in query: $query. ".$mysqli->error;
}
98
// close connection
$mysqli->close();
?>
</body>
</html>
Here's what it looks like:
Notice my usage of the affected_rows property of the mysqli object here - this returns the total
number of rows affected by the last operation. It's available in ext/mysql as well, as the function
mysql_affected_rows().
Looking Inside
PHP comes with a bunch of functions designed to tell you everything you would ever want to
know about the MySQL client and server, their version numbers, the total number of databases
available, the tables inside each database, the processes running... you name it, and it's probably
there. Here's an example which uses them to give you a big-picture view of what's going on
inside your MySQL RDBMS:
<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
// set server access variables
$host = "localhost";
$user = "root";
$pass = "guessme";
$db = "testdb";
// open connection
$connection = mysql_connect($host, $user, $pass) or die ("Unable to
99
connect!");
// get database list
$query = "SHOW DATABASES";
$result = mysql_query($query) or die ("Error in query: $query.
".mysql_error());
echo "<ul>";
while ($row = mysql_fetch_array($result)) {
echo "<li>".$row[0];
// for each database, get table list and print
$query2 = "SHOW TABLES FROM ".$row[0];
$result2 = mysql_query($query2) or die ("Error in query: $query2.
".mysql_error());
echo "<ul>";
while ($row2 = mysql_fetch_array($result2)) {
echo "<li>".$row2[0];
}
echo "</ul>";
}
echo "</ul>";
// get version and host information
echo "Client version: ".mysql_get_client_info()."<br />";
echo "Server version: ".mysql_get_server_info()."<br />";
echo "Protocol version: ".mysql_get_proto_info()."<br />";
echo "Host: ".mysql_get_host_info()."<br />";
// get server status
$status = mysql_stat();
echo $status;
// close connection
mysql_close($connection);
?>
</body>
</html>
Here's what the output might look like:
100
The first part of this script is fairly simple: it runs the SHOW DATABASES query to get a list of
databases, then iterates over the list and runs the SHOW TABLES command to retrieve the list of
tables inside each. Next, the mysql_get_*_info() functions provide the client version number,
the MySQL version number, the version number of the special MySQL client-server protocol
used for communication between the two, the current host name, and how it is connected to the
MySQL server. Finally, new in PHP 4.3.0 is the mysql_stat() function, which returns a string
containing status information on the MySQL server (including information on server uptime,
open tables, queries per second and other statistical information).
Oops!
All done? Nope, not quite yet - before you go out there and start building cool data-driven Web
sites, you should be aware that both MySQL extensions come with powerful error-tracking
functions which can speed up development time. Take a look at the following example, which
contains a deliberate error in the SELECT query string:
<?php
// connect
$connection = mysql_connect("localhost", "test", "test") or die("Invalid
server or user");
mysql_select_db("testdb", $connection) or die("Invalid database");
// query
$query = "SELECT FROM symbols";
// result
$result = mysql_query($query,$connection);
// look for errors and print
if(!$result) {
$error_number = mysql_errno();
$error_msg = mysql_error();
echo "MySQL error $error_number: $error_msg";
}
101
// disconnect
mysql_close($connection);
?>
Here's an example of the output:
The mysql_errno() function displays the error code returned by MySQL if there's an error in
your SQL statement, while the mysql_error() function returns the actual error message. Turn
these both on, and you'll find that they can significantly reduce the time you spend fixing bugs.
The ext/mysqli code tree includes two additional functions for connection errors,
mysqli_connect_errno() and mysqli_connect_error(), which contain information on
connection (not query) errors only. Use these to debug errors in your MySQL connections, as in
the example below:
<?php
// create mysqli object
// open connection
$mysqli = new mysqli("localhost", "test", "test", "testdb");
// check for connection errors
if (mysqli_connect_errno()) {
die("Unable to connect: ".mysqli_connect_error());
}
// query
$query = "SELECT FROM symbols";
// execute query
$result = $mysqli->query($query);
// look for errors and print
if(!$result) {
$error_number = $mysqli->errno;
$error_msg = $mysqli->error;
echo "MySQL error $error_number: $error_msg";
}
// disconnect
$mysqli->close();
?>
102
And in case you were wondering why I haven't used object syntax for these two functions in the
script above, it's actually very simple: I can't. You see, if there is an error in connecting to the
server, the mysqli() object will not be created, and so methods and properties related to that
object will not exist. For this reason, to debug connection errors in ext/mysqli, you must
always use the procedural, rather than the object, notation.
And that's about all I have for this issue of PHP 101. In Part Nine I'm going to tell you all about
PHP 5's built-in DBMS alternative, the very cool SQLite database engine. Don't miss it!
103
PHP 101 (part 9): SQLite My Fire! - Part 1
Vikram Vaswani | 3 comments | Thursday, September 16, 2004
Hard Choices
Making New Friends
The Bookworm Turns
Anatomy Class
Different Strokes
Adding to the Collection
PHP 101 (part 9): SQLite My Fire! - Part 2
Hard Choices
If you've been paying attention, you now know how to use PHP's MySQL API to perform
queries and process result sets. You might even have started thinking about how to re-program
your site to run off a MySQL database. All of this is a Good Thing - it means you're getting
comfortable with using PHP's database support to power your applications - but there's still a
little further to go.
As you saw in Part Eight, enabling MySQL support in PHP 5.0 is not as simple as it used to be.
Instead of supporting MySQL out of the box, PHP now requires you to make all kinds of
decisions about versions and libraries before allowing you to hook your scripts up to a MySQL
database. If you're lazy (and deep down, we both know you are), you might instead prefer to try a
simpler option: the SQLite database engine.
Built-in SQLite support is new to PHP 5.0, and offers users a lightweight database system that is
fast, efficient and gets the job done. Since it's enabled by default in PHP 5.0, it provides a viable
alternative to MySQL; you can use it out of the box, without spending time on version checks
and library downloads; just install PHP 5 and start typing. That's why I'm devoting a whole
tutorial to it - so get out of bed, make yourself some coffee and let's get started!
Making New Friends
Before getting into the code, let's make sure that you have a clear idea of what SQLite is (and
isn't). Unlike MySQL, which operates on a client-server paradigm, SQLite is a file-based
database engine and uses file I/O (input/output) functions to store and read databases from files
on disk. It's also much, much smaller than MySQL - the command-line version of SQLite weighs
in at under 200 KB - and supports most of the SQL commands you're used to.
104
This small size shouldn't deceive you, however - according to the official SQLite Web site,
SQLite supports databases up to 2 terabytes in size and is actually faster than MySQL in certain
situations. SQLite database files are easily portable, and SQLite databases created on Windows
work fine on *NIX platforms and vice-versa.
One of SQLite's more interesting aspects is that it is completely typeless. Fields in an SQLite
database need not be associated with a specific type, and even if they are, you can still insert
values of different types into them (there is one exception to this rule, but I'll get to that later).
This is important, because it means that if you're concerned about values of the wrong type
getting into your tables, you need to write code to implement type checking in your application.
Another important difference between MySQL and SQLite lies in their licensing policies: unlike
MySQL, SQLite source code is completely public-domain, which means that you can use and
distribute it however you choose in both commercial and non-commercial products. Take a look
at http://sqlite.org/copyright.html for more on this.
In order to use SQLite and PHP together, your PHP build must include SQLite. This is enabled
by default in both the UNIX and Windows versions of PHP 5. Read more about this at
http://www.php.net/manual/en/ref.sqlite.php. If you're a PHP 4.x user, though, don't lose heart you can still use SQLite, by manually downloading and installing php_sqlite.dll from
http://snaps.php.net (Windows) or the latest tarball from http://pecl.php.net/package/SQLite
(UNIX). You don't need to download anything else; the SQLite 'client' is its own engine.
The Bookworm Turns
As with MySQL, you use regular SQL commands to interact with an SQLite database. The exact
SQL syntax used by SQLite is listed at http://sqlite.org/lang.html, but for most operations SQL
commands are standard.
Here's an example, which sets up the table I'll be using in this tutorial:
C:\WINDOWS\Desktop\sqlite>sqlite library.db
SQLite version 2.8.15
Enter ".help" for instructions
sqlite> create table books (
...> id integer primary key,
...> title varchar(255) not null,
...> author varchar(255) not null
...>);
sqlite> insert into books (title, author) values
'J.R.R. Tolkien');
sqlite> insert into books (title, author) values
Morgue', 'Edgar Allen Poe');
sqlite> insert into books (title, author) values
'Jerome K. Jerome');
sqlite> insert into books (title, author) values
'Arthur Conan Doyle');
sqlite> insert into books (title, author) values
('The Lord Of The Rings',
('The Murders In The Rue
('Three Men In A Boat',
('A Study In Scarlet',
('Alice In Wonderland',
105
'Lewis Carroll');
sqlite> .exit
You can enter these commands either interactively or non-interactively through the SQLite
commandline program, which is available at http://sqlite.org/download.html as a precompiled
binary for Windows and Linux. SQLite 2.* is the version currently used in both branches of
PHP, with SQLite 3.* support anticipated for PDO and later PHP 5.* releases.
Extract the downloaded files to a directory of your choice, cd into it from your shell or DOS box
and type 'sqlite'. You should see the SQLite version information and the line:
Enter ".help" for instructions
Read http://sqlite.org/sqlite.html for more information on how to use the commandline program.
Once the data has been imported into the database file library.db, run a quick SELECT query to
check if everything is working as it should:
sqlite> select * from books;
1|The Lord Of The Rings|J.R.R. Tolkien
2|The Murders In The Rue Morgue|Edgar Allen Poe
3|Three Men In A Boat|Jerome K. Jerome
4|A Study In Scarlet|Arthur Conan Doyle
5|Alice In Wonderland|Lewis Carroll
If you saw the same output as above, you're good to go!
Anatomy Class
Now, use PHP to communicate with SQLite, generate the same result set and format it as an
HTML page. Here's the code:
<html>
<head></head>
<body>
<?php
// set path of database file
$db = $_SERVER['DOCUMENT_ROOT']."/../library.db";
// open database file
$handle = sqlite_open($db) or die("Could not open database");
// generate query string
$query = "SELECT * FROM books";
// execute query
$result = sqlite_query($handle, $query) or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
// if rows exist
if (sqlite_num_rows($result) > 0) {
// get each row as an array
106
// print values
echo "<table cellpadding=10 border=1>";
while($row = sqlite_fetch_array($result)) {
echo "<tr>";
echo "<td>".$row[0]."</td>";
echo "<td>".$row[1]."</td>";
echo "<td>".$row[2]."</td>";
echo "</tr>";
}
echo "</table>";
}
// all done
// close database file
sqlite_close($handle);
?>
</body>
</html>
If all goes well, you should see something like this:
If you remember what you learned in Part Eight, the PHP script above should be easy to
decipher. In case you don't, here's a fast rundown:
1. The ball starts rolling with the sqlite_open() function, which accepts the name of the
database file as argument and attempts to open it. If this database file cannot be found, an
empty database file will be created with the supplied name (assuming the script has write access
to the directory).
<?php
$db = $_SERVER['DOCUMENT_ROOT']."/../library.db";
$handle = sqlite_open($db) or die("Could not open database");
?>
The database file library.db needs to be kept somewhere it can't be accessed through
the browser by visitors to your site. That means that you need to create it outside your
public_html, www or htdocs directory, in a directory that allows your scripts read/write
107
permissions. Web hosting companies generally will offer a space above your web-visible
directory where you can do this. $_SERVER['DOCUMENT_ROOT']."/.." is the directory
directly above your web-visible directory.
If successful, the sqlite_open() function returns a handle to the file, which is stored in
the variable $handle and is used for all subsequent communication with the database.
2. The next step is to create and execute the query, with the sqlite_query() function.
<?php
$query = "SELECT * FROM books";
$result = sqlite_query($handle, $query) or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
?>
This function also needs two parameters: the database handle and the query string.
Depending on whether or not the query was successful, the function returns true or false;
in the event of a failure, the sqlite_error_string() and sqlite_last_error()
functions can be used to display the error that took place.
3. If sqlite_query() is successful, the result set returned by the query is stored in the variable
$result. You can retrieve the records in the result set with the sqlite_fetch_array()
function, which fetches a single row of data as an array called $row. Fields in that record are
represented as array elements, and can be accessed using standard index notation.
Each time you call sqlite_fetch_array(), the next record in the result set is returned.
This makes sqlite_fetch_array() very suitable for use in a while() loop, in much the
same way as mysql_fetch_row() was used earlier.
<?php
if (sqlite_num_rows($result) > 0) {
echo "<table cellpadding=10 border=1>";
while($row = sqlite_fetch_array($result)) {
echo "<tr>";
echo "<td>".$row[0]."</td>";
echo "<td>".$row[1]."</td>";
echo "<td>".$row[2]."</td>";
echo "</tr>";
}
echo "</table>";
}
?>
The number of records returned by the query can be retrieved with the
sqlite_num_rows() function. Or, if what you're really interested in is the number of
fields in the result set, use the sqlite_num_fields() function instead. Of course, these
108
are only applicable with queries that actually return records; it doesn't really make sense
to use them with INSERT, UPDATE or DELETE queries.
4. Once you're done, it's a good idea to close the database handle and return the used memory to
the system, with a call to sqlite_close():
<?php
sqlite_close($handle);
?>
In PHP 5 you can also use the SQLite API in an object-oriented way, wherein each of the
functions above becomes a method of the SQLiteDatabase() object. Take a look at this next
listing, which is equivalent to the one above:
<html>
<head></head>
<body>
<?php
// set path of database file
$file = $_SERVER['DOCUMENT_ROOT']."/../library.db";
// create database object
$db = new SQLiteDatabase($file) or die("Could not open database");
// generate query string
$query = "SELECT * FROM books";
// execute query
// return result object
$result = $db->query($query) or die("Error in query");
// if rows exist
if ($result->numRows() > 0) {
// get each row as an array
// print values
echo "<table cellpadding=10 border=1>";
while($row = $result->fetch()) {
echo "<tr>";
echo "<td>".$row[0]."</td>";
echo "<td>".$row[1]."</td>";
echo "<td>".$row[2]."</td>";
echo "</tr>";
}
echo "</table>";
}
// all done
// destroy database object
unset($db);
?>
</body>
</html>
109
Here, the new keyword is used to instantiate an object of the class SQLiteDatabase() by passing
the object constructor the name of the database file. If the database file does not already exist, a
new database file is created. The resulting object, stored in $db, then exposes methods and
properties to perform queries. Every query returns an instance of the class SQLiteResult(),
which in turn exposes methods for fetching and processing records.
If you look closely at the two scripts above, you'll see the numerous similarities between the
procedural function names and the object method names. While the correspondence between the
two is not perfect, it's usually close enough to make it possible to guess the one if you know the
other.
Different Strokes
As with the MySQL API, PHP's SQLite API offers you more than one way to skin a cat. For
example, you can retrieve each row as an object with the sqlite_fetch_object() method, and
access field values by using the field names as object properties. Here's an example:
<html>
<head></head>
<body>
<?php
// set path of database file
$db = $_SERVER['DOCUMENT_ROOT']."/../library.db";
// open database file
$handle = sqlite_open($db) or die("Could not open database");
// generate query string
$query = "SELECT * FROM books";
// execute query
$result = sqlite_query($handle, $query) or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
// if rows exist
if (sqlite_num_rows($result) > 0) {
// get each row as an object
// print field values as object properties
echo "<table cellpadding=10 border=1>";
while($obj = sqlite_fetch_object($result)) {
echo "<tr>";
echo "<td>".$obj->id."</td>";
echo "<td>".$obj->title."</td>";
echo "<td>".$obj->author."</td>";
echo "</tr>";
}
echo "</table>";
}
// all done
// close database file
sqlite_close($handle);
110
?>
</body>
</html>
Another option is to retrieve the complete result set in one fell swoop with the
sqlite_fetch_all() function. This function retrieves the complete set of records as an array of
arrays; each element of the outer array represents a record, and is itself structured as an array
whose elements represent fields in that record.
Here's an example, which might make this clearer:
<html>
<head></head>
<body>
<?php
// set path of database file
$db = $_SERVER['DOCUMENT_ROOT']."/../library.db";
// open database file
$handle = sqlite_open($db) or die("Could not open database");
// generate query string
$query = "SELECT * FROM books";
// execute query
$result = sqlite_query($handle, $query) or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
// get the complete result set as a series of nested arrays
$data = sqlite_fetch_all($result);
// all done
// close database file
sqlite_close($handle);
// check the array to see if it contains at least one record
if (sizeof($data) > 0) {
echo "<table cellpadding=10 border=1>";
// iterate over outer array (rows)
// print values for each element of inner array (columns)
foreach ($data as $row) {
echo "<tr>";
echo "<td>".$row[0]."</td>";
echo "<td>".$row[1]."</td>";
echo "<td>".$row[2]."</td>";
echo "</tr>";
}
echo "</table>";
}
?>
</body>
</html>
111
In all the previous examples, the database remained open while the result set was processed,
because records were retrieved one after another with the sqlite_fetch_array() or
sqlite_fetch_object() functions. The example above is unique in that the database can be
closed before the result set array is processed. This is because the entire result set is retrieved at
once and stored in the $data array, so there really isn't any need to leave the database open while
processing it.
If your result set contains only a single field, use the sqlite_fetch_single()function, which
retrieves the value of the first field of a row. The PHP manual puts it best when it says "this is
the most optimal way to retrieve data when you are only interested in the values from a single
column of data." Take a look:
<html>
<head></head>
<body>
<?php
// set path of database file
$db = $_SERVER['DOCUMENT_ROOT']."/../library.db";
// open database file
$handle = sqlite_open($db) or die("Could not open database");
// generate query string
// this query returns only a single record with a single field
$query = "SELECT author FROM books WHERE title = 'A Study In Scarlet'";
// execute query
$result = sqlite_query($handle, $query) or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
// if a row exists
if (sqlite_num_rows($result) > 0) {
// get the value of the first field of the first row
echo sqlite_fetch_single($result);
}
// all done
// close database file
sqlite_close($handle);
?>
</body>
</html>
You can even use the sqlite_fetch_single() function in combination with a while() loop to
iterate over a result set containing many records but a single field. Notice also my usage of the
sqlite_has_more() function, to check if the next row exists or not.
<html>
<head></head>
<body>
<?php
// set path of database file
$db = $_SERVER['DOCUMENT_ROOT']."/../library.db";
112
// open database file
$handle = sqlite_open($db) or die("Could not open database");
// generate query string
$query = "SELECT DISTINCT author FROM books";
// execute query
$result = sqlite_query($handle, $query) or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
// if rows exist
if (sqlite_num_rows($result) > 0) {
echo "<table cellpadding=10 border=1>";
// check for more rows
while (sqlite_has_more($result)) {
// get first field from each row
// print values
$row = sqlite_fetch_single($result);
echo "<tr>";
echo "<td>".$row."</td>";
echo "</tr>";
}
echo "</table>";
}
// all done
// close database file
sqlite_close($handle);
?>
</body>
</html>
You can, of course, do the same thing using object notation in PHP 5. However, you need to know that
sqlite_has_more() is one function that really doesn't translate to its object method name; in an OO
script, you would need to call $result->valid();.
This script is the OO equivalent of the one above:
<html>
<head></head>
<body>
<?php
// set path of database file
$file = $_SERVER['DOCUMENT_ROOT']."/../library.db";
// create database object
$db = new SQLiteDatabase($file) or die("Could not open database");
// generate query string
$query = "SELECT DISTINCT author FROM books";
// execute query
$result = $db->query($query) or die("Error in query");
113
// if rows exist
if ($result->numRows() > 0) {
echo "<table cellpadding=10 border=1>";
// check for more rows
while ($result->valid()) {
// get first field from each row
// print values
$row = $result->fetchSingle();
echo "<tr>";
echo "<td>".$row."</td>";
echo "</tr>";
}
echo "</table>";
}
// all done
// destroy database object
unset($db);
?>
</body>
</html>
PHP 101 (PART 9): SQLITE MY FIRE! PART 2
Vikram Vaswani | 2 comments | Thursday, September 16, 2004
PHP 101 (PART 9): SQLITE MY FIRE! - PART 1
Not My Type
Starting From Scratch
A Few Extra Tools
Not My Type
Whilst on the topic of INSERT, remember my statement a couple pages back about how SQLite is
typeless and so you can insert values of any type into any field? There is one important exception
to this rule: a field marked as INTEGER PRIMARY KEY. In SQLite, fields marked as INTEGER
PRIMARY KEY do two important things: they provide a unique numeric identifier for each record
in the table, and if you insert a NULL value into them, SQLite automatically inserts a value that is
1 greater than the largest value already present in that field.
114
INTEGER PRIMARY KEY
fields in SQLite thus perform the equivalent of AUTO_INCREMENT fields
in MySQL, and are a convenient way of automatically numbering your records. Obviously, you
can't insert non-numeric values into such a field, which is why I said they were an exception to
the typeless rule. Read more about this at http://www.sqlite.org/datatypes.html.
Since the books table used in the previous example already contains such a field (the id field),
it's clear that every INSERT into it with a NULL value for that field generates a new record
number. If you'd like to retrieve this number, PHP has a way to do that too - just use the
sqlite_last_insert_rowid() function, which returns the ID of the last inserted row
(equivalent to the mysql_insert_id() function in PHP's MySQL API).
To see this in action, update the if() loop in the middle of the previous script to include a call to
sqlite_last_insert_rowid(), as follows:
<?php
// check to see if the form was submitted with a new record
if (isset($_POST['submit'])) {
// make sure both title and author are present
if (!empty($_POST['title']) && !empty($_POST['author'])) {
// generate INSERT query
$insQuery = "INSERT INTO books (title, author) VALUES
(\"".sqlite_escape_string($_POST['title'])."\",
\"".sqlite_escape_string($_POST['author'])."\")";
// execute query
$insResult = sqlite_query($handle, $insQuery) or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
// print success message
echo "<i>Record successfully inserted with ID
".sqlite_last_insert_rowid($handle)."!</i><p />";
}
else {
// missing data
// display error message
echo "<i>Incomplete form input. Record not inserted!</i><p />";
}
}
?>
If you need to, you can also find out how many rows were affected using the sqlite_changes()
function - try it for yourself and see!
Starting From Scratch
You'll remember, from the beginning of this tutorial, that I suggested you initialize the
library.db database using the SQLite commandline program. Well, that isn't the only way to
create a fresh SQLite database - you can use PHP itself to do this, by issuing the necessary
CREATE TABLE and INSERT commands through the sqlite_query() function. Here's how:
115
<?php
// set path of database file
$db = $_SERVER['DOCUMENT_ROOT']."/../library2.db";
// open database file
$handle = sqlite_open($db) or die("Could not open database");
// create database
sqlite_query($handle, "CREATE TABLE books (id INTEGER PRIMARY KEY, title
VARCHAR(255) NOT NULL, author VARCHAR(255) NOT NULL)") or die("Error in
query: ".sqlite_error_string(sqlite_last_error($handle)));
// insert records
sqlite_query($handle, "INSERT INTO books (title, author) VALUES ('The Lord Of
The Rings', 'J.R.R. Tolkien')") or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
sqlite_query($handle, "INSERT INTO books (title, author) VALUES ('The Murders
In The Rue Morgue', 'Edgar Allan Poe')") or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
sqlite_query($handle, "INSERT INTO books (title, author) VALUES ('Three Men
In A Boat', 'Jerome K. Jerome')") or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
sqlite_query($handle, "INSERT INTO books (title, author) VALUES ('A Study In
Scarlet', 'Arthur Conan Doyle')") or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
sqlite_query($handle, "INSERT INTO books (title, author) VALUES ('Alice In
Wonderland', 'Lewis Carroll')") or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
// print success message
echo "<i>Database successfully initialized!";
// all done
// close database file
sqlite_close($handle);
?>
Or, in PHP 5, you can use the object-oriented approach:
<?php
// set path of database file
$file = $_SERVER['DOCUMENT_ROOT']."/../library3.db";
// create database object
$db = new SQLiteDatabase($file) or die("Could not open database");
// create database
$db->query("CREATE TABLE books (id INTEGER PRIMARY KEY, title VARCHAR(255)
NOT NULL, author VARCHAR(255) NOT NULL)") or die("Error in query");
116
// insert records
$db->query("INSERT INTO books (title, author) VALUES ('The Lord Of The
Rings', 'J.R.R. Tolkien')") or die("Error in query");
$db->query("INSERT INTO books (title, author) VALUES ('The Murders In The Rue
Morgue', 'Edgar Allan Poe')") or die("Error in query");
$db->query("INSERT INTO books (title, author) VALUES ('Three Men In A Boat',
'Jerome K. Jerome')") or die("Error in query");
$db->query("INSERT INTO books (title, author) VALUES ('A Study In Scarlet',
'Arthur Conan Doyle')") or die("Error in query");
$db->query("INSERT INTO books (title, author) VALUES ('Alice In Wonderland',
'Lewis Carroll')") or die("Error in query");
// print success message
echo "<i>Database successfully initialized!";
// all done
// destroy database object
unset($db);
?>
A Few Extra Tools
Finally, the SQLite API also includes some ancillary functions, to provide you with information
on the SQLite version and encoding, and on the error code and message generated by the last
failed operation. The following example demonstrates the sqlite_libversion() and
sqlite_libencoding() functions, which return the version number and encoding of the linked
SQLite library respectively:
<?php
// version
echo "SQLite version: ".sqlite_libversion()."<br />";
// encoding
echo "SQLite encoding: ".sqlite_libencoding()."<br />";
?>
When things go wrong, reach for the sqlite_last_error() function, which returns the last
error code returned by SQLite. Of course, this error code - a numeric value - is not very useful in
itself; to convert it to a human-readable message, couple it with the sqlite_error_string()
function. Consider the following example, which illustrates by attempting to run a query with a
deliberate error in it:
<?php
// set path of database file
117
$db = $_SERVER['DOCUMENT_ROOT']."/../library.db";
// open database file
$handle = sqlite_open($db) or die("Could not open database");
// generate query string
// query contains a deliberate error
$query = "DELETE books WHERE id = 1";
// execute query
$result = sqlite_query($handle, $query) or die("Error in query:
".sqlite_error_string(sqlite_last_error($handle)));
// all done
// close database file
sqlite_close($handle);
?>
Here's what the output looks like:
Note that although they might appear similar, the sqlite_last_error() and
sqlite_error_string() functions don't work in exactly the same way as the mysql_errno()
and mysql_error() functions. The mysql_errno() and mysql_error() functions can be used
independently of each other to retrieve the last error code and message respectively, but the
sqlite_error_string() is dependent on the error code returned by sqlite_last_error().
If your appetite has been whetted, you can read more about the things PHP can do with SQLite in Zend's
PHP 5 In Depth section.
And that's about all I have for you in this tutorial. More secrets await you in Part 10 of PHP 101,
so make sure you come back for that one!
PHP 101 (part 10): A Session In The Cookie
Jar
Vikram Vaswani | 12 comments | Sunday, October 3, 2004
118
Patience Pays
Party Time
The First Session
Remember Me
Rules Of The Game
Meeting Old Friends
Form And Function
Access Granted
Patience Pays
Now that you've used PHP with MySQL and SQLite, you probably think you know everything
you need to get started with PHP programming. In fact, you might even be thinking of cutting
down your visits to Zend.com altogether, giving up this series for something flashier and
cooler...
Uh-uh. Big mistake.
You see, while built-in database support makes programming with PHP easy, it isn't the only
thing that makes PHP so popular. An easy-to-use XML API and new exception handling
mechanism (in PHP 5), support for pluggable modules, and built-in session management are just
some of the many other features that make PHP rock. And all these capabilities are going to be
explored, in depth, right here in this very series, if you can just find it in yourself to hang around
a little longer. So close your eyes, take a deep breath, and read on to find out all about this
tutorial's topic: sessions and cookies.
Party Time
Maybe you heard this at the last party you went to: "HTTP is a stateless protocol, and the
Internet is a stateless development environment".
No? Hmmm. Obviously, you don't go to the right parties.
In simple language, all this means is that HTTP, the HyperText Transfer Protocol that is the
backbone of the Web, is unable to retain a memory of the identity of each client that connects to
a Web site, and therefore treats each request for a Web page as a unique and independent
connection, with no relationship whatsoever to the connections that preceded it. This "stateless
environment" works great so long as you're aimlessly surfing the Web, but it can cause a serious
headache for sites that actually depend on the data accumulated in previous requests. The most
common example is that of an online shopping cart - in a stateless environment, it becomes
119
difficult to keep track of all the items you've shortlisted for purchase as you jump from one
catalog page to another.
Obviously, then, what is required is a method that makes it possible to "maintain state", allowing
client connections to be tracked and connection-specific data to be maintained. And thus came
about cookies, which allow Web sites to store client-specific information on the client system,
and access the information whenever required. A cookie is simply a file, containing a series of
variable-value pairs and linked to a domain. When a client requests a particular domain, the
values in the cookie file are read and imported into the server environment, where a developer
can read, modify and use them for different purposes. A cookie is a convenient way to carry
forward data from one client visit to the next.
Another common approach is to use a session to store connection-specific data; this session data
is preserved on the server for the duration of the visit, and is destroyed on its conclusion.
Sessions work by associating every session with a session ID (a unique identifier for the session)
that is automatically generated by PHP. This session ID is stored in two places: on the client
using a temporary cookie, and on the server in a flat file or a database. By using the session ID to
put a name to every request received, a developer can identify which client initiated which
request, and track and maintain client-specific information in session variables (variable-value
pairs which remain alive for the duration of the session and which can store textual or numeric
information).
Sessions and cookies thus provide an elegant way to bypass the stateless nature of the HTTP
protocol, and are used on many of today's largest sites to track and maintain information for
personal and commercial transactions. Typically, you use a session to store values that are
required over the course of a single visit, and a cookie to store more persistent data that is used
over multiple visits.
PHP has included support for cookies since PHP 3.0, and built-in session management since PHP
4.0. Both these features are enabled by default, so you don't have to do anything special to
activate them. Instead, scroll down and take a look at your first session.
The First Session
One of the standard examples used to demonstrate how a session works is the hit counter
application. This is a simple counter that initializes a variable the first time you visit a Web page,
and increments it each time you reload the page. The counter variable is stored in a session,
which means that if you browse to another site and then return, the last saved value of the
counter will be restored (so long as you didn't destroy the session by shutting down the browser
in the interim).
Take a look at the code:
<?php
// initialize a session
session_start();
120
// increment a session counter
$_SESSION['counter']++;
// print value
echo "You have viewed this page " . $_SESSION['counter'] . " times";
?>
To see how this works, request the script above through your browser a few times. You will
notice that the counter increases by 1 on each subsequent page load. If you open up two browser
windows and request the same page in each one, PHP will maintain and increment individual
session counters for each browser instance. The session ID is used to identify which client made
which request, and recreate the prior saved environment for each individual session. This also
means that if you visit one (or more) other Web sites during the same session and then return to
the script above without shutting down your browser in the interim, your previous session will be
retrieved and recreated for you.
Every session in PHP begins with a call to the session_start() function. This function checks
to see whether a session already exists, and either restores it (if it does) or creates a new one (if it
doesn't). Session variables can then be registered by adding keys and values to the special
$_SESSION superglobal array, and can be accessed at any time during the session using standard
array notation. In the example above, a key named counter has been added to the $_SESSION
array. The first time a session is created, this key will have the value 0. On every subsequent
request for the page during the same session, the previous value of the counter will be retrieved
and incremented by 1.
If the example above doesn't work as advertised, check to make sure that the
session.save_path variable in your php.ini file points to a valid temporary directory for your
system. This value is hard-wired to /tmp by default, so if you're trying the example on a
Windows system, you will need to edit it to C:\Windows\temp (or your system's temporary
directory).
Remember Me
Here's another example, this one asking you to log in and then storing your login name and
session start time as two session variables. This information is then used to display the total
number of minutes the session has been active.
<?php
// initialize a session
session_start();
?>
<html>
<head></head>
<body>
<?php
121
if (!isset($_SESSION['name']) && !isset($_POST['name'])) {
// if no data, print the form
?>
<form action="<?php echo $_SERVER['PHP_SELF']?>" method="post">
<input type="text" name="name">
<input type="submit" name="submit" value="Enter your name">
</form>
<?php
}
else if (!isset($_SESSION['name']) && isset($_POST['name'])) {
// if a session does not exist but the form has been submitted
// check to see if the form has all required values
// create a new session
if (!empty($_POST['name'])) {
$_SESSION['name'] = $_POST['name'];
$_SESSION['start'] = time();
echo "Welcome, " . $_POST['name'] . ". A new session has been
activated for you. Click <a href=" . $_SERVER['PHP_SELF'] . ">here</a> to
refresh the page.";
}
else {
echo "ERROR: Please enter your name!";
}
}
else if (isset($_SESSION['name'])) {
// if a previous session exists
// calculate elapsed time since session start and now
echo "Welcome back, " . $_SESSION['name'] . ". This session was activated
" . round((time() - $_SESSION['start']) / 60) . " minute(s) ago. Click <a
href=" . $_SERVER['PHP_SELF'] . ">here</a> to refresh the page.";
}
?>
</body>
</html>
In this example, the presence or absence of a session variable is used to decide which of the three
possible screens to display. The session start time is also recorded in $_SESSION['start'] with
the time() function, which returns the total number of seconds between January 1 1970 and the
current time. At a later stage, the value stored in $_SESSION['start'] is compared with the
most current value of time() to calculate and display an (approximate) display of elapsed time.
It's important to note that the call to session_start() must appear first, before any output is
generated by the script (assuming you're not using PHP's output buffering feature, which you can
read about at http://www.php.net/manual/en/ref.outcontrol.php). This is because the PHP session
handler internally uses in-memory cookies to store session data, and the cookie creation headers
must be transmitted to the client browser before any output. If you ever see an error like this in
one of your session-enabled pages:
Warning: Cannot send session cache limiter - headers already sent (output
started at ...)
122
it's usually because somewhere, somehow, some output has found its way to the browser before
session_start() was called. Even a carriage return or a blank space outside the PHP tags
surrounding session_start() can cause this error, so watch out for them.
As noted previously, every session has a unique session ID, which PHP uses to keep track of
different clients. This session ID is a long alphanumeric string, which is automatically passed by
PHP from page to page so that the continuity of the session is maintained. To see what it looks
like, use the session_id() function, as in this simple example:
<?php
// initialize a session
session_start();
// print session ID
echo "I'm tracking you with session ID " . session_id();
?>
When the user shuts down the client browser and destroys the session, the $_SESSION array will
be flushed of all session variables. You can also explicitly destroy a session - for example, when
a user logs out - by calling the session_destroy() function, as in the following example:
<?php
// initialize a session
session_start();
// then destroy it
session_destroy();
?>
In case you were wondering if you read that right - yes, before you can call session_destroy()
to destroy a session, you must first call session_start() to recreate it.
Remember that $_SESSION is a superglobal, so you can use it inside and outside functions
without needing to declare it as global first. The following simple example illustrates this:
<?php
// initialize a session
session_start();
// this function checks the value of a session variable
// and returns true or false
function isAdmin() {
if ($_SESSION['name'] == 'admin') {
return true;
}
else {
return false;
123
}
}
// set a value for $_SESSION['name']
$_SESSION['name'] = "guessme";
// call a function which uses a session variable
// returns false here
echo isAdmin()."<br />";
// set a new value for $_SESSION['name']
$_SESSION['name'] = "admin";
// call a function which uses a session variable
// returns true here
echo isAdmin()."<br />";
?>
You can read more about sessions and session handling functions at
http://www.php.net/manual/en/ref.session.php.
Rules Of The Game
A session works by using an in-memory cookie, which explains why it's only active while the
browser instance that created it is active; once the browser instance is terminated, the memory
allocated to that instance is flushed and returned to the system, destroying the session cookie in
the process. If you want longer-lasting cookies, you can use PHP's built-in cookie functions to
write data to the user's disk as a cookie file, and read this data back as and when needed.
Before you start using cookies, there are a few things you should be aware of:
1. Since cookies are used to record information about your activities on a particular domain, they
can only be read by the domain that created them
2. A single domain cannot set more than twenty cookies, and each cookie is limited to a maximum
size of 4 KB
3. A cookie usually possesses six attributes, of which only the first is mandatory. Here they are:
o name: the name of the cookie
o value: the value of the cookie
o expires: the date and time at which the cookie expires
o path: the top-level directory on the domain from which cookie data can be accessed
o domain: the domain for which the cookie is valid
o secure: a Boolean flag indicating whether the cookie should be transmitted only over a
secure HTTP connection
More information on cookies can be obtained from Netscape, the people who originally invented
them. Visit http://www.netscape.com/newsref/std/cookie_spec.html for the Netscape cookie
specification.
It's important to remember that, since cookies are stored on the user's hard drive, you as the
developer have very little control over them. If a user decides to turn off cookie support in his or
124
her browser, your cookies will simply not be saved. Therefore, avoid writing code that depends
heavily on cookies; and have a backup plan ready in case cookie data cannot be retrieved from
the client.
With that caveat out of the way, let's look at some simple cookie-handling code in PHP.
Meeting Old Friends
PHP offers a single function for cookie manipulation: setcookie(). This function allows you to
read and write cookie files, as demonstrated in the following example:
<?php
if (!isset($_COOKIE['visited'])) {
// if a cookie does not exist
// set it
setcookie("visited", "1", mktime()+86400, "/") or die("Could not set
cookie");
echo "This is your first visit here today.";
}
else {
// if a cookie already exists
echo "Nice to see you again, old friend!";
}
?>
To see how this works, request the page above through your browser a couple of times. The first
time around, because no cookie has yet been set, the first message will be displayed. On all
subsequent attempts, because the cookie has already been set, the client will be recognized and
the second message will be displayed. Note that this works even if you terminate the browser
instance, restart it and visit the page again - a marked difference from what happened in the
session examples you saw earlier.
The setcookie() function accepts six arguments: the name of the cookie, its value, its expiry
date, the domain, the path for which it is valid, and a Boolean value indicating its security state.
As noted previously, only the name and value are mandatory, although the example above
specifies both a top-level directory and an expiry date for the cookie (1 day) with the mktime()
function, which works like the time() function described previously.
Cookie values are automatically sent to PHP from the client, and converted to key-value pairs in
the $_COOKIE variable, a superglobal array similar to $_SESSION. Values can then be retrieved
using standard associative array notation, as in the example above. Note that, as with sessions,
calls to setcookie() must take place before any output is generated by the script, or else you'll
see an error like this:
Warning: Cannot add header information - headers already sent by (output
started at ... )
125
Form And Function
Here's another, slightly more complex example:
<?php
if (!isset($_POST['email'])) {
// if form has not been submitted
// display form
// if cookie already exists, pre-fill form field with cookie value
?>
<html>
<head></head>
<body>
<form action="<?php echo $_SERVER['PHP_SELF']?>" method="post">
Enter your email address: <input type="text" name="email"
value="<?php echo $_COOKIE['email']; ?>">
<input type="submit" name="submit">
<?php
// also calculate the time since the last submission
if ($_COOKIE['lastsave']) {
$days = round((time() - $_COOKIE['lastsave']) / 86400);
echo "<br /> $days day(s) since last submission";
}
?>
</form>
</body>
</html>
<?php
}
else {
// if form has been submitted
// set cookies with form value and timestamp
// both cookies expire after 30 days
if (!empty($_POST['email'])) {
setcookie("email", $_POST['email'], mktime()+(86400*30), "/");
setcookie("lastsave", time(), mktime()+(86400*30), "/");
echo "Your email address has been recorded.";
}
else {
echo "ERROR: Please enter your email address!";
}
}
?>
</body>
</html>
In this case, the value entered into the form is stored as a cookie called email, and automatically
retrieved to pre-fill the form field on all subsequent requests. This technique is frequently used
by Web sites that require the user to enter a login name and password; by automatically prefilling the username field in the login box with the value used in the last successful attempt, they
save the user a few keystrokes.
126
This example also demonstrates how you can set more than one cookie for a domain, by calling
setcookie() multiple times. In the example above, the time at which the data was entered is
stored as a second cookie, and used to calculate the time elapsed between successive entries.
To remove a cookie from the client, simply call setcookie() with the same syntax you used to
originally set the cookie, but an expiry date in the past. This will cause the cookie to be removed
from the client system. Here's an example:
<?php
// delete cookie
setcookie("lastsave", NULL, mktime() - 3600, "/");
?>
Read more about cookies and the setcookie() function at
http://www.php.net/manual/en/features.cookies.php and
http://www.php.net/manual/en/function.setcookie.php.
Access Granted
As I said at the beginning of this tutorial, cookies and sessions are two different ways of making
data "persistent" on the client. A session retains data for the duration of the session, while a
cookie retains values for as long as you need it to. With that in mind, let's now look at an
example that uses them both.
The application here is a simple user authentication system, where certain pages can only be
viewed by users who successfully log in to the system. Users who have not been authenticated
with a valid password are denied access to these "special" pages. The list of valid usernames and
passwords is stored in a MySQL database, and PHP is used to verify a user's credentials and
decide whether or not to grant access.
Assuming the MySQL database table looks like this
+-------+-----------------------------------------------+
| name | pass
|
+-------+-----------------------------------------------+
| sue
| 9565d44fd0fe4db59f073eea1db70f3ea258e10b
|
| harry | 6e74234b8b552685113b53c7bff0f386c8cef8cf
|
| louis | 6817dda51b64b8190029490d2811a4d9cb9cd432
|
| sam
| bd17f8243e771a57cfbb06aa9a82bbf09fd2d90b
|
| james | 792ec9b44d432c947ac6775b2b52326e9d08512f
|
+-------+-----------------------------------------------+
with a unique username field and a password field created with the SHA1() function, here's the
PHP script that does all the hard work:
<?php
127
if (isset($_POST['name']) || isset($_POST['pass'])) {
// form submitted
// check for required values
if (empty($_POST['name'])) {
die ("ERROR: Please enter username!");
}
if (empty($_POST['pass'])) {
die ("ERROR: Please enter password!");
}
// set server access variables
$host = "localhost";
$user = "test";
$pass = "test";
$db = "db2";
// open connection
$connection = mysql_connect($host, $user, $pass) or die ("Unable to
connect!");
// select database
mysql_select_db($db) or die ("Unable to select database!");
// create query
$query = "SELECT * FROM users WHERE name = '" . $_POST['name'] . "' AND
pass = SHA1('" . $_POST['pass'] . "')";
// execute query
$result = mysql_query($query) or die ("Error in query: $query. " .
mysql_error());
// see if any rows were returned
if (mysql_num_rows($result) == 1) {
// if a row was returned
// authentication was successful
// create session and set cookie with username
session_start();
$_SESSION['auth'] = 1;
setcookie("username", $_POST['name'], time()+(84600*30));
echo "Access granted!";
}
else {
// no result
// authentication failed
echo "ERROR: Incorrect username or password!";
}
// free result set memory
mysql_free_result($result);
// close connection
mysql_close($connection);
}
else {
// no submission
// display login form
?>
128
<html>
<head></head>
<body>
<center>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
Username <input type="text" name="name" value="<?php echo
$_COOKIE['username']; ?>">
<p />
Password <input type="password" name="pass">
<p />
<input type="submit" name="submit" value="Log In">
</center>
</body>
</html>
<?php
}
?>
Here, the values entered into the login box are integrated into a MySQL SELECT query, which
is executed on the user table. If both username and password match, a single record will be
returned, indicating that authentication succeeded; if they don't, no records will be returned,
indicating that authentication failed.
Assuming authentication succeeds, a session is initialized, the $_SESSION['auth'] key is
created and assigned a value of Boolean true, and the username is stored in a cookie for next
time. The cookie will remain valid for 30 days, and will be used to pre-fill the username field in
the login box on the next login attempt.
Of course, this isn't enough by itself. While the script above performs authentication and
initializes both a session and a cookie if the user's credentials are validated, a security check must
also be carried out on each of the restricted pages. Without this check, any user could bypass the
login screen and simply type in the exact URL to each page to view it.
Since it is clear from the previous script that the session variable $_SESSION['auth'] can only
exist if the user's credentials have been validated, it suffices to check for the presence of the
$_SESSION['auth'] variable at the top of each restricted page, and grant access if that check
returns true. Here's how:
<?php
// start session
session_start();
if (!$_SESSION['auth'] == 1) {
// check if authentication was performed
// else die with error
die ("ERROR: Unauthorized access!");
}
else {
?>
<html>
<head></head>
129
<body>
This is a secure page. You can only see this if $_SESSION['auth'] = 1
</body>
</html>
<?php
}
?>
Pretty neat, huh? Only authenticated users will be able to see this page, because only their clients
will have a session with the $_SESSION['auth'] variable in it. Everyone else will simply see an
error message.
That's about it for this tutorial. In Part Eleven, I'll be telling you all about SimpleXML, the new
XML processing toolkit that comes bundled with PHP 5. Make sure you come back for that!
PHP 101 (part 11): Sinfully Simple
Vikram Vaswani | 10 comments | Sunday, October 3, 2004
Easy Peasy
The Bad Old Days
Petting Zoo
Sin City
The Shape Of Things To Come
X Marks The Spot
An Evening At The Moulin Rouge
Easy Peasy
Unless you've been hiding in a cave for the last few years, you've heard about XML - it's the
toolkit that more and more Web publishers are switching to for content markup. You may even
have seen an XML document in action, complete with user-defined tags and markup, and you
might have wondered how on earth one converts that tangled mess of code into human-readable
content.
The answer is, not easily.
While PHP has included support for the two standard methods of parsing (read: making sense of)
XML - SAX and DOM - since version 4.0, the complexity and inherent geekiness of these
130
methods often turned off all but the most dedicated XML developers. All that has changed,
however, with PHP 5.0, which introduces a brand-spanking-new XML extension named
SimpleXML that takes all (and I do mean all) the pain out of processing XML documents. Keep
reading, and find out how.
The Bad Old Days
In order to understand why SimpleXML is so cool, a brief history lesson is in order.
In the days before SimpleXML, there were two ways of processing XML documents. The first,
SAX or the Simple API for XML, involved traversing an XML document and calling specific
functions as the parser encountered different types of tags. For example, you might have called
one function to process a starting tag, another function to process an ending tag, and a third
function to process the data between them. The second, DOM or the Document Object Model,
involved creating a tree representation of the XML document in memory, and then using treetraversal methods to navigate it. Once a particular node of the tree was reached, the
corresponding content could be retrieved and used.
Neither of these two approaches was particularly user-friendly: SAX required the developer to
custom-craft event handlers for each type of element encountered in an XML file, while the
DOM approach used an object-oriented paradigm which tended to throw developers off, in
addition to being memory-intensive and thus inefficient with large XML documents. In the larger
context also, PHP 4 used a number of different backend libraries for each of its different XML
extensions, leading to inconsistency in the way different XML extensions worked and thus
creating interoperability concerns (as well as a fair amount of confusion for developers).
With PHP 5.0, a concerted effort was made to fix this problem, by adopting the libxml2 library
(http://www.xmlsoft.org/) as the standard library for all XML extensions and by getting the
various XML extensions to operate more consistently. The biggest change in the PHP 5 XML
pantheon, though, is the SimpleXML extension developed by Sterling Hughes, Rob Richards and
Marcus Börger, which attempts to make parsing XML documents significantly more userfriendly than it was in PHP 4.
SimpleXML works by converting an XML document into an object, and then turning the
elements within that document into object properties which can be accessed using standard
object notation. This makes it easy to drill down to an element at any level of the XML hierarchy
to access its content. Repeated elements at the same level of the document tree are represented as
arrays, while custom element collections can be created using XPath location paths (of which,
more later); these collections can then be processed using PHP's standard loop constructs.
Accessing element attributes is as simple as accessing the keys of an associative array - there's
nothing new to learn, and no special code to write.
In order to use SimpleXML and PHP together, your PHP build must include support for
SimpleXML. This support is enabled by default in both the UNIX and Windows versions of PHP
5. Read more about this at http://www.php.net/manual/en/ref.simplexml.php. If you're a PHP 4
user, you're out of luck - SimpleXML is only available for PHP 5.
131
Petting Zoo
To see how SimpleXML works, consider the following XML file:
<?xml version="1.0"?>
<pet>
<name>Polly Parrot</name>
<age>3</age>
<species>parrot</species>
<parents>
<mother>Pia Parrot</mother>
<father>Peter Parrot</father>
</parents>
</pet>
Now, you need a way to get to the content enclosed between the <name>, <age>, <species> and
<parents> elements. With SimpleXML, it's a snap:
<?php
// set name of XML file
$file = "pet.xml";
// load file
$xml = simplexml_load_file($file) or die ("Unable to load XML file!");
// access XML data
echo "Name: " . $xml->name . "\n";
echo "Age: " . $xml->age . "\n";
echo "Species: " . $xml->species . "\n";
echo "Parents: " . $xml->parents->mother . " and " .
"\n";
$xml->parents->father .
?>
The action begins with the simplexml_load_file() function, which accepts the path and name
of the XML file to be parsed. The result of parsing the file is a PHP object, whose properties
correspond to the elements under the root element. The character data within an element can then
be accessed using standard object->property notation, beginning with the root element and
moving down the hierarchical path of the document.
Just as you can read, so also can you write. SimpleXML makes it easy to alter the contents of a
particular XML element - simply assign a new value to the corresponding object property. Here's
an example:
<?php
// set name of XML file
$file = "pet.xml";
// load file
$xml = simplexml_load_file($file) or die ("Unable to load XML file!");
132
// modify XML data
$xml->name = "Sammy Snail";
$xml->age = 4;
$xml->species = "snail";
$xml->parents->mother = "Sue Snail";
$xml->parents->father = "Sid Snail";
// write new data to file
file_put_contents($file, $xml->asXML());
?>
Here, the original XML file is first read in, and then the character data enclosed within each
element is altered by assigning new values to the corresponding object property. The asXML()
method, typically used to dump the XML tree back out to the standard output device, is in this
instance combined with the file_put_contents() function to overwrite the original XML
document with the new data.
Sin City
Repeated elements at the same level of the XML hierarchy are represented as array elements, and
can be accessed using numeric indices. To see how this works, consider the following XML file:
<?xml version="1.0"?>
<sins>
<sin>pride</sin>
<sin>envy</sin>
<sin>anger</sin>
<sin>greed</sin>
<sin>sloth</sin>
<sin>gluttony</sin>
<sin>lust</sin>
</sins>
Here's the PHP script that reads it and retrieves the data from it:
<?php
// set name of XML file
$file = "sins.xml";
// load file
$xml = simplexml_load_file($file) or die ("Unable to load XML file!");
// access each <sin>
echo $xml->sin[0] . "\n";
echo $xml->sin[1] . "\n";
echo $xml->sin[2] . "\n";
echo $xml->sin[3] . "\n";
echo $xml->sin[4] . "\n";
echo $xml->sin[5] . "\n";
echo $xml->sin[6] . "\n";
133
?>
If you'd prefer, you can even iterate over the collection with a foreach() loop, as in this next,
equivalent listing:
<?php
// set name of XML file
$file = "sins.xml";
// load file
$xml = simplexml_load_file($file) or die ("Unable to load XML file!");
// iterate over <sin> element collection
foreach ($xml->sin as $sin) {
echo "$sin\n";
}
?>
The Shape Of Things To Come
SimpleXML handles element attributes as transparently as it does elements and their content.
Attribute-value pairs are represented as members of a PHP associative array, and can be accessed
like regular array elements. To see how this works, take a look at this script:
<?php
// create XML string
$str = <<< XML
<?xml version="1.0"?>
<shapes>
<shape type="circle" radius="2" />
<shape type="rectangle" length="5" width="2" />
<shape type="square" length="7" />
</shapes>
XML;
// load string
$xml = simplexml_load_string($str) or die ("Unable to load XML string!");
// for each shape
// calculate area
foreach ($xml->shape as $shape) {
if ($shape['type'] == "circle") {
$area = pi() * $shape['radius'] * $shape['radius'];
}
elseif ($shape['type'] == "rectangle") {
$area = $shape['length'] * $shape['width'];
}
elseif ($shape['type'] == "square") {
$area = $shape['length'] * $shape['length'];
}
echo $area."\n";
134
}
?>
Unlike previous examples, which used an external XML file, this one creates the XML
dynamically and loads it into SimpleXML with the simplexml_load_string() method. The
XML is then parsed with a foreach() loop, and the area for each shape calculated on the basis
of the value of each <shape> element's type attribute. The listing above demonstrates how
attribute values can be accessed as keys of the attribute array associated with each element
property.
X Marks The Spot
SimpleXML also supports custom element collections, through XPath location paths. For those
of you new to XML, XPath is a standard addressing mechanism for an XML document,
allowing developers to access collections of elements, attributes or text nodes within a document.
Read more about XPath at http://www.w3.org/TR/xpath.html and
http://www.melonfire.com/community/columns/trog/article.php?id=83.
To see how this works, consider the following XML document:
<?xml version="1.0"?>
<ingredients>
<item>
<desc>Boneless chicken breasts</desc>
<quantity>2</quantity>
</item>
<item>
<desc>Chopped onions</desc>
<quantity>2</quantity>
</item>
<item>
<desc>Ginger</desc>
<quantity>1</quantity>
</item>
<item>
<desc>Garlic</desc>
<quantity>1</quantity>
</item>
<item>
<desc>Red chili powder</desc>
<quantity>1</quantity>
</item>
<item>
<desc>Coriander seeds</desc>
<quantity>1</quantity>
</item>
<item>
<desc>Lime juice</desc>
<quantity>2</quantity>
</item>
</ingredients>
135
Now, let's suppose you want to print all the <desc> elements. You could do it by iterating over
the array of <item> elements, as discussed earlier...or you could just create a custom collection
of only the <desc> elements with the xpath() method, and iterate over that instead:
<?php
// set name of XML file
$file = "ingredients.xml";
// load file
$xml = simplexml_load_file($file) or die ("Unable to load XML file!");
// get all the <desc> elements and print
foreach ($xml->xpath('//desc') as $desc) {
echo "$desc\n";
}
?>
Using XPath, you can get even fancier than this - for example, by creating a collection of only
those <desc> elements whose corresponding quantities are two or more.
<?php
// set name of XML file
$file = "ingredients.xml";
// load file
$xml = simplexml_load_file($file) or die ("Unable to load XML file!");
// get all the <desc> elements and print
foreach ($xml->xpath('//item[quantity > 1]/desc') as $desc) {
echo "$desc\n";
}
?>
Without XPath, accomplishing this would be far more complicated than the five lines of code
above...try it for yourself and see!
An Evening At The Moulin Rouge
Now that you've seen what XPath can do, let's wrap this up with an example of how you might
actually use it. Let's suppose you have a bunch of movie reviews marked up in XML, like this:
<?xml version="1.0"?>
<review id="57" category="2">
<title>Moulin Rouge</title>
<teaser>
Baz Luhrmann's over-the-top vision of Paris at the turn of the
century
is witty, sexy...and completely unforgettable
136
</teaser>
<cast>
<person>Nicole Kidman</person>
<person>Ewan McGregor</person>
<person>John Leguizamo</person>
<person>Jim Broadbent</person>
<person>Richard Roxburgh</person>
</cast>
<director>Baz Luhrmann</director>
<duration>120</duration>
<genre>Romance/Comedy</genre>
<year>2001</year>
<body>
A stylishly spectacular extravaganza, Moulin Rouge is hard to
categorize; it is, at different times, a love story, a costume drama,
a musical, and a comedy. Director Baz Luhrmann (well-known for the
very hip William Shakespeare's Romeo + Juliet) has taken some simple
themes - love, jealousy and obsession - and done something completely
new and different with them by setting them to music.
</body>
<rating>5</rating>
</review>
Now, you want to display this review on your Web site. So, you need a PHP script to extract the
data from this file and place it in the appropriate locations in an HTML template. With
everything you've learned so far, this is a snap...as the code below illustrates:
<?php
// set name of XML file
// normally this would come through GET
// it's hard-wired here for simplicity
$file = "57.xml";
// load file
$xml = simplexml_load_file($file) or die ("Unable to load XML file!");
?>
<html>
<head><basefont face="Arial"></head>
<body>
<!-- title and year -->
<h1><?php echo $xml->title; ?> (<?php echo $xml->year; ?>)</h1>
<!-- slug -->
<h3><?php echo $xml->teaser; ?></h3>
<!-- review body -->
<?php echo $xml->body; ?>
<!-- director, cast, duration and rating -->
<p align="right"/>
<font size="-2">
Director: <b><?php echo $xml->director; ?></b>
<br />
Duration: <b><?php echo $xml->duration; ?> min</b>
137
<br />
Cast: <b><?php foreach ($xml->cast->person as $person) { echo "$person "; }
?></b>
<br />
Rating: <b><?php echo $xml->rating; ?></b>
</font>
</body>
</html>
Pretty simple, huh?
That's about all for the moment. In Part Twelve of PHP 101, I'll be telling you all about the new
exception handling model in PHP 5, showing you how you can use it to catch your scripts before
they crash and burn. See you there!
PHP 101 (part 12): Bugging Out - Part 1
Vikram Vaswani | 1 comment | Sunday, January 30, 2005
Fire-Proofing Your Code
Rogues Gallery
Early Warning
Rolling Your Own
PHP 101 (part 12): Bugging Out - Part 2
Fire-Proofing Your Code
Even the best developers make mistakes sometimes. That's why most programming languages including PHP - come with built-in capabilities to catch errors and take remedial action. This
action can be as simple as displaying an error message, or as complex as sending the site
administrator an email with a complete stack trace.
To make it easier to do this, PHP comes with a full-featured error handling API that can be used
to trap and resolve errors. In addition to deciding which types of errors a user sees, you can also
replace the built-in error handling mechanism with your own custom (and usually more creative)
functions. If you're using PHP 5, you get a bonus: a spanking-new exception model, which lets
you wrap your code in Java-like try-catch() blocks for more efficient error handling.
138
In this edition of PHP 101, I'm going to discuss all these things, giving you a crash course in how
to add error-handling to your PHP application. Keep reading - this is pretty cool stuff!
Rogues Gallery
Before we get into the nitty-gritty of how to write an error handler, you need to know a little
theory.
Normally, when a PHP script encounters an error, it displays a message indicating the cause of
the error and may also (depending on how serious the error is) terminate script execution. Now,
while this behaviour is acceptable during the development phase, it cannot continue once a PHP
application has been released to actual users. In "live" situations, it is unprofessional to display
cryptic error messages (which are usually incomprehensible to non-technical users); it is more
professional to intercept these errors and either resolve them (if resolution is possible), or notify
the user with an easily-understood error message (if not).
There are three basic types of runtime errors in PHP:
1. Notices: These are trivial, non-critical errors that PHP encounters while executing a script - for
example, accessing a variable that has not yet been defined. By default, such errors are not
displayed to the user at all - although, as you will see, you can change this default behaviour.
2. Warnings: These are more serious errors - for example, attempting to include() a file which
does not exist. By default, these errors are displayed to the user, but they do not result in script
termination.
3. Fatal errors: These are critical errors - for example, instantiating an object of a non-existent
class, or calling a non-existent function. These errors cause the immediate termination of the
script, and PHP's default behaviour is to display them to the user when they take place.
It should be noted that a syntax error in a PHP script - for example, a missing brace or semicolon - is treated as a fatal error and results in script termination. That's why, if you forget a
semi-colon at the end of one of your PHP statements, PHP will refuse to execute your script until
you correct the mistake.
PHP errors can be generated by the Zend engine, by PHP built-in functions, or by user-defined
functions. They may occur at startup, at parse-time, at compile-time or at run-time. Internally,
these variations are represented by twelve different error types (as of PHP 5), and you can read
about them at http://www.php.net/manual/en/ref.errorfunc.php. Named constants like E_NOTICE
and E_USER_ERROR provide a convenient way to reference the different error types.
A quick tip here: most of the time, you'll be worrying about run-time errors (E_NOTICE,
E_WARNING and E_ERROR) and user-triggered errors (E_USER_NOTICE, E_USER_WARNING and
E_USER_ERROR). During the debug phase, you can use the shortcut E_ALL type to see all fatal and
non-fatal errors generated by your script, and in PHP 5 you might also want to use the new
E_STRICT error type to view errors that affect the forward compatibility of your code.
139
Early Warning
With the theory out of the way, let's now apply it to some examples. Consider the following code
snippet:
<?php
// initialize the $string variable
$string = 'a string';
// explode() a string
// this will generate a warning or E_WARNING because the number of arguments
to explode() is incorrect
explode($string);
?>
If you run this script, you'll get a non-fatal error (E_WARNING), which means that if you had
statements following the call to explode(), they would still get executed. Try it for yourself and
see!
To generate a fatal error, you need to put in a bit more work. Take a look at this:
<?php
// call a non-existent function
// this will generate a fatal error (E_ERROR)
callMeJoe();
?>
Here, the call to a non-existent function trips all of PHP's alarm wires and generates a fatal error,
which immediately stops script execution.
Now, here's the interesting bit. You can control which errors are displayed to the user, by using a
built-in PHP function called error_reporting(). This function accepts a named constant, and
tells the script to report only errors that match that type. To see this in action, consider the
following rewrite of one of the earlier scripts to "hide" non-fatal errors:
<?php
// report only fatal errors
error_reporting(E_ERROR);
// initialize the $string variable
$string = 'string';
// attempt to explode() a string
// this will not generate a warning because only fatal errors are reported
explode($string);
140
?>
In this case, when the script executes, no warning will be generated even though the call to
explode() contains one less argument than it should.
You can use a similar technique to turn off the display of fatal errors:
<?php
// report no fatal errors
error_reporting(~E_ERROR);
// call a non-existent function
callMeJoe();
?>
Keep in mind, though, that just because the error isn't being reported doesn't mean it isn't
occurring. Although the script above will not display a visible error message, script execution
will still stop at the point of error and statements subsequent to that point will not be executed.
error_reporting() gives you control over which errors are displayed; it doesn't prevent the
errors themselves.
Note that there are further settings within php.ini that should be used on production sites. You
can (and should) turn off display_errors, stipulate an error_log file and switch on
log_errors.
Note also that the approach used above to hide error messages, although extremely simple, is not
recommended for general use. It is poor programming practice to trap all errors, regardless of
type, and ignore them; it is far better - and more professional - to anticipate the likely errors
ahead of time, and write defensive code that watches for them and handles them appropriately.
This will prevent your users from finding themselves staring at an unexplained blank page when
something goes wrong.
Rolling Your Own
With this in mind, let's talk a little bit about changing the way errors are handled. Consider a
typical PHP error message: it lists the error type, a descriptive message, and the name of the
script that generated the error. Most of the time, this is more than sufficient... but what if your
boss is a demanding customer, and insists that there must be a "better way"?
Well, there is. It's a little function called set_error_handler(), and it allows you to divert all
PHP errors to a custom function that you've defined, instead of sending them to the default
handler. This custom function must be capable of accepting a minimum of two mandatory
arguments (the error type and corresponding descriptive message) and up to three additional
arguments (the file name and line number where the error occurred and a dump of the variable
space at the time of error).
141
The following example might make this clearer:
<?php
// define a custom error handler
set_error_handler('oops');
// initialize the $string variable
$string = 'a string';
// explode() a string
// this will generate a warning because the number of arguments to explode()
is incorrect
// the error will be caught by the custom error handler
explode($string);
// custom error handler
function oops($type, $msg, $file, $line, $context) {
echo "<h1>Error!</h1>";
echo "An error occurred while executing this script. Please contact the
<a href=mailto:webmaster@somedomain.com>webmaster</a> to report this error.";
echo "<p />";
echo "Here is the information provided by the script:";
echo "<hr><pre>";
echo "Error code: $type<br />";
echo "Error message: $msg<br />";
echo "Script name and line number of error: $file:$line<br />";
$variable_state = array_pop($context);
echo "Variable state when error occurred: ";
print_r($variable_state);
echo "</pre><hr>";
}
?>
The set_error_handler() function tells the script that all errors are to be routed to my userdefined oops() function. This function is set up to accept five arguments: error type, message,
file name, line number, and an array containing a lot of information about the context that the
error occurred in (including server and platform, as well as script information). The final element
of the context array contains the current value of the guilty variable. These arguments are then
used to create an error page that is friendlier and more informative than PHP's standard one-line
error message.
You can use this custom error handler to alter the error message the user sees, on the basis of the
error type. Take a look at the next example, which demonstrates this technique:
<?php
// define a custom error handler
set_error_handler('oops');
// initialize $string variable
$string = 'a string';
142
// this will generate a warning
explode($string);
// custom error handler
function oops($type, $msg, $file, $line, $context) {
switch ($type) {
// notices
case E_NOTICE:
// do nothing
break;
// warnings
case E_WARNING:
// report error
print "Non-fatal error on line $line of $file: $msg <br />";
break;
// other
default:
print "Error of type $type on line $line of $file: $msg <br />";
break;
}
}
?>
Note that certain error types can't be handled in this way. For example, a fatal E_ERROR will
prevent the PHP script from continuing, therefore it can never reach a user-created errorhandling mechanism. See http://www.php.net/set-error-handler for more information on this.
PHP 101 (PART 12): BUGGING OUT PART 2
Vikram Vaswani | 2 comments | Sunday, January 30, 2005
PHP 101 (PART 12): BUGGING OUT - PART 1
Pulling the Trigger
Catching Up
Pulling the Trigger
143
So far we've been talking about handling errors generated by PHP itself, but why stop there?
PHP allows you to use its built-in error handling system to raise your own custom errors as well.
This is accomplished via a function named trigger_error(), which allows you to raise any of
the three error types reserved for users: E_USER_NOTICE, E_USER_WARNING and E_USER_ERROR.
When these errors are triggered, PHP's built-in handler will automatically wake up to handle
them.
<?php
// function to test a number
// generates E_USER_WARNING if number is a float
// generates E_USER_ERROR is number is negative
function testNumber($num) {
// float
// trigger a warning
if (is_float($num)) {
trigger_error("Number $num is not an integer", E_USER_WARNING);
}
// negative
// trigger a fatal error
if ($num < 0) {
trigger_error("Number $num is negative", E_USER_ERROR);
}
}
// test the function with different values
testNumber(100);
testNumber(5.6);
testNumber(-8);
?>
If you'd like to have a custom error handler to handle your custom errors... well, you're just hard
to please, aren't you? Take a look at this next example, which rewrites the previous script to use
a user-defined error handler:
<?php
// function to test a number
// generates E_USER_WARNING if number is a float
// generates E_USER_ERROR is number is negative
function testNumber($num) {
// float
// trigger a warning
if (is_float($num)) {
trigger_error("Number $num is not an integer", E_USER_WARNING);
}
// negative
// trigger a fatal error
if ($num < 0) {
trigger_error("Number $num is negative", E_USER_ERROR);
144
}
}
// custom error handler
function myErrorHandler($type, $msg, $file, $line, $context) {
switch ($type) {
// warnings
case E_USER_WARNING:
// report error
print "Non-fatal error on line $line of $file: $msg <br />";
break;
// fatals
case E_USER_ERROR:
// report error and die()
die("Fatal error on line $line of $file: $msg <br />");
break;
// notices
default:
// do nothing
break;
}
}
// set the name of the custom handler
set_error_handler('myErrorHandler');
// test the function with different values
testNumber(100);
testNumber(5.6);
testNumber(-8);
?>
Note that it is the responsibility of the custom handler to die() in the event of user-generated
fatal errors - PHP will not do this automatically.
You can use the same method to deal with exceptions too. Scroll on down, and let me show you
how.
Catching Up
If you're using PHP 5, you also have an alternative to the techniques discussed so far in the new
Exception model (exception is Geek for error). Exceptions are new to PHP (although they've
been in languages like Java and Python for ages) and they're stirring up a great deal of
excitement.
In the exception-based approach, program code is wrapped in a try() block, and exceptions
generated by it are "caught" and resolved by a catch() block. Multiple catch() blocks are
145
possible, each one dealing with a different error type; this allows developers to trap different
types of errors and execute appropriate exception-handling.
Here's what a typical try-catch() block looks like:
try {
execute this block
}
catch (exception type 1) {
execute this block to resolve exception type 1
}
catch (exception type 2) {
execute this block to resolve exception type 2
}
... and so on ...
When PHP encounters code wrapped within a try-catch() block, it first attempts to execute the
code within the try() block. If this code is processed without any exceptions being generated,
control transfers to the lines following the try-catch() block. However, if an exception is
generated while running the code within the try() block, PHP stops execution of the block at
that point and begins checking each catch() block to see if there is a handler for the exception.
If a handler is found, the code within the appropriate catch() block is executed; if not, a fatal
error is generated. It is even possible to handle that fatal error in a nice way using exceptions; see
http://www.php.net/set-exception-handler for more on this.
The exceptions themselves are generated via PHP's throw statement. The throw statement needs
to be passed a descriptive message, and an optional error code. When the exception is raised, this
description and code will be made available to the exception handler.
Wanna see how this works? Here's an example:
<?php
// PHP 5
error_reporting(0);
// try this code
try {
$file = 'somefile.txt';
// open file
if (!$fh = fopen($file, 'r')) {
throw new Exception('Could not open file!');
}
// read file contents
if (!$data = fread($fh, filesize($file))) {
throw new Exception('Could not read file!');
}
// close file
fclose($fh);
146
// print file contents
echo $data;
}
// catch errors if any
catch (Exception $e) {
print 'Something bad just happened...';
}
?>
If the file doesn't exist or is unreadable, the throw statement will generate an exception
(basically, an instance of PHP's built-in Exception object) and pass it a message describing the
error. When such an exception is generated, control passes to the first catch() block. If the
catch() block can handle the exception type, the code within the catch() block is executed. If
the first catch() block cannot handle the generated exception, control passes to the next one.
Don't worry too much about "exception types" at this point - all will be explained shortly. For the
moment, all you need to know is that the generic catch() block above will catch all exceptions,
regardless of type.
Now, there's one problem with the previous listing. Although the catch() block will trap the
exception and print a message, it can't display the descriptive message sent by the throw
statement with the exception. To access this message, as well as a couple of other interesting
pieces of information, it is necessary to use some of the Exception object's built-in methods.
Take a look at this revision of the previous script, which illustrates:
<?php
// PHP 5
error_reporting(0);
// try this code
try {
$file = 'somefile.txt';
// open file
if (!$fh = fopen($file, 'r')) {
throw new Exception('Could not open file!', 12);
}
// read file contents
if (!$data = fread($fh, filesize($file))) {
throw new Exception('Could not read file!', 9);
}
// close file
fclose($fh);
// print file contents
echo $data;
}
147
// catch errors if any
catch (Exception $e) {
print '<h2>Exception</h2>';
print 'Error message: ' . $e->getMessage() . '<br />';
print 'Error code: ' . $e->getCode() . '<br />';
print 'File and line: ' . $e->getFile() . '(' . $e->getLine() . ')<br
/>';
print 'Trace: ' . $e->getTraceAsString() . '<br />';
}
?>
When you run this script, you'll see that the message generated by the exception handler
contains:




the descriptive data sent by throw,
an error code (also sent by throw),
the file name and line number where the exception occurred, and
a stack trace indicating the exception's progress through the class hierarchy, if there is one.
This data is generated by calling the Exception object's getMessage(), getCode(), getFile(),
getLine() and getTraceAsString() methods respectively inside the catch() block.
Adding Some Class
You can handle different exceptions in different ways, by sub-classing the generic Exception
object and using more than one catch() block. The following example is a simple illustration of
this:
<?php
// PHP 5
// sub-class the Exception class
class NegativeNumException extends Exception {}
class OutOfRangeException extends Exception {}
class FloatException extends Exception {}
// function to test a number
function testNumber($num) {
// float
// trigger an exception
if (is_float($num)) {
throw new FloatException($num);
}
// negative
// trigger an exception
if ($num < 0) {
throw new NegativeNumException($num);
}
// out of range
148
// trigger an exception
if ($num > 1000 || $num < 100) {
throw new OutOfRangeException($num);
}
}
// try this code
try {
testNumber(-19);
}
// catch errors, if any
catch (NegativeNumException $e) {
print 'A negative number was provided ('.$e->getMessage().'). Please
provide a positive integer between 100 and 1000.<br />';
}
catch (OutOfRangeException $e) {
print 'The number provided is out of range ('.$e->getMessage().'). Please
provide a positive integer between 100 and 1000.<br />';
}
catch (FloatException $e) {
print 'The number provided is not an integer ('.$e->getMessage().').
Please provide a positive integer between 100 and 1000.<br />';
}
catch (Exception $e) {
print 'Error message: ' . $e->getMessage() . '<br />';
print 'Error code: ' . $e->getCode() . '<br />';
print 'File and line: ' . $e->getFile() . '(' . $e->getLine() . ')<br
/>';
print 'Trace: ' . $e->getTraceAsString() . '<br />';
}
?>
In this case, I've created three new Exception sub-classes from the base object, one for each
possible error. Next, I've set up catch() blocks for each exception type, and written exceptionhandling code that is specific to each type. Depending on which exception occurs (you can
generate different ones by sending the testNumber() function different values), the appropriate
catch() block will be invoked and a different error message will be printed.
Note that because PHP will always use the first catch() block that matches the exception type,
and because the generic Exception class matches all exceptions, the catch() blocks must be
arranged in the order of most specific first. This has been done in the example above, where the
generic catch() block appears last on the list.
Here's another example, this one illustrating a more useful application - using the exception
model in a user authentication class to provide easy-to-understand error handling. Take a look:
<?php
// PHP 5
// class definition
class userAuth {
149
// define properties
private $username;
private $passwd;
private $passwdFile;
// constructor
// must be passed username and non-encrypted password
public function __construct($username, $password) {
$this->username = $username;
$this->passwd = $password;
}
// set .htaccess-style file to check for passwords
public function setPasswdFile($file) {
$this->passwdFile = $file;
}
// perform password verification
public function authenticateUser() {
// check that the file exists
if (!file_exists($this->passwdFile)) {
throw new FileException("Password file cannot be found: " .
$this->passwdFile);
}
// check that the file is readable
if (!is_readable($this->passwdFile)) {
throw new FileException("Unable to read password file: ". $this>passwdFile);
}
// read file
$data = file($this->passwdFile);
// iterate through file
foreach ($data as $line) {
$arr = explode(":", $line);
// if username matches, test password
if ($arr[0] == $this->username) {
// get salt and crypt(), assuming encryption
$salt = substr($arr[1], 0, 2);
// if match, user/pass combination is correct
if ($arr[1] == crypt($this->passwd, $salt)) {
echo "User was authenticated";
// do some other stuff
}
// otherwise return exception
else {
throw new AuthException("Incorrect password");
break;
}
}
else {
// could not find a username match
// return exception
throw new AuthException("No such user");
150
}
}
}
// end class definition
}
// subclass exceptions
class FileException extends Exception {};
class AuthException extends Exception {};
// try the code
try {
// create instance
$ua = new userAuth("joe", "secret");
// set password file
$ua->setPasswdFile("password.txt");
// perform authentication
$ua->authenticateUser();
}
// catch authentication failures, if any
catch (FileException $e) {
// print file errors
print "A file error occurred. ".$e->getMessage();
}
catch (AuthException $e) {
// an authentication error occurred
print "An authentication error occurred. ".$e->getMessage();
// more normally, redirect to new page on auth errors, e.g.
// header ('Location: login_fail.php');
}
catch (Exception $e) {
print "An unknown error occurred";
}
?>
Here, depending on the type of error, either a FileException() or an AuthException() will be
generated - and handled by the corresponding catch() block. Notice how easy the exception
handling framework is to read and extend. It's precisely this ease of use and extensibility that
helps the new PHP 5 model score over the earlier, more primitive techniques of handling
application errors.
Well, that's about it for the moment. Come back soon, for more PHP 101!
151
PHP 101 (part 13): The Trashman Cometh Part 1
Vikram Vaswani | 1 comment | Sunday, February 27, 2005
Waiting to Exhale
An Empty Vessel...
Not My Type
The Dating Game
PHP 101 (part 13): The Trashman Cometh - Part 2
Waiting to Exhale
Maybe you've heard the term GIGO before.
If you haven't, it stands for Garbage In, Garbage Out, and it's a basic fact of computer
programming: if you feed your program bad input, you're almost certainly going to get bad
output. And no matter which way you cut it, bad output is not a Good Thing for a programmer
who wants to get noticed.
In case you think I'm exaggerating, let me give you a simple example. Consider an online loan
calculator that allows a user to input a desired loan amount, finance term and interest rate. Let's
assume that the application doesn't include any error checks, and that the user decides to enter
that magic number, 0, into the Term field.
You can imagine the result. After a few internal calculations the application will end up
attempting to divide the total amount payable by zero. The slew of ugly error messages that
follow don't really bear discussion, but it's worth noting that they could have been avoided had
the developer had the foresight to include an input validation routine when designing the
application.
The moral of this story? If you're serious about using PHP for web development, one of the most
important things you must learn is how to validate user input and deal with potentially unsafe
data. Such input verification is one of the most important safeguards a developer can build into
an application, and a failure to do this can snowball into serious problems, or even cause your
application to break when it encounters invalid or corrupt data.
152
That's where this edition of PHP 101 comes in. Over the next few paragraphs, I'm going to show
you some basic tricks to validate user input, catch "bad" data before it corrupts your calculations
and databases, and provide user notification in a gentle, understandable and non-threatening way.
To prepare for this exercise, I suggest you spin up a CD of John Lennon singing 'Imagine', fill
your heart with peace and goodwill towards all men, and take a few deep, calming breaths. Once
you've exhaled, we can get going.
An Empty Vessel...
This tutorial assumes that the user input to be validated arrives through a web form. This is not
the only way a PHP script can get user data; however, it is the most common way. If your PHP
application needs to validate command-line input, I'd recommend you read my article on the
PEAR Console_Getopt class, available for your perusal at
http://www.zend.com/pear/tutorials/Console-Getopt.php.
It's common practice to use client-side scripting languages such as JavaScript or VBScript for
client-side form validation. However, this type of client-side validation is not foolproof. You're
not in control of the client, so if a user turns off JavaScript in his or her browser, all your efforts
to ensure that the user does not enter irrelevant data become - well - irrelevant. That's why most
experienced developers use both client-side and server-side validation. Server-side validation
involves checking the values submitted to the server through a PHP script, and taking
appropriate action when the input is incorrect.
Let's begin with the most commonly found input error: a required form field that is missing its
value. Take a look at this example:
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
Which sandwich filling would you like?
<br />
<input type = 'text' name = 'filling'>
<br />
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// set database variables
$host = 'localhost';
$user = 'user';
$pass = 'secret';
$db = 'sandwiches';
// get user input
$filling = mysql_escape_string($_POST['filling']);
153
// open connection
$connection = mysql_connect($host, $user, $pass) or die('Unable to
connect!');
// select database
mysql_select_db($db) or die('Unable to select database!');
// create query
$query = 'INSERT INTO orders (filling) VALUES ("$filling")';
// execute query
$result = mysql_query($query) or die("Error in query: $query.
".mysql_error());
// close connection
mysql_close($connection);
// display message
echo "Your {$_POST['filling']} sandwich is coming right up!";
}
?>
</body>
</html>
It's clear from the example above that submitting the form without entering any data will result
in an empty record being added to the database (assuming no NOT NULL constraints on the target
table). To avoid this, it's important to verify that the form does, in fact, contain valid data, and
only then perform the INSERT query. Here's how:
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
Which sandwich filling would you like?
<br />
<input type = 'text' name = 'filling'>
<br />
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// check for required data
// die if absent
if (!isset($_POST['filling']) || trim($_POST['filling']) == '') {
die("ERROR: You can't have a sandwich without a filling!");
}
else {
$filling = mysql_escape_string(trim($_POST['filling']));
}
154
// set database variables
$host = 'localhost';
$user = 'user';
$pass = 'secret';
$db = 'sandwiches';
// open connection
$connection = mysql_connect($host, $user, $pass) or die('Unable to
connect!');
// select database
mysql_select_db($db) or die('Unable to select database!');
// create query
$query = 'INSERT INTO orders (filling) VALUES ("$filling")';
// execute query
$result = mysql_query($query) or die("Error in query: $query.
".mysql_error());
// close connection
mysql_close($connection);
// display message
echo "Your {$_POST['filling']} sandwich is coming right up!";
}
?>
</body>
</html>
The error check here is both simple and logical: the trim() function is used to trim leading and
trailing spaces from the field value, which is then compared with an empty string. If the match is
true, the field was submitted empty, and the script dies with an error message before MySQL
comes into the picture.
A common mistake, especially among newbies, is to replace the isset() and trim()
combination with a call to PHP's empty() function, which tells you if a variable is empty. This
isn't usually a good idea, because empty() has a fatal flaw: it'll return true even if a variable
contains the number 0. The following simple example illustrates this:
<?php
// no data, returns empty
$data = '';
echo empty($data) ? "$data is empty" : "$data is not empty";
echo "<br />\n";
// some data, returns not empty
$data = '1';
echo empty($data) ? "$data is empty" : "$data is not empty";
echo "<br />\n";
// some data, returns empty
$data = '0';
155
echo empty($data) ? "$data is empty" : "$data is not empty";
?>
So, if your form field is only allowed to hold non-empty, non-zero data, empty() is a good
choice for validating it. But if the range of valid values for your field includes the number 0,
stick with the isset() and trim() combination instead.
Not My Type
So now you know how to catch the most basic error - missing data - and stop script processing
before any damage takes place. But what if the data's present, but of the wrong type or size?
Your 'missing values' test won't be triggered, but your calculations and database could still be
affected. Obviously, then, you need to add a further layer of security, wherein the data type of
the user input is also verified.
Here's an example which illustrates:
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF']?>' method = 'post'>
How many sandwiches would you like? (min 1, max 9)
<br />
<input type = 'text' name = 'quantity'>
<br />
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// check for required data
// die if absent
if (!isset($_POST['quantity']) || trim($_POST['quantity']) == '') {
die ("ERROR: Can't make 'em if you don't say how many!");
}
// check if input is a number
if (!is_numeric($_POST['quantity'])) {
die ("ERROR: Whatever you just said isn't a number!");
}
// check if input is an integer
if (intval($_POST['quantity']) != $_POST['quantity']) {
die ("ERROR: Can't do halves, quarters or thirds... I'd lose my
job!");
}
// check if input is in the range 1-9
if (($_POST['quantity'] < 1) || ($_POST['quantity'] > 9)) {
156
die ('ERROR: I can only make between 1 and 9 sandwiches per order!');
}
// process the data
echo "I'm making you {$_POST['quantity']} sandwiches. Hope you can eat
them all!";
}
?>
</body>
</html>
Notice that once I've established that the field contains some data, I've added a bunch of tests to
make sure it meets data type and range constraints. First, I've checked if the value is numeric,
with the is_numeric() function. This function tests a string to see if it is a numeric string - that
is, a string consisting only of numbers.
Assuming what you've got is a number, the next step is to make sure it's an integer value between
1 and 9. To test if it's an integer, I've used the intval() function to extract the integer part of the
value, and tested it against the value itself. Float values (such as 2.5) will fail this test; integer
values will pass it. The final step before green-lighting the value is to see if it falls between 1 and
9. This is easy to accomplish with a couple of inequality tests.
Whilst on the topic, it's also worth mentioning the strlen() function, which returns the length
of a string. This can come in handy to make sure that form input doesn't exceed a particular
length. The following example shows how:
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF']?>' method = 'post'>
Enter a nickname 6-10 characters long:
<br />
<input type = 'text' name = 'nick'>
<br />
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// check for required data
// die if absent
if (!isset($_POST['nick']) || trim($_POST['nick']) == '') {
die ('ERROR: Come on, surely you can think of a nickname! How about
Pooky?');
}
// check if input is of the right length
if (!(strlen($_POST['nick']) >= 6 && strlen($_POST['nick']) <= 10)) {
die ("ERROR: That's either too long or too short!");
}
157
// process the data
echo "I'll accept the nickname {$_POST['nick']}, seeing as it's you!";
}
?>
</body>
</html>
Here, the strlen() function is used to verify that the string input is neither too long nor too
short. It's also a handy way to make sure that input data satisfies the field length constraints of
your database. For example, if you have a MySQL VARCHAR(10) field, strings over 10 characters
in length will be truncated. The strlen() function can serve as an early warning system in such
cases, notifying the user of the length mismatch and avoiding data corruption.
The Dating Game
Validating dates is another important aspect of input validation. It's all too easy, given a series
of drop-down list boxes or free-form text fields, for a user to select a date like 29-Feb-2005 or
31-Apr-2005, neither of which is valid. Therefore, it's important to check that date values
provided by the user are valid before using them in a calculation.
In PHP, this task is significantly simpler than in other languages, because of the checkdate()
function. This function accepts three arguments - month, day and year - and returns a Boolean
value indicating whether or not the date is valid. The following example demonstrates it in
action:
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF']?>' method = 'post'>
Enter your date of birth:
<br /><br />
<select name = 'day'>
<?php
// generate day numbers
for ($x = 1; $x <= 31; $x++) {
echo "<option value = $x>$x</option>";
}
?>
</select>
<select name = 'month'>
<?php
// generate month names
for ($x = 1; $x <= 12; $x++) {
echo "<option value=$x>".date('F', mktime(0, 0, 0, $x, 1,
1)).'</option>';
}
?>
</select>
158
<select name = 'year'>
<?php
// generate year values
for ($x = 1950; $x <= 2005; $x++) {
echo "<option value=$x>$x</option>";
}
?>
</select>
<br /><br />
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// check if date is valid
if (!checkdate($_POST['month'], $_POST['day'], $_POST['year'])) {
die("ERROR: The date {$_POST['day']}-{$_POST['month']}{$_POST['year']} doesn't exist!");
}
// process the data
echo "You entered {$_POST['day']}-{$_POST['month']}-{$_POST['year']} which is a valid date.";
}
?>
</body>
</html>
Try entering an invalid date, and see how PHP calls you on it. Ain't that cool?
If you're storing date input in a MySQL table, it's interesting to note that MySQL does not
perform any rigorous date verification of its own before accepting a DATE, DATETIME or
TIMESTAMP value. Instead, it expects the developer to build date verification into the application
itself. The most that MySQL will do, if it encounters an obviously illegal value, is convert the
date to a zero value - not very helpful at all! Read more about this at
http://dev.mysql.com/doc/mysql/en/datetime.html.
While we're on the topic, let's talk a little bit more about multiple-choice form elements like
drop-down list boxes and radio buttons. In cases where it's mandatory to make a choice, a
developer must verify that at least one of the available options has been selected by the user. This
mainly involves clever use of the isset() and - for multi-select list boxes - the is_array() and
sizeof() functions. The next example illustrates this:
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
Pizza base:
<br />
<input type = 'radio' name = 'base' value = 'thin and crispy'>Thin and
159
crispy
<input type = 'radio' name = 'base' value = 'deep-dish'>Deep-dish
<br />
Cheese:
<br />
<select name = 'cheese'>
<option value = 'mozzarella'>Mozzarella</option>
<option value = 'parmesan'>Parmesan</option>
<option value = 'gruyere'>Gruyere</option>
</select>
<br />
Toppings:
<br />
<select multiple name = 'toppings[]'>
<option value = 'tomatoes'>Tomatoes</option>
<option value = 'olives'>Olives</option>
<option value = 'pepperoni'>Pepperoni</option>
<option value = 'onions'>Onions</option>
<option value = 'peppers'>Peppers</option>
<option value = 'sausage'>Sausage</option>
<option value = 'anchovies'>Anchovies</option>
</select>
<br />
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// check radio button
if (!isset($_POST['base'])) {
die('You must select a base for the pizza');
}
// check list box
if (!isset($_POST['cheese'])) {
die('You must select a cheese for the pizza');
}
// check multi-select box
if (!is_array($_POST['toppings']) || sizeof($_POST['toppings']) < 1) {
die('You must select at least one topping for the pizza');
}
// process the data
echo "One {$_POST['base']} {$_POST['cheese']} pizza with ";
foreach ($_POST['toppings'] as $topping) echo $topping.", ";
echo "coming up!";
}
?>
</body>
</html>
Nothing to tax your brain too much here - the isset() function merely checks to see if at least
one of a set of options has been selected, and prints an error message if this is not the case.
Notice how the multi-select list box is validated: when the form is submitted, selections made
160
here are placed in an array, and PHP's is_array() and sizeof() functions are used to test that
array and ensure that it contains at least one element.
PHP 101 (part 13): The Trashman Cometh Part 2
Vikram Vaswani | 1 comment | Sunday, February 27, 2005
PHP 101 (part 13): The Trashman Cometh - Part 1
A Regular Guy
A Pattern Emerges
Back to Class
A Regular Guy
So far, the validation routines have been fairly simple- checking dates, checking for required
values, and checking data type or size. Often, however, you need more sophisticated validation for example, to test whether an email address or telephone number is written in the correct
format. To accomplish these more complex validation tasks, clever PHP programmers turn to
regular expressions.
Regular expressions, aka regex, are a powerful tool for pattern matching and substitution.
They are commonly associated with almost all UNIX-based tools, including editors like vi,
scripting languages like Perl and PHP, and shell programs like awk and sed. You'll even find
them in client-side scripting languages like JavaScript. Kinda like Madonna, their popularity cuts
across languages and territorial boundaries.
A regular expression lets you build patterns using a set of special characters. These patterns can
then be compared with text in a file, data entered into an application, or input from a form filled
in by users on a web site. Depending on whether or not there's a match, appropriate program
code can be executed. Regular expressions thus play an important role in the decision-making
routines of web applications.
A regular expression can be as simple as this:
161
/love/
All this does is match the pattern love in the text it's applied to. Like many other things in life, it's
simpler to get your mind around the pattern than the concept - but that's neither here nor there.
How about something a little more complex? The pattern /fo+/ would match the words fool,
footsie and four-seater. Try it:
<?php
$array = array('fool', 'footsie', 'four-seater');
foreach ($array as $element) {
if (preg_match('/fo+/', $element)) echo "$element gives a match<br />\n";
}
?>
And although it's a pretty silly example, you have to admit it's realistic - after all, who but fools
in love would play footsie in a four-seater?
The + symbol used in the expression is called a metacharacter - a character that has a special
meaning when used within a pattern. The + metacharacter is used to match one or more
occurrences of the preceding character - in the example above, that would be the letter f
followed by one or more occurrences of the letter o.
Similar to the + metacharacter are * and ?, which are used to match zero or more occurrences
of the preceding character, and zero or one occurrence of the preceding character,
respectively. So /ab*/ would match aggressive, absolutely and abbey, while /Ron?/ would
match Ronald, Roger and Roland, though not Rimbaud or Mona.
In case all this seems a little too imprecise, you can also specify a range for the number of
matches. For example, the regular expression /ron{2,6}/ would match ronny and ronnnnnny!,
but not ron. The numbers in the curly braces represent the lower and upper values of the range to
match; you can leave out the upper limit for an open-ended range match.
Just as you can specify a range for the number of characters to be matched, you can also specify
a range of characters. For example, the range /[A-Z]/ would match any string containing an
upper-case alphabetic character, while /[a-z]/ would match any lowercase letters, and /[0-9]/
would match all numbers between 0 and 9.
Using these three character ranges, it's pretty easy to create a regular expression to match an
ordered alphanumeric field: /([a-z][A-Z][0-9])+/ would match an alphanumeric string
given the same character type order, such as aB2, but not abc. Note the parentheses around the
patterns - contrary to what you might think, these are not there purely to confuse you; they come
in handy when grouping sections of a regular expression together.
162
Of course, this is just the tip of the regular expression iceberg. There are many more
metacharacters, and innumerable ways in which they can be combined to create powerful
pattern-matching rules. For an in-depth introduction, take a look at
http://www.melonfire.com/community/columns/trog/article.php?id=2, the reference pages at
http://it.metr.ou.edu/regex/, and the PHP manual pages at
http://www.php.net/manual/en/ref.regex.php and http://www.php.net/manual/en/ref.pcre.php.
You can find a bunch of sample regular expressions for all manner of applications at
http://www.regexlib.com/.
A Pattern Emerges
In PHP, regular expression matching takes place with the ereg() or preg_match() functions
(ereg() also comes in a case-insensitive version called eregi()). These functions, which differ
marginally from each other in their semantics, can be used to test user input against pre-defined
patterns and thus catch invalid data before it gets into your application. The most common
example of regex usage in PHP is, of course, the email address validator... and since I'm a slave
to tradition, that's also my first example. Take a look:
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
Email address:
<br />
<input type = 'text' name = 'email'>
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// check email address
if (!ereg('^([a-zA-Z0-9])+([\.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(\.[a-zA-Z09_-]+)*\.([a-zA-Z]{2,6})$', $_POST['email'])) {
die("Dunno what that is, but it sure isn't an email address!");
}
// process the data
echo "The email address {$_POST['email']} has a valid structure. Doesn't
mean it works!";
}
?>
</body>
</html>
Here, the pattern /^([a-zA-Z0-9])+([\.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(\.[a-zA-Z09_-]+)*\.([a-zA-Z]{2,6})$/ (try saying that fast!) is a regular expression that matches the
basic format for a user@host email address. Input which matches this pattern will be
accepted; input which doesn't will trigger a piercing siren. Notice that ereg() doesn't need the
163
same delimiters as the faster preg_match(), which complains if it doesn't get a / at each end of
the expression.
Here's another example, this one good for testing international phone numbers:
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
Phone number (with country/area codes):
<br />
<input type = 'text' name = 'tel'>
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// check phone number
if (!preg_match('/^(\+|00)[1-9]{1,3}(\.|\s|-)?([0-9]{1,5}(\.|\s|)?){1,3}$/', $_POST['tel'])) {
die ("Dunno what that is, but it sure isn't an international phone
number!");
}
// process the data
echo "{$_POST['tel']} has a valid structure. Doesn't mean it works!";
}
?>
</body>
</html>
If you play with this a bit, you'll see that it'll accept any of the numbers +1.212.1234.4567, +44
1865 123456 and 0091 11 1234 5678... even though each is formatted differently. Mostly this is
because of my use of the | separator in the regular expression, which functions as logical OR and
makes it possible to create a pattern that supports alternatives internally. Obviously you can
tighten the pattern up as necessary. For example, if you're in India and your application only
supports Indian phone numbers, you can fix the pattern so that it expects 91 (India's country
code) as the first two digits of the number.
It's interesting to try rewriting some of our earlier validation routines using regular expressions.
Here's an alternative version of one of the early examples in this tutorial, rewritten to use ereg()
instead of intval(), is_numeric() and isset():
<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
<form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
164
How many sandwiches would you like? (min 1, max 9)
<br />
<input type = 'text' name = 'quantity'>
<br />
<input type = 'submit' name = 'submit' value = 'Save'>
</form>
<?php
}
else {
// check for required data
if (!ereg('^[1-9]$', $_POST['quantity'])) {
die('ERROR: That is an invalid quantity!');
}
// process the data
echo "I'm making you {$_POST['quantity']} sandwiches. Hope you can eat
them all!";
}
?>
</body>
</html>
Notice how a single regular expression here replaces four separate tests in the earlier version, and
how much more compact the result is. It's precisely this power and flexibility that make regular
expressions such an important part of the input validation toolkit.
Back to Class
Now that you know the basics of input validation, it should be clear to you that this is a task
you'll be performing often. It therefore makes sense to create a reusable library of functions for
input validation, which you can use every time an application needs its input checked for errors.
That's precisely what I'm going to do next - create a PHP class that exposes basic object methods
for data validation and error handling, and then use it to validate a form.
Here's the class definition, class.formValidator.php, written for PHP 5. You could adapt it to
PHP 4 by simply getting rid of the public and private markers on the class methods and
making the private errorList property a var. The rest of the following scripts run under either
PHP version.
<?php
// PHP 5
// class definition
// class encapsulating data validation functions
class formValidator {
// define properties
private $_errorList;
// define methods
// constructor
public function __construct() {
165
$this->resetErrorList();
}
// initialize error list
private function resetErrorList() {
$this->_errorList = array();
}
// check whether input is empty
public function isEmpty($value) {
return (!isset($value) || trim($value) == '') ? true : false;
}
// check whether input is a string
public function isString($value) {
return is_string($value);
}
// check whether input is a number
public function isNumber($value) {
return is_numeric($value);
}
// check whether input is an integer
public function isInteger($value) {
return (intval($value) == $value) ? true : false;
}
// check whether input is alphabetic
public function isAlpha($value) {
return preg_match('/^[a-zA-Z]+$/', $value);
}
// check whether input is within a numeric range
public function isWithinRange($value, $min, $max) {
return (is_numeric($value) && $value >= $min && $value <= $max) ?
true : false;
}
// check whether input is a valid email address
public function isEmailAddress($value) {
return eregi('^([a-z0-9])+([\.a-z0-9_-])*@([a-z0-9_-])+(\.[a-z0-9_]+)*\.([a-z]{2,6})$', $value);
}
// check if a value exists in an array
public function isInArray($array, $value) {
return in_array($value, $array);
}
// add an error to the error list
public function addError($field, $message) {
$this->_errorList[] = array('field' => $field, 'message' =>
$message);
}
// check if errors exist in the error list
166
public function isError() {
return (sizeof($this->_errorList) > 0) ? true : false;
}
// return the error list to the caller
public function getErrorList() {
return $this->_errorList;
}
// destructor
// de-initialize error list
public function __destruct() {
unset($this->_errorList);
}
// end class definition
}
?>
Stripped down to its bare bones, this formValidator class consists of two primary components.
The first is a series of methods that accept the data to be validated, test this data to see whether or
not it is valid (however "valid" may be defined within the scope of the method), and return a
Boolean result code. Here's a list of the supported methods:








isEmpty() - tests if a value is an empty string
isString() - tests if a value is a string
isNumber() - tests if a value is a numeric string
isInteger() - tests if a value is an integer
isAlpha() - tests if a value consists only of alphabetic characters
isEmailAddress() - tests if a value is an email address
isWithinRange() - tests if a value falls within a numeric range
isInArray() - tests if a value exists in an array
Obviously, the list above is not exhaustive - you should feel free to add to it as per your own
requirements.
In earlier examples in this tutorial, I set things up so that the data validation routine would
terminate script processing immediately with die() if it encountered an input error. In the real
world, such abrupt termination on the first error is not usually a good idea; instead, it's more
efficient to process all the user's input, identify all the errors, and then list them for the user to
correct at once.
That's where the second component of this class comes in. It's a PHP array that holds a list of all
the errors encountered during the validation process, and some methods to manipulate this
structure. Here's a list:


isError() - check if any errors exist in the error list
addError() - add an error to the error list
167


getErrorList() - retrieve the current list of errors
resetErrorList() - reset the error list
This might all seem somewhat abstruse to you at the moment. Let's jump into a practical
example and all the code above will begin to make more sense. First, we need a straightforward
HTML form:
<html>
<head></head>
<body>
<b>Fields marked with * are mandatory</b>
<form action = 'processor.php' method = 'post'>
<b>Name*:</b>
<br />
<input type = 'text' name = 'name' size = '15'>
<p />
<b>Age*:</b>
<br />
<input type = 'text' name = 'age' size = '2' maxlength = '2'>
<p />
<b>Email address*:</b>
<br />
<input type = 'text' name = 'email' size = '30'>
<p />
<b>Sex*:</b>
<br />
<input type = 'radio' name = 'sex' value = 'm'>Male
<input type = 'radio' name = 'sex' value = 'f'>Female
<p />
<b>Color*:</b>
<br />
<select name = 'color'>
<option value = ''>-select one-</option>
<option value = 'r'>Red</option>
<option value = 'g'>Green</option>
<option value = 'b'>Blue</option>
<option value = 's'>Silver</option>
</select>
<p />
<b>Insurance*:</b>
<br />
<select name = 'insurance'>
<option value = ''>-select one-</option>
<option value = '1'>Basic</option>
<option value = '2'>Enhanced</option>
<option value = '3'>Premium</option>
</select>
<p />
168
<b>Optional
<br />
<input type
<input type
<input type
<input type
<input type
<p />
<input type
</form>
features:</b>
=
=
=
=
=
'checkbox'
'checkbox'
'checkbox'
'checkbox'
'checkbox'
name
name
name
name
name
=
=
=
=
=
'options[]'
'options[]'
'options[]'
'options[]'
'options[]'
value
value
value
value
value
=
=
=
=
=
'PSTR'>Power steering
'AC'>Air-conditioning
'4WD'>Four-wheel drive
'SR'>Sun roof
'LUP'>Leather upholstery
= 'submit' name = 'submit' value = 'Save'>
</body>
</html>
Now, we need a PHP script to process the input sent through this form, using my new
formValidator object. Save this as processor.php:
<?php
// include file containing class
include('class.formValidator.php');
// instantiate object
$fv = new formValidator();
// start checking the data
// check name
if ($fv->isEmpty($_POST['name'])) {
$fv->addError('Name', 'Please enter your name');
}
// check age and age range
if (!$fv->isNumber($_POST['age'])) {
$fv->addError('Age', 'Please enter your age');
}
else if (!$fv->isWithinRange($_POST['age'], 1, 99)) {
$fv->addError('Age', 'Please enter an age value in the numeric range 199');
}
// check sex
if (!isset($_POST['sex'])) {
$fv->addError('Sex', 'Please select your gender');
}
// check email address
if (!$fv->isEmailAddress($_POST['email'])) {
$fv->addError('Email address', 'Please enter a valid email address');
}
// check color
if ($fv->isEmpty($_POST['color'])) {
$fv->addError('Color', 'Please select one of the listed colors');
}
169
// check insurance type
if ($fv->isEmpty($_POST['insurance'])) {
$fv->addError('Insurance', 'Please select one of the listed insurance
types');
}
// check optional features
if (isset($_POST['options'])) {
if ($fv->isInArray($_POST['options'], '4WD') && !$fv>isInArray($_POST['options'], 'PSTR')) {
$fv->addError('Optional features', 'Please also select Power Steering
if you would like Four-Wheel Drive');
}
}
// check to see if any errors were generated
if ($fv->isError()) {
// print errors
echo '<b>The operation could not be performed because one or more
error(s) occurred.</b> <p /> Please resubmit the form after making the
following changes:';
echo '<ul>';
foreach ($fv->getErrorList() as $e) {
echo '<li>'.$e['field'].': '.$e['message'];
}
echo '</ul>';
}
else {
// do something useful with the data
echo 'Data OK';
}
?>
As the listing above illustrates, the kind of methods exposed by my formValidator() object
come in very handy to verify the user's input. In all cases, the isEmpty() method is used to test
if required fields have been filled in, while the isEmailAddress() and isWithinRange()
methods are used for more precise validation. The isInArray() method, very useful for check
boxes and multiple-select lists, is also a great way to enforce associative rules and link specific
choices together.
It's important to note that the formValidator class created above has nothing to do with the
visual presentation of either the form or the form's result page. Its methods merely test the input
sent to them and return a result code; how that result code is interpreted is entirely up to the
developer. In the script above, a foreach() loop iterates over the list of errors and prints them in
a bulleted list; however, you could just as easily display the errors in a table or write them to a
log file in a custom format. I'll leave it to you to experiment with the possibilities.
That's about it for this episode of PHP 101. But hey, don't be depressed - I'll be back soon and,
next time, I'm going to be taking everything I've taught you and using it to build a real-world
PHP/MySQL web application. Make sure you don't miss that!
170
PHP 101 (part 14): Going to the Polls - Part 1
Vikram Vaswani | 6 comments | Tuesday, March 8, 2005
The Real World
Burning Questions
Designer Databases
Rocking the Vote
PHP 101 (part 14): Going to the Polls - Part 2
The Real World
In the course of this series, I've taken you on a tour of PHP, teaching you everything you need to
know to get started with this extremely powerful toolkit. You've learned how to process arrays,
write functions, construct objects, and throw exceptions. You've also learned how to read user
input from forms, search databases, and use cookies and sessions to maintain state. You're no
longer the timid PHP newbie you used to be, but a bold and powerful PHP warrior, ready to take
on anything the world (or your boss) throws at you...
There's only one drawback. Sure, you have all the weaponry... but you haven't ever used it in the
real world. That's where these concluding segments of PHP 101 come in.
Over the final two chapters of this tutorial, I'm going to guide you through the process of
creating two real-world PHP applications. Not only will this introduce you to practical
application development with PHP, but it will also give you an opportunity to try out all the
theory you've imbibed over the past weeks.
Drivers, start your engines!
Burning Questions
The first application is fairly simple. It's a polling system for a web site, one which allows you
to quickly measure what your visitors think about controversial issues (Kerry versus Bush, tomah-to versus to-mae-to, that kind of thing). This online polling mechanism is fairly popular,
because it lets you find out what your visitors are thinking, and makes your web site more
dynamic and interactive.
171
I'm sure you've seen such a system in action on many web portals, and have a fairly clear mind'seye picture of how it works. Nevertheless, it's good practice to write down exactly what the end
product is supposed to do before you begin writing even a single line of code (geeks call this
defining requirements).
1. There needs to be a mechanism by which the user can view a question, and then select from a
list of possible answers. This "vote" then needs to be captured by the system, and added to the
existing tally of votes for that question.
2. There needs to be a way for the site administrator to add new questions, or delete old ones. A
MySQL database is a good place to store these questions and answers, but the administrator
may not necessarily be proficient enough in SQL to change this data manually. Therefore, a
form-based interface should be provided, to make the task simple and error-free.
3. Obviously, there also needs to be a way to view reports of the votes submitted for each
question and its answers. The report would contain a count of the total votes registered for a
question, as well as a breakdown of the votes each answer received.
An important question here is: Does it make sense to fix the number of available choices for each
question? In my opinion, it doesn't, because the number of available choices is likely to change with
each question. It's better to leave this number variable, and to allow the poll administrator to add as
many choices per question as appropriate. We can, however, define an upper limit on the number of
possible choices for each question - for argument's sake let's say five.
With this basic outline in mind, the next step is to design a database that supports these
requirements.
Designer Databases
This is a good time for you to download the source code for this application, so that you can refer
to it throughout this tutorial. (Note that you will need a MySQL server and a PHP-capable Web
server to run this code.)
Here's the database which I'll be using for this application, stored in db.sql:
#
# Table structure for table `questions`
#
CREATE TABLE `questions` (
`qid` tinyint(3) unsigned NOT NULL auto_increment,
`qtitle` varchar(255) NOT NULL default '',
`qdate` date NOT NULL default '0000-00-00',
PRIMARY KEY (`qid`)
);
#
# Table structure for table `answers`
#
CREATE TABLE `answers` (
`aid` tinyint(3) unsigned NOT NULL auto_increment,
172
`qid` tinyint(4) NOT NULL default '0',
`atitle` varchar(255) NOT NULL default '',
`acount` int(11) NOT NULL default '0',
PRIMARY KEY (`aid`)
);
As you can see, this is pretty simple: one table for the questions, and one for the answers. The two
tables are linked to each other by means of the qid field. With this structure, it's actually possible to
have an infinite numbers of answers to each question. (This is not what we want - we'd prefer this
number to be five or less - but the logic to implement this rule is better placed at the application layer
than at the database layer).
To get things started, and to give you a better idea of how this structure plays in real life, let's
INSERT a question into the database, together with three possible responses:
INSERT INTO `questions` VALUES (1, 'What
'2004-10-15');
INSERT INTO `answers` VALUES (1, 1, 'PHP
INSERT INTO `answers` VALUES (2, 1, 'PHP
INSERT INTO `answers` VALUES (3, 1, 'PHP
version of PHP are you using?',
3.x', 0);
4.x', 0);
5.x', 0);
Alternatively, you could create a new database and type source db.sql from the command prompt
to load the table structures and data directly.
Rocking the Vote
With the database taken care of, it's time to put together the web pages that the user sees. The
first of these is user.php, which connects to the database to get the latest poll question and
displays it together with all its possible responses. Take a look:
<html>
<head><basefont face = 'Arial'></head>
<body>
<?php
// include configuration file
include('config.php');
// open database connection
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// generate and execute query
$query = "SELECT qid, qtitle FROM questions ORDER BY qdate DESC LIMIT 0, 1";
$result = mysql_query($query) or die("ERROR: $query.".mysql_error());
// if records are present
if (mysql_num_rows($result) > 0) {
173
$row = mysql_fetch_object($result);
// get question ID and title
$qid = $row->qid;
echo '<h2>'.$row->qtitle .'</h2>';
echo "<form method = post action = 'user_submit.php'>";
// get possible answers using question ID
$query = "SELECT aid, atitle FROM answers WHERE qid = '$qid'";
$result = mysql_query($query) or die("ERROR: $query.".mysql_error());
if (mysql_num_rows($result) > 0) {
// print answer list as radio buttons
while ($row = mysql_fetch_object($result)) {
echo "<input type = radio name = aid value = '".$row>aid."'>'".$row->atitle."'</input><br />";
}
echo "<input type = hidden name = qid value = '".$qid."'>";
echo "<input type = submit name = submit value = 'Vote!'>";
}
echo '</form>';
}
// if no records present, display message
else {
echo '<font size="-1">No questions currently configured</font>';
}
// close connection
mysql_close($connection);
?>
</body>
</html>
Pay special attention to the SQL query I'm running: I'm using the ORDER BY, DESC and LIMIT
keywords to ensure that I get the latest record (question) from the questions table. Once the
query returns a result, the record ID is used to get the corresponding answer list from the
answers table. A while() loop is then used to print the answers as a series of radio buttons. The
record ID corresponding to each answer is attached to its radio button; when the form is
submitted, this identifier will be used to ensure that the correct counter is updated.
Note that if the database is empty, an error message is displayed. In this example, we've already
inserted one question into the database, so you won't see it at all; however, it's good
programming practice to ensure that all eventualities are accounted for, even the ones that don't
occur that very often.
174
The file config.php included at the top of the script contains the access parameters for the
MySQL database. This data has been placed in a separate file to make it easy to change it if you
move the application to a new server. Take a look inside:
<?php
// database access parameters
$host = 'localhost';
$user = 'guest';
$pass = 'guessme';
$db = 'db3';
?>
Here's what the form looks like:
Okay, now you've got the poll displayed. Users are lining up to participate, and clicks are being
generated by the millions. What do you do with them?
The answer lies in the script that gets activated when a user casts a vote and submits the form
described earlier. This script, user_submit.php, takes care of updating the vote counter for the
appropriate question/answer combination. Take a look:
175
<html>
<head><basefont face = 'Arial'></head>
<body>
<?php
if (isset($_POST['submit'])) {
if (!isset($_POST['aid'])) {
die('ERROR: Please select one of the available choices');
}
// include configuration file
include('config.php');
// open database connection
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// update vote counter
$query = "UPDATE answers SET acount = acount + 1 WHERE aid =
".$_POST['aid']." AND qid = ".$_POST['qid'];
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
// close connection
mysql_close($connection);
// print success message
echo 'Your vote was successfully registered!';
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
This script first checks to ensure that an answer has been selected, by verifying the presence of
the answer ID $_POST['aid']. Assuming the ID is present, the script updates the database to
reflect the new vote and displays an appropriate message.
Now, flip back through your notebook and look at the initial requirement list. Yup, you can cross
off Item #1. Onwards to Item #2.
PHP 101 (part 14): Going to the Polls - Part 2
Vikram Vaswani | 4 comments | Tuesday, March 8, 2005
176
PHP 101 (part 14): Going to the Polls - Part 1
Adding More...
Playing the Numbers
Exit Poll
Adding More...
The next step in building this application is to provide the administrator with an easy way to
add and delete questions and answers from the MySQL database. Consider the script admin.php,
which provides the starting point for these tasks:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Administration</h2>
<h4>Current Questions:</h4>
<table border = '0' cellspacing = '10'>
<?php
// include configuration file
include('config.php');
// open database connection
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// generate and execute query
$query = 'SELECT qid, qtitle, qdate FROM questions ORDER BY qdate DESC';
$result = mysql_query($query) or die('ERROR: $query. '.mysql_error());
// if records are present
if (mysql_num_rows($result) > 0) {
// iterate through resultset
// print question titles
while($row = mysql_fetch_object($result)) {
?>
<tr>
<td><?php echo $row->qtitle; ?></td>
<td><font size = '-2'><a href = 'view.php?qid=<?php echo $row>qid; ?>'>view report</a></font></td>
<td><font size = '-2'><a href = 'delete.php?qid=<?php echo $row-
177
>qid; ?>'>delete</a></font></td>
</tr>
<?php
}
}
// if no records are present, display message
else {
?>
<font size='-1'>No questions currently configured</font>
<?php
}
// close connection
mysql_close($connection);
?>
</table>
<h4>Add New Question:</h4>
<form action = 'add.php' method ='post'>
<table border = '0' cellspacing = '5'>
<tr>
<td>Question</td>
<td><input type = 'text' name = 'qtitle'></td>
</tr>
<tr>
<td>Option #1</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #2</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #3</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #4</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #5</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td colspan = '2' align = 'right'><input type = 'submit' name = 'submit'
value = 'Add Question'></td>
</tr>
</table>
</form>
</body>
</html>
178
Here's what it looks like:
As you can see, there are two sections in this script. The first half connects to the database and
prints a list of all available questions, with "view report" and "delete" links next to each (more on
this these shortly). The second half contains a simple form for the administrator to add a new
question and up to five possible answers.
Once the form is submitted, the data entered by the administrator gets POST-ed to the script
add.php, which validates it and saves it to the database. Here's the code:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Administration</h2>
<?php
if (isset($_POST['submit'])) {
// check form input for errors
179
// check title
if (trim($_POST['qtitle']) == '') {
die('ERROR: Please enter a question');
}
// clean up options
// add valid ones to a new array
foreach ($_POST['options'] as $o) {
if (trim($o) != '') {
$atitles[] = $o;
}
}
// check for at least two options
if (sizeof($atitles) <= 1) {
die('ERROR: Please enter at least two answer choices');
}
// include configuration file
include('config.php');
// open database connection
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// generate and execute query to insert question
$query = "INSERT INTO questions (qtitle, qdate) VALUES
('{$_POST['qtitle']}', NOW())";
$result = mysql_query($query) or die("ERROR: $query.".mysql_error());
// get the ID of the inserted record
$qid = mysql_insert_id();
// reset variables
unset($query);
unset ($result);
// now insert the options
// linking each with the question ID
foreach ($atitles as $atitle) {
$query = "INSERT INTO answers (qid, atitle, acount) VALUES ('$qid',
'$atitle', '0')";
$result = mysql_query($query) or die("ERROR: $query.
".mysql_error());
}
// close connection
mysql_close($connection);
// print success message
echo "Question successfully added to the database! Click <a
href='admin.php'>here</a> to return to the main page";
}
180
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
This script has a lot of things happening in it, so let's go through it step-by-step.
The first order of business is to sanitize the data entered by the user. There are a bunch of lines
of code at the top of the script that do this, by checking for a question title and verifying that at
least two answer choices are present. Notice my use of the trim() function to weed out any
input that contains only empty spaces, and the sizeof() function that verifies the presence of at
least two valid answer choices in the $POST['options'] array. Any failure here results in an
error message, and the script will refuse to proceed further.
Assuming all the data is acceptable, the next step is to save it to the database. First, the question
is saved to the questions table via an INSERT query. The ID generated by this INSERT query is
retrieved via the mysql_insert_id() function, and used to link the answer choices to the
question when saving them to the answers table. Since there will be more than one answer
choice for each question, a foreach() loop is used to repeatedly run an INSERT query - once for
each possible answer choice (with MySQL 4.1 and the PHP 5 mysqli extension, you could
instead use a prepared query here - feel free to experiment with this alternative yourself).
That takes care of adding questions and answers. Now, what about removing them?
Well, go back and take a look at the admin.php script. You'll see that, next to each question
displayed, there is a "delete" link, which points to the script delete.php. You'll also see that this
script is passed an input parameter, the question ID, on the URL itself. It's clear, then, that
delete.php can use this input parameter to identify the corresponding question in the questions
table (as well as its answers - the question ID is common to both tables, remember) and run a
DELETE query to erase this data from the system.
Here's the code that actually does the work:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Administration</h2>
<?php
if ($_GET['qid'] && is_numeric($_GET['qid'])) {
// include configuration file
include('config.php');
// open database connection
181
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// generate and execute query
$query = "DELETE FROM answers WHERE qid = '".$_GET['qid']."'";
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
// generate and execute query
$query = "DELETE FROM questions WHERE qid = '".$_GET['qid']."'";
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
// close connection
mysql_close($connection);
// print success message
echo "Question successfully removed from the database! Click <a href =
'admin.php'>here</a> to return to the main page";
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
As you can see, the question ID passed through the GET method is retrieved by the script, and
used inside two DELETE queries to remove all the records linked to that ID.
Playing the Numbers
Now for possibly the most interesting section of this tutorial: Item #3. Obviously, once you have
users and votes coming in, you'd like to see reports of how the votes are distributed. This
involves connecting to the database, using the question ID to extract the correct record set,
calculating the total number of votes and the percentage each option has of the total, and
displaying this information in a table.
Here's what all that looks like in PHP:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Administration</h2>
<?php
if ($_GET['qid'] && is_numeric($_GET['qid'])) {
// include configuration file
182
include('config.php');
// open database connection
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// get the question
$query = "SELECT qtitle FROM questions WHERE qid = '".$_GET['qid']."'";
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
$row = mysql_fetch_object($result);
echo '<h3>'.$row->qtitle.'</h3>';
// reset variables
unset($query);
unset($result);
unset($row);
// find out if any votes have been cast
$query = "SELECT qid, SUM(acount) AS total FROM answers GROUP BY qid
HAVING qid = ".$_GET['qid'];
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
$row = mysql_fetch_object($result);
$total = $row->total;
// if votes have been cast
if ($total > 0) {
// reset variables
unset($query);
unset($result);
unset($row);
// get individual counts
$query = "SELECT atitle, acount FROM answers WHERE qid =
'".$_GET['qid']."'";
$result = mysql_query($query) or die("ERROR: $query.
".mysql_error());
// if records present
if (mysql_num_rows($result) > 0) {
// print vote results
echo '<table border=1 cellspacing=0 cellpadding=15>';
// iterate through data
// print absolute and percentage totals
while($row = mysql_fetch_object($result)) {
echo '<tr>';
echo '<td>'.$row->atitle.'</td>';
echo '<td>'.$row->acount.'</td>';
echo '<td>'.round(($row->acount/$total) * 100, 2).'%</td>';
echo '</tr>';
}
// print grand total
echo '<tr>';
183
echo
echo
echo
echo
echo
'<td><u>TOTAL</u></td>';
'<td>'.$total.'</td>';
'<td>100%</td>';
'</tr>';
'</table>';
}
}
// if votes have not been cast
else {
echo 'No votes cast yet';
}
// close connection
mysql_close($connection);
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
Here's an example of what the output might look like:
184
This script, view.php, is activated from admin.php in much the same way as delete.php - a
question ID is passed to it as an input parameter, and that ID is used to retrieve the corresponding
answers and the votes each one has gathered. Once the answer set has been retrieved, the total
number of votes submitted can be calculated, and the percentage share of each option in the total
vote can be obtained. This data is then displayed in a simple HTML table.
You need to be careful when converting the absolute numbers into percentages - if there aren't
any votes yet, you can get some pretty strange division by zero errors. To avoid this, the
second query in the script uses MySQL's SUM() function and GROUP BY clause to obtain the total
number of votes for a particular question. If this total is 0, no votes have yet been cast, and a
message to that effect is displayed; if the total is greater than 0, the individual percentages are
calculated.
Exit Poll
The way things are currently set up, a single user can vote for a particular option more than once,
thereby contravening one of the basic principles of democracy: one citizen, one vote. Although
185
it's unlikely that many users would have the patience or inclination to do this; however, it is a
hole, and should be plugged.
I've decided to set a cookie on the voter's system once the vote has successfully been cast. With
the addition of a few lines of script, I can now check for the presence or absence of this cookie
whenever a user tries to vote, and thereby decide whether or not to accept the vote.
Here's the code, which gets added to the very top of user_submit.php:
<?php
// check if a cookie exists for this question
// deny access if it does
if (isset($_COOKIE) && !empty($_COOKIE)) {
if ($_COOKIE['lastpoll'] && $_COOKIE['lastpoll'] == $_POST['qid']) {
die('ERROR: You have already voted in this poll');
}
}
// set cookie
setCookie('lastpoll', $_POST['qid'], time() + 2592000);
?>
With this in place, when a user votes, a cookie is set on the client browser, containing the ID for
the question the user voted on. At each subsequent vote attempt, the script will first check for the
presence of the cookie and, if it exists, the value of the cookie variable $_COOKIE['lastpoll'].
Only if the cookie is absent (indicating that this is a first-time voter) or the value of
$_COOKIE['lastpoll'] is different from the ID of the current poll question (indicating that the
user has voted previously, but in response to a different question), will the vote be accepted.
This is by no means foolproof: any reasonably adept user can delete the cookie from the client's
cache and vote again - but it does add a layer of security to the process. The ideal method, of
course, would be to track voters on the server itself and deny votes to those who have already
voted; and indeed, this is a feasible alternative if the site requires users to register with unique
usernames before accessing its online polls.
Well, that's about it. Hopefully, this exercise has given you some insight into how PHP can be
used to build a simple web application, and illustrated its power and flexibility as a rapid
development tool for the web medium. Come back soon for the final PHP 101, and one more doit-yourself application!
PHP 101 (part 14): Going to the Polls - Part 2
Vikram Vaswani | 4 comments | Tuesday, March 8, 2005
186
PHP 101 (part 14): Going to the Polls - Part 1
Adding More...
Playing the Numbers
Exit Poll
Adding More...
The next step in building this application is to provide the administrator with an easy way to
add and delete questions and answers from the MySQL database. Consider the script admin.php,
which provides the starting point for these tasks:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Administration</h2>
<h4>Current Questions:</h4>
<table border = '0' cellspacing = '10'>
<?php
// include configuration file
include('config.php');
// open database connection
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// generate and execute query
$query = 'SELECT qid, qtitle, qdate FROM questions ORDER BY qdate DESC';
$result = mysql_query($query) or die('ERROR: $query. '.mysql_error());
// if records are present
if (mysql_num_rows($result) > 0) {
// iterate through resultset
// print question titles
while($row = mysql_fetch_object($result)) {
?>
<tr>
<td><?php echo $row->qtitle; ?></td>
<td><font size = '-2'><a href = 'view.php?qid=<?php echo $row>qid; ?>'>view report</a></font></td>
<td><font size = '-2'><a href = 'delete.php?qid=<?php echo $row-
187
>qid; ?>'>delete</a></font></td>
</tr>
<?php
}
}
// if no records are present, display message
else {
?>
<font size='-1'>No questions currently configured</font>
<?php
}
// close connection
mysql_close($connection);
?>
</table>
<h4>Add New Question:</h4>
<form action = 'add.php' method ='post'>
<table border = '0' cellspacing = '5'>
<tr>
<td>Question</td>
<td><input type = 'text' name = 'qtitle'></td>
</tr>
<tr>
<td>Option #1</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #2</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #3</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #4</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #5</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td colspan = '2' align = 'right'><input type = 'submit' name = 'submit'
value = 'Add Question'></td>
</tr>
</table>
</form>
</body>
</html>
188
Here's what it looks like:
As you can see, there are two sections in this script. The first half connects to the database and
prints a list of all available questions, with "view report" and "delete" links next to each (more on
this these shortly). The second half contains a simple form for the administrator to add a new
question and up to five possible answers.
Once the form is submitted, the data entered by the administrator gets POST-ed to the script
add.php, which validates it and saves it to the database. Here's the code:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Administration</h2>
<?php
if (isset($_POST['submit'])) {
// check form input for errors
189
// check title
if (trim($_POST['qtitle']) == '') {
die('ERROR: Please enter a question');
}
// clean up options
// add valid ones to a new array
foreach ($_POST['options'] as $o) {
if (trim($o) != '') {
$atitles[] = $o;
}
}
// check for at least two options
if (sizeof($atitles) <= 1) {
die('ERROR: Please enter at least two answer choices');
}
// include configuration file
include('config.php');
// open database connection
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// generate and execute query to insert question
$query = "INSERT INTO questions (qtitle, qdate) VALUES
('{$_POST['qtitle']}', NOW())";
$result = mysql_query($query) or die("ERROR: $query.".mysql_error());
// get the ID of the inserted record
$qid = mysql_insert_id();
// reset variables
unset($query);
unset ($result);
// now insert the options
// linking each with the question ID
foreach ($atitles as $atitle) {
$query = "INSERT INTO answers (qid, atitle, acount) VALUES ('$qid',
'$atitle', '0')";
$result = mysql_query($query) or die("ERROR: $query.
".mysql_error());
}
// close connection
mysql_close($connection);
// print success message
echo "Question successfully added to the database! Click <a
href='admin.php'>here</a> to return to the main page";
}
190
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
This script has a lot of things happening in it, so let's go through it step-by-step.
The first order of business is to sanitize the data entered by the user. There are a bunch of lines
of code at the top of the script that do this, by checking for a question title and verifying that at
least two answer choices are present. Notice my use of the trim() function to weed out any
input that contains only empty spaces, and the sizeof() function that verifies the presence of at
least two valid answer choices in the $POST['options'] array. Any failure here results in an
error message, and the script will refuse to proceed further.
Assuming all the data is acceptable, the next step is to save it to the database. First, the question
is saved to the questions table via an INSERT query. The ID generated by this INSERT query is
retrieved via the mysql_insert_id() function, and used to link the answer choices to the
question when saving them to the answers table. Since there will be more than one answer
choice for each question, a foreach() loop is used to repeatedly run an INSERT query - once for
each possible answer choice (with MySQL 4.1 and the PHP 5 mysqli extension, you could
instead use a prepared query here - feel free to experiment with this alternative yourself).
That takes care of adding questions and answers. Now, what about removing them?
Well, go back and take a look at the admin.php script. You'll see that, next to each question
displayed, there is a "delete" link, which points to the script delete.php. You'll also see that this
script is passed an input parameter, the question ID, on the URL itself. It's clear, then, that
delete.php can use this input parameter to identify the corresponding question in the questions
table (as well as its answers - the question ID is common to both tables, remember) and run a
DELETE query to erase this data from the system.
Here's the code that actually does the work:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Administration</h2>
<?php
if ($_GET['qid'] && is_numeric($_GET['qid'])) {
// include configuration file
include('config.php');
// open database connection
191
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// generate and execute query
$query = "DELETE FROM answers WHERE qid = '".$_GET['qid']."'";
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
// generate and execute query
$query = "DELETE FROM questions WHERE qid = '".$_GET['qid']."'";
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
// close connection
mysql_close($connection);
// print success message
echo "Question successfully removed from the database! Click <a href =
'admin.php'>here</a> to return to the main page";
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
As you can see, the question ID passed through the GET method is retrieved by the script, and
used inside two DELETE queries to remove all the records linked to that ID.
Playing the Numbers
Now for possibly the most interesting section of this tutorial: Item #3. Obviously, once you have
users and votes coming in, you'd like to see reports of how the votes are distributed. This
involves connecting to the database, using the question ID to extract the correct record set,
calculating the total number of votes and the percentage each option has of the total, and
displaying this information in a table.
Here's what all that looks like in PHP:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Administration</h2>
<?php
if ($_GET['qid'] && is_numeric($_GET['qid'])) {
// include configuration file
192
include('config.php');
// open database connection
$connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to
connect!');
// select database
mysql_select_db($db) or die('ERROR: Unable to select database!');
// get the question
$query = "SELECT qtitle FROM questions WHERE qid = '".$_GET['qid']."'";
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
$row = mysql_fetch_object($result);
echo '<h3>'.$row->qtitle.'</h3>';
// reset variables
unset($query);
unset($result);
unset($row);
// find out if any votes have been cast
$query = "SELECT qid, SUM(acount) AS total FROM answers GROUP BY qid
HAVING qid = ".$_GET['qid'];
$result = mysql_query($query) or die("ERROR: $query. ".mysql_error());
$row = mysql_fetch_object($result);
$total = $row->total;
// if votes have been cast
if ($total > 0) {
// reset variables
unset($query);
unset($result);
unset($row);
// get individual counts
$query = "SELECT atitle, acount FROM answers WHERE qid =
'".$_GET['qid']."'";
$result = mysql_query($query) or die("ERROR: $query.
".mysql_error());
// if records present
if (mysql_num_rows($result) > 0) {
// print vote results
echo '<table border=1 cellspacing=0 cellpadding=15>';
// iterate through data
// print absolute and percentage totals
while($row = mysql_fetch_object($result)) {
echo '<tr>';
echo '<td>'.$row->atitle.'</td>';
echo '<td>'.$row->acount.'</td>';
echo '<td>'.round(($row->acount/$total) * 100, 2).'%</td>';
echo '</tr>';
}
// print grand total
echo '<tr>';
193
echo
echo
echo
echo
echo
'<td><u>TOTAL</u></td>';
'<td>'.$total.'</td>';
'<td>100%</td>';
'</tr>';
'</table>';
}
}
// if votes have not been cast
else {
echo 'No votes cast yet';
}
// close connection
mysql_close($connection);
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
Here's an example of what the output might look like:
194
This script, view.php, is activated from admin.php in much the same way as delete.php - a
question ID is passed to it as an input parameter, and that ID is used to retrieve the corresponding
answers and the votes each one has gathered. Once the answer set has been retrieved, the total
number of votes submitted can be calculated, and the percentage share of each option in the total
vote can be obtained. This data is then displayed in a simple HTML table.
You need to be careful when converting the absolute numbers into percentages - if there aren't
any votes yet, you can get some pretty strange division by zero errors. To avoid this, the
second query in the script uses MySQL's SUM() function and GROUP BY clause to obtain the total
number of votes for a particular question. If this total is 0, no votes have yet been cast, and a
message to that effect is displayed; if the total is greater than 0, the individual percentages are
calculated.
Exit Poll
The way things are currently set up, a single user can vote for a particular option more than once,
thereby contravening one of the basic principles of democracy: one citizen, one vote. Although
195
it's unlikely that many users would have the patience or inclination to do this; however, it is a
hole, and should be plugged.
I've decided to set a cookie on the voter's system once the vote has successfully been cast. With
the addition of a few lines of script, I can now check for the presence or absence of this cookie
whenever a user tries to vote, and thereby decide whether or not to accept the vote.
Here's the code, which gets added to the very top of user_submit.php:
<?php
// check if a cookie exists for this question
// deny access if it does
if (isset($_COOKIE) && !empty($_COOKIE)) {
if ($_COOKIE['lastpoll'] && $_COOKIE['lastpoll'] == $_POST['qid']) {
die('ERROR: You have already voted in this poll');
}
}
// set cookie
setCookie('lastpoll', $_POST['qid'], time() + 2592000);
?>
With this in place, when a user votes, a cookie is set on the client browser, containing the ID for
the question the user voted on. At each subsequent vote attempt, the script will first check for the
presence of the cookie and, if it exists, the value of the cookie variable $_COOKIE['lastpoll'].
Only if the cookie is absent (indicating that this is a first-time voter) or the value of
$_COOKIE['lastpoll'] is different from the ID of the current poll question (indicating that the
user has voted previously, but in response to a different question), will the vote be accepted.
This is by no means foolproof: any reasonably adept user can delete the cookie from the client's
cache and vote again - but it does add a layer of security to the process. The ideal method, of
course, would be to track voters on the server itself and deny votes to those who have already
voted; and indeed, this is a feasible alternative if the site requires users to register with unique
usernames before accessing its online polls.
Well, that's about it. Hopefully, this exercise has given you some insight into how PHP can be
used to build a simple web application, and illustrated its power and flexibility as a rapid
development tool for the web medium. Come back soon for the final PHP 101, and one more doit-yourself application!
Comments
Add Comment
196
Tuesday, January 9, 2007
MAKE IT SHORT
6:59AM PST · dotSilver [unregistered]
You are able to make a part in the admin area shorter and easier to use using some PHP.
<tr>
<td>Option #1</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #2</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #3</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #4</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
<tr>
<td>Option #5</td>
<td><input type = 'text' name = 'options[]'></td>
</tr>
Can be changed to the following (also making it shorter):
<?
$i = 0;
while($i < 6){ //While i is less than 6
echo("<tr>
<td>Option #".$i."</td>
<td><input type='text' name='options[]'></td>
</tr>"); //Show input field.
$i++ //Add one to $i
} //stop while
?>
197
PHP 101 (part 15): No News is Good News
Vikram Vaswani | 6 comments | Saturday, June 4, 2005
A Difficult Choice
Alphabet Soup
Laying the Foundation
Top Story
Point and Click
A Difficult Choice
After the workout I gave you last time, you're probably either chomping at the bit to build
another PHP application or you've decided to give up PHP programming and try growing
cucumbers instead. If it's the latter, you should stop reading right now, because I can guarantee
you that this concluding installment of PHP 101 has absolutely nothing to teach you about
vegetable farming.
If it's the former, however, then you're going to enjoy what's coming up. Over the next few
pages, I'm going to be building a simple RSS news aggregator using PHP, SQLite and
SimpleXML. With this news aggregator, you can plug into RSS news feeds from all over the
web, creating a newscast that reflects your needs and interests for your website. The best part: it
updates itself automatically with the latest stories every time you view it!
Come on in, and let's get this show on the road!
Alphabet Soup
I'll start with the basics. What the heck is RSS anyhow?
RSS (the acronym stands for RDF Site Summary) is a format originally devised by Netscape to
distribute information about the content on its My.Netscape.Com portal. The format has gone
through many iterations since its introduction in early 1997 (take a look at
http://backend.userland.com/stories/rss091 for information on RSS's long and complicated
history) but most feeds use RSS 1.0 or RSS 0.91, both of which are lightweight yet full-featured.
RSS makes it possible for webmasters to publish and distribute information about what's new
and interesting on a particular site at a particular time. This information, which could range from
a list of news articles to stock market data or weather forecasts, is published as a well-formed
198
XML document, and can therefore be parsed, processed and rendered by any XML parser including the SimpleXML parser that is part of PHP 5.
Quite a few popular web sites make an RSS or RDF news feed available to the public at large.
Freshmeat and Slashdot both have one, and so do many others, including the PEAR, PECL and
Zend sites. A quick Google search for public RSS feeds will get you more links than you can
shake a stick at.
An RSS document typically contains a list of resources (URLs), marked up with descriptive
metadata. Here's an example:
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/">
<channel rdf:about="http://www.melonfire.com/">
<title>Trog</title>
<description>Well-written technical articles and tutorials on web
technologies</description>
<link>http://www.melonfire.com/community/columns/trog/</link>
<items>
<rdf:Seq>
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.php?id=
100" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.php?id=
71" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.php?id=
62" />
</rdf:Seq>
</items>
</channel>
<item
rdf:about="http://www.melonfire.com/community/columns/trog/article.php?id=100
">
<title>Building A PHP-Based Mail Client (part 1)</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=100</l
ink>
<description>Ever wondered how web-based mail clients work? Find out
here.</description>
</item>
<item
rdf:about="http://www.melonfire.com/community/columns/trog/article.php?id=71"
>
<title>Using PHP With XML (part 1)</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=71</li
nk>
199
<description>Use PHP's SAX parser to parse XML data and generate HTML
pages.</description>
</item>
<item
rdf:about="http://www.melonfire.com/community/columns/trog/article.php?id=62"
>
<title>Access Granted</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=62</li
nk>
<description>Precisely control access to information with the SQLite grant
tables.</description>
</item>
</rdf:RDF>
As you can see, an RDF file is split up into clearly demarcated sections. First comes the
document prolog, namespace declarations, and root element. This is followed by a <channel>
block, which contains general information on the channel that is described by this RDF file. In
the example above, the channel is Melonfire's Trog column, which gets updated every week with
new technical articles and tutorials.
The <channel> block contains an <items> block, which contains a sequential list of all the
resources described within the RDF document. Every resource in this block corresponds to a
resource described in greater detail in a subsequent <item> block. Every <item> block describes
a single resource in greater detail, providing a title, an URL and a description of that resource.
It's this information that our application will use to generate a personalized news feed.
Laying the Foundation
Now that you know what RSS and RDF are all about, it's time to start work. I'll begin by sitting
down at a table near the window and doodling aimlessly on a sheet of paper until I figure out
exactly what my application is supposed to do, piece by piece (actually, in this case, the
requirements are actually pretty basic):
1. The application must support one or more RSS-compliant news feeds. On start-up, the
application should retrieve the latest versions of these feeds, parse them and display their
contents in an easy-to-read manner. A SQLite database is a good choice to store this list of
feeds.
2. The user should be able to control the number of stories s/he picks up from each feed. For
example, a user might want to display more science and health news than business news.
3. The application should offer the user a web-based interface to add or delete news feeds. This
interface will use PHP's SQLite API to run appropriate SQL queries on the SQLite database file
and alter the information stored in the database.
Keeping these requirements in mind, it's possible to design a simple database table to hold the (userconfigurable) list of RSS news feeds. Here's what it might look like:
CREATE TABLE rss (
200
id INTEGER NOT NULL PRIMARY KEY,
title varchar(255) NOT NULL,
url varchar(255) NOT NULL,
count INTEGER NOT NULL
);
From the table above, it's clear that every news feed will have three attributes: a descriptive title, the
URL to the feed itself, and a value indicating how many of the stories in the feed you would like to see
displayed in your own custom news page.
Let's add some data to get things started:
INSERT INTO rss VALUES(1, 'Slashdot', 'http://slashdot.org/slashdot.rdf', 5);
INSERT INTO rss VALUES(2, 'Wired News',
'http://www.wired.com/news_drop/netcenter/netcenter.rdf', 5);
INSERT INTO rss VALUES(3, 'Business News',
'http://www.npr.org/rss/rss.php?topicId=6', 3);
INSERT INTO rss VALUES(4, 'Health News',
'http://news.bbc.co.uk/rss/newsonline_world_edition/health/rss091.xml', 3);
INSERT INTO rss VALUES(5, 'Freshmeat', 'http://www.freshmeat.net/backend/fmreleases.rdf', 5);
You can create all this directly from the schema file rss.sql using the SQLite command .read from the
command-line client, if you still have that on board from Part Nine. In fact, now would be a good time
for you to download all the source code for this application, so that you can check it out and refer to it
easily throughout this tutorial. Note that you will need a PHP 5-enabled web server to run this code.
Top Story
With the database safely in its web-inaccessible directory, the next step is to write the code that
uses the data inside it to connect to each news feed, parse it for news data, and present a
customized news page.
Here's what that code, user.php, looks like:
<?php
// PHP 5
// include configuration file
include('config.php');
// open database file
$handle = sqlite_open($db) or die('ERROR: Unable to open database!');
// generate and execute query
$query = "SELECT id, title, url, count FROM rss";
$result = sqlite_query($handle, $query) or die("ERROR: $query.
".sqlite_error_string(sqlite_last_error($handle)));
201
// if records present
if (sqlite_num_rows($result) > 0) {
// iterate through resultset
// fetch and parse feed
while($row = sqlite_fetch_object($result)) {
$xml = simplexml_load_file($row->url);
echo "<h4>$row->title</h4>";
// print descriptions
for ($x = 0; $x < $row->count; $x++) {
// for RSS 0.91
if (isset($xml->channel->item)) {
$item = $xml->channel->item[$x];
}
// for RSS 1.0
elseif (isset($xml->item)) {
$item = $xml->item[$x];
}
echo "<a href=\"$item->link\">$item->title</a><br />$item>description<p />";
}
echo "<hr />";
// reset variables
unset($xml);
unset($item);
}
}
// if no records present
// display message
else {
?>
<font size = '-1'>No feeds currently configured</font>
<?php
}
// close connection
sqlite_close($handle);
?>
202
Here's what the output might look like (note that there will a time lag in producing the page,
because PHP will be silently opening HTTP connections to each URL to retrieve the
corresponding RSS feed):
The code to accomplish this might look simple, but there's actually a lot going on behind the
scenes. The first step is to obtain a list of the RSS feeds configured by the user from the SQLite
database. To accomplish this, a SQLite database handle is initialized, and a SQL SELECT query is
executed. A while() loop is used to iterate through the resulting record collection.
For each URL thus obtained, the simplexml_load_file() function is used to retrieve and read
the RSS feed. Depending on the number of stories to be displayed, a for() loop is executed and
the appropriate number of <item> elements in the feed are parsed. Notice that the path to access
an <item> differs depending on whether the feed is RSS 0.91 or RSS 1.0.
Note that if the database is empty, an error message will appear. In this example, since I've
already inserted a bunch of records into the database, you'll never see the error message at all;
203
however, it's good programming practice to ensure that all eventualities are accounted for, even
remote ones.
As before, the file config.php is included at the top of every script. This file contains database
access parameters, as below:
<?php
// database details
// always use a directory that cannot be accessed from the web
$path = $_SERVER['DOCUMENT_ROOT'].'/../';
$db = $path.'rss.db';
?>
Point and Click
With the news display out of the way, all that's left is to add a simple administrative tool to
manipulate the contents of the SQLite database. The code here is going to be very similar to what
you saw in PHP 101 Part 14: a start page called admin.php that provides a snapshot of the
current database, and a form to add new entries. Here it is in full:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Feed Manager</h2>
<h4>Current Feeds:</h4>
<table border = '0' cellspacing = '10'>
<?php
// PHP 5
// include configuration file
include('config.php');
// open database file
$handle = sqlite_open($db) or die('ERROR: Unable to open database!');
// generate and execute query
$query = "SELECT id, title, url, count FROM rss";
$result = sqlite_query($handle, $query) or die("ERROR: $query.
".sqlite_error_string(sqlite_last_error($handle)));
// if records present
204
if (sqlite_num_rows($result) > 0) {
// iterate through result set
// print article titles
while ($row = sqlite_fetch_object($result)) {
?>
<tr>
<td><?php echo $row->title; ?> (<?php echo $row->count; ?>)</td>
<td><font size = '-2'><a href="delete.php?id=<?php echo $row->id;
?>">delete</a></font></td>
</tr>
<?php
}
}
// if there are no records present, display message
else {
?>
<font size = '-1'>No feeds currently configured</font>
<?php
}
// close connection
sqlite_close($handle);
?>
</table>
<h4>Add New Feed:</h4>
<form action = 'add.php' method =
<table border = '0' cellspacing =
<tr>
<td>Title</td>
<td><input type = 'text' name
</tr>
<tr>
<td>Feed URL</td>
<td><input type = 'text' name
</tr>
<tr>
<td>Stories to display</td>
<td><input type = 'text' name
</tr>
<tr>
'post'>
'5'>
= 'title'></td>
= 'url'></td>
= 'count' size = '2'></td>
205
<td colspan = '2' align = 'right'><input type = 'submit' name = 'submit'
value = 'Add RSS Feed'></td>
</tr>
</table>
</form>
</body>
</html>
Here's what it looks like:
As you can see, there are two sections in this script. The first half connects to the database and
prints a list of all the currently configured news feeds, with a "delete" link next to each. The
second half contains a form for the administrator to add a new feed, together with its attributes.
206
Once the form is submitted, the data gets POST-ed to the script add.php, which validates it and
saves it to the database. Here's the code for add.php:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Feed Manager</h2>
<?php
// PHP 5
if (isset($_POST['submit'])) {
// check form input for errors
// check title
if (trim($_POST['title']) == '') {
die('ERROR: Please enter a title');
}
// check URL
if ((trim($_POST['url']) == '') || !ereg("^http\://[a-zA-Z0-9\-\.]+\.[azA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\._\?\,\'/\\\+&%\$#\=~\-])*$",
$_POST['url'])) {
die('ERROR: Please enter a valid URL');
}
// check story number
if (!is_numeric($_POST['count'])) {
die('ERROR: Please enter a valid story count');
}
// include configuration file
include('config.php');
// open database file
$handle = sqlite_open($db) or die('ERROR: Unable to open database!');
// generate and execute query
$query = "INSERT INTO rss (title, url, count) VALUES
('".$_POST['title']."', '".$_POST['url']."', '".$_POST['count']."')";
$result = sqlite_query($handle, $query) or die("ERROR: $query.
".sqlite_error_string(sqlite_last_error($handle)));
// close connection
sqlite_close($handle);
// print success message
echo "Item successfully added to the database! Click <a href =
207
'admin.php'>here</a> to return to the main page";
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
The lower half of the script should be familiar to you: it contains the usual function calls to open
an SQLite database and execute an INSERT query to save the user's data to the database. What's
interesting, though, is the top half of the script, which contains a number of input tests to ensure
that the data being saved doesn't contain gibberish.
There are three tests here. One checks for the presence of a descriptive title, another uses the
is_numeric() function to verify that the value entered for the story count is a valid number, and
the third uses the ereg() function to check the format of the URL. If you read Part 13, you'll
know all about the importance of validating user input; here's that theory going into action.
That takes care of adding new RSS feeds. Now, what about removing them?
Remember how, in admin.php, each feed displayed in the list had a "delete" link, which pointed
to the script delete.php. This delete.php script takes care of deleting a news feed from the table,
given the feed ID (which is passed through the link). Take a look at the code, and things will
become clearer:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Feed Manager</h2>
<?php
// PHP 5
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
// include configuration file
include('config.php');
// open database file
$handle = sqlite_open($db) or die('ERROR: Unable to open database!');
// generate and execute query
$query = "DELETE FROM rss WHERE id = '".$_GET['id']."'";
$result = sqlite_query($handle, $query) or die("ERROR: $query.
208
".sqlite_error_string(sqlite_last_error($handle)));
// close connection
sqlite_close($handle);
// print success message
echo "Item successfully removed from the database! Click <a href =
'admin.php'>here</a> to return to the main page";
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
The record ID passed through the URL GET method is retrieved by delete.php, and used with a
DELETE SQL query to erase the corresponding record. Try it out and see for yourself!
And that's about all I have for you. I hope you enjoyed the 15-part journey that was PHP 101,
and that you found it both educational and fun - I know I did, and it was a pleasure having you
along for the ride. If you'd like to read more about specific aspects of PHP, drop by
www.melonfire.com and take a look at some more of my tutorials and articles. Until then...
happy coding!
209
Download