Defining an Error Handling Function
If we know that a certain statement or block of code can throw an exception, we
should wrap that code within the try…catch block to prevent the default error
message being displayed and present a user-friendly error page. But before we
proceed, let’s create a function that will render an error message and exit the
application. As we will be calling it from different script fi les, the best place for this
function is, of course, the common.inc.php fi le.
Our function, called showError(), will do the following:
- Render a heading saying “Error”.
- Render the error message. We will escape the text with the
htmlspecialchars() function and process it with the nl2br() function so
that we can display multi-line messages. (This function will convert all line
break characters to <br> tags.) - Call the showFooter() function to close the opening <html> and <body>
tags. The function will assume that the application has already called the
showHeader() function. (Otherwise, we will end up with broken HTML.)
We will also have to modify the block that creates the connection object in common.
inc.php to catch the possible exception. With all these changes, the new version of
common.inc.php will look like this:
<?php
/**
* This is a common include file
* PDO Library Management example application
* @author Dennis Popel
*/
// DB connection string and username/password
$connStr = 'mysql:host=localhost;dbname=pdo';
$user = 'root';
$pass = 'root';
/**
* This function will render the header on every page,
* including the opening html tag,
* the head section and the opening body tag.
* It should be called before any output of the
* page itself.
* @param string $title the page title
*/
function showHeader($title)
{
?>
<html>
<head><title><?=htmlspecialchars($title)?></title></head>
<body>
<h1><?=htmlspecialchars($title)?></h1>
<a href="books.php">Books</a>
<a href="authors.php">Authors</a>
<hr>
<?php
}
/**
* This function will 'close' the body and html
* tags opened by the showHeader() function
*/
function showFooter()
{
?>
</body>
</html>
<?php
}
/**
* This function will display an error message, call the
* showFooter() function and terminate the application
* @param string $message the error message
*/
function showError($message)
{
echo "<h2>Error</h2>";
echo nl2br(htmlspecialchars($message));
showFooter();
exit();
}
// Create the connection object
try
{
$conn = new PDO($connStr, $user, $pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e)
{
showHeader('Error');
showError("Sorry, an error has occurred. Please try your request
later\n" . $e->getMessage());
}
As you can see, the newly created function is pretty straightforward. The more
interesting part is the try…catch block that we use to trap the exception. Now with
these modifi cations we can test how a real exception will get processed. To do that,
make sure your connection string is wrong (so that it specifi es wrong database
name for MySQL or contains invalid fi le name for SQLite). Point your browser to
books.php and you should see the following window:

Creating the Edit Book Page
As we have discussed earlier, we want to extend our application so that we can
add and edit books and authors. Also, our system should be able to protect us from
entering the same book twice—by enforcing the unique index on the ISBN column in
the books table.
Before we proceed with the code, we will create the index. Fire up your command line
client and enter the following command (which is the same for MySQL and SQLite):
CREATE UNIQUE INDEX idx_isbn ON books(isbn);
We will also make our edit book page serve two purposes at once—adding a new
book and editing an existing one. The script will distinguish which action to take by
the presence of the book ID, either in an URL or in a hidden form fi eld. We will link
to this new page from within books.php, so that we will be able to edit every book
just by clicking on a link on the books listing page.
This page is more complicated than those described in the previous chapter, so I will
provide you with the code fi rst and then discuss it. Let’s call this page edit Book.php:
<?php
/**
* This page allows to add or edit a book
* PDO Library Management example application
* @author Dennis Popel
*/
// Don't forget the include
include('common.inc.php');
// See if we have the book ID passed in the request
$id = (int)$_REQUEST['book'];
if($id) {
// We have the ID, get the book details from the table
$q = $conn->query("SELECT * FROM books WHERE id=$id");
$book = $q->fetch(PDO::FETCH_ASSOC);
$q->closeCursor();
$q = null;
}
else {
// We are creating a new book
$book = array();
}
// Now get the list of all authors' first and last names
// We will need it to create the dropdown box for author
$authors = array();
$q = $conn->query("SELECT id, lastName, firstName FROM authors ORDER
BY lastName, firstName");
$q->setFetchMode(PDO::FETCH_ASSOC);
while($a = $q->fetch())
{
$authors[$a['id']] = "$a[lastName], $a[firstName]";
}
// Now see if the form was submitted
if($_POST['submit']) {
// Validate every field
$warnings = array();
// Title should be non-empty
if(!$_POST['title'])
{
$warnings[] = 'Please enter book title';
}
// Author should be a key in the $authors array
if(!array_key_exists($_POST['author'], $authors))
{
$warnings[] = 'Please select author for the book';
}
// ISBN should be a 10-digit number
if(!preg_match('~^\d{10}$~', $_POST['isbn'])) {
$warnings[] = 'ISBN should be 10 digits';
}
// Published should be non-empty
if(!$_POST['publisher']) {
$warnings[] = 'Please enter publisher';
}
// Year should be 4 digits
if(!preg_match('~^\d{4}$~', $_POST['year'])) {
$warnings[] = 'Year should be 4 digits';
}
// Sumary should be non-empty
if(!$_POST['summary']) {
$warnings[] = 'Please enter summary';
}
// If there are no errors, we can update the database
// If there was book ID passed, update that book
if(count($warnings) == 0) {
if(@$book['id']) {
$sql = "UPDATE books SET title=" . $conn>quote($_POST['title']) .
', author=' . $conn->quote($_POST['author']) .
', isbn=' . $conn->quote($_POST['isbn']) .
', publisher=' . $conn->quote($_POST['publisher']) .
', year=' . $conn->quote($_POST['year']) .
', summary=' . $conn->quote($_POST['summary']) .
" WHERE id=$book[id]";
}
else {
$sql = "INSERT INTO books(title, author, isbn, publisher,
year,summary) VALUES(" .
$conn->quote($_POST['title']) .
', ' . $conn->quote($_POST['author']) .
', ' . $conn->quote($_POST['isbn']) .
', ' . $conn->quote($_POST['publisher']) .
', ' . $conn->quote($_POST['year']) .
', ' . $conn->quote($_POST['summary']) .
')';
}
// Now we are updating the DB.
// We wrap this into a try/catch block
// as an exception can get thrown if
// the ISBN is already in the table
try
{
$conn->query($sql);
// If we are here that means that no error
// We can return back to books listing
header("Location: books.php");
exit;
}
catch(PDOException $e)
{
$warnings[] = 'Duplicate ISBN entered. Please correct';
}
}
}
else {
// Form was not submitted.
// Populate the $_POST array with the book's details
$_POST = $book;
}
// Display the header
showHeader('Edit Book');
// If we have any warnings, display them now
if(count($warnings)) {
echo "<b>Please correct these errors:</b><br>";
foreach($warnings as $w)
{
echo "- ", htmlspecialchars($w), "<br>";
}
}
// Now display the form
?>
<form action="editBook.php" method="post">
<table border="1" cellpadding="3">
<tr>
<td>Title</td>
<td>
<input type="text" name="title"
value="<?=htmlspecialchars($_POST['title'])?>">
</td>
</tr>
<tr>
<td>Author</td>
<td>
<select name="author">
<option value="">Please select...</option>
<?php foreach($authors as $id=>$author) { ?>
<option value="<?=$id?>"
<?= $id == $_POST['author'] ? 'selected' : ''?>>
<?=htmlspecialchars($author)?>
</option>
<?php } ?>
</select>
</td>
</tr>
<tr>
<td>ISBN</td>
<td>
<input type="text" name="isbn"
value="<?=htmlspecialchars($_POST['isbn'])?>">
</td>
</tr>
<tr>
<td>Publisher</td>
<td>
<input type="text" name="publisher"
value="<?=htmlspecialchars($_POST['publisher'])?>">
</td>
</tr>
<tr>
<td>Year</td>
<td>
<input type="text" name="year"
value="<?=htmlspecialchars($_POST['year'])?>">
</td>
</tr>
<tr>
<td>Summary</td>
<td>
<textarea name="summary"><?=htmlspecialchars(
$_POST['summary'])?></textarea>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" name="submit" value="Save">
</td>
</tr>
</table>
<?php if(@$book['id']) { ?>
<input type="hidden" name="book" value="<?=$book['id']?>">
<?php } ?>
</form>
<?php
// Display footer
showFooter();
The code is rather self-documenting, but let’s briefl y go through its main parts.
Lines 12 to 23 deal with fetching the book details would be edited if the page was
requested with the book ID. These details are stored in the $book variable. Note how
we explicitly cast the request parameter book to integer so that no SQL injection
can occur (line 13). If no book ID is provided, we set it to an empty array. Note how
we call the closeCursor() function and then assign the $q variable to null. This is
necessary as we are going to reuse the connection object.
Lines 26 to 33 prepare the list of authors. As our system allows exactly one author
per book, we will create a select box fi eld listing all the authors.
Line 35 checks whether there was a submission of the form. If the test is successful,
the script validates every fi eld (lines 37 to 68). Every failed validation is appended
to a list of warnings. (The $warnings variable is initialized with an empty array.)
We will use this list to see whether validations were successful and to store error
messages if they weren’t.
Lines 69 to 94 build the actual SQL for update. The fi nal SQL depends on whether we
are updating a book (when the $book array will contain the id key), or adding a new
one. Note how we quote every column value prior to query execution.
Lines 95 to 112 try to execute the query. It may fail if the user has entered a duplicate
ISBN so we wrap the code in a try…catch block. If an exception does get thrown,
the catch block will append the corresponding warning to the $warnings array.
If everything works without an error, the script redirects to the books listing page
where you should see the changes.
Lines 113 to 118 get executed if there was no submission of the form. Here the
$_POST array gets populated with the contents of the $books variable. We do this
because we will use the $_POST array to display form fi elds’ values later in the code.
Note how we display error messages (if any) on lines 122 to 129 and the select box on
lines 141 to 154. (We are looking through all authors and if the author’s ID matches
this book author’s ID then that author is marked as the selected option.) Also, the
other form fi elds are rendered using the htmlspecialchars() function applied to
the items of the $_POST array. Lines 189 to 191 will add a hidden fi eld to the form
that contains the ID of the currently edited book (if any).
Modern web applications employ client-side validation in addition to server-side
validation of user-supplied data. Though this is not in the scope of this book, you
might consider browser-based validation in your projects to increase responsiveness
and potentially decrease load of your web server.
Now, we should link to the newly created page from the books.php page. We will
provide an Edit this book link for every listed book as well as an Add book link under
the table. I will not reproduce the whole books.php source here, just the lines that
should be changed. So, lines 32 to 48 should be replaced with the following:
<?php
// Now iterate over every row and display it
while($r = $q->fetch())
{
?>
<tr>
<td><ahref="author.php?id=<?=$r['authorId']?>">
<?=htmlspecialchars("$r[firstName] $r[lastName]")?></a></td>
<td><?=htmlspecialchars($r['title'])?></td>
<td><?=htmlspecialchars($r['isbn'])?></td>
<td><?=htmlspecialchars($r['publisher'])?></td>
<td><?=htmlspecialchars($r['year'])?></td>
<td><?=htmlspecialchars($r['summary'])?></td>
<td>
<a href="editBook.php?book=<?=$r['id']?>">Edit</a>
</td>
</tr>
<?php
}
?>
The following should be added just before the call to the showFooter() function so
that the four lines look like this:
<a href="editBook.php">Add book...</a>
<?php
// Display footer
showFooter();
Now, if you again navigate to the books.php page you should see the
following window:

To see how our edit book page looks, click on any Edit link in the last column of the
table. You should see the following form:

Let’s see how our form works. It is validating every form fi eld that gets sent to the
database. If there is any validation error, the form will not update the database and
prompt the user to correct his submission. For example, try changing the author
select box to the default option (labeled Please select…) and editing the ISBN to be 5
digits long.
If you click the Save button, you should see that the form displays following
error messages:

Now correct the errors and try to change the ISBN to 1904811027. This ISBN is
already used in our database by another book, so the form will again display an
error. You can further test the form by adding a book. You might also want to test
how it works with SQLite.