ServerCore

ServerCore helps you write programs that run on Web servers. As explained in Foundations, that’s where much of the business value of the Web is. It’s not surprising that companies want to hire people with server-side skills. Even humans, as well as us dogs.

ServerCore covers the programming language PHP. Other languages are used on the server-side, like PERL, Ruby, Java, and C#. The first chapter explains why PHP was chosen. The second chapter is short, talking about how PHP works, and (free!) tools for writing PHP.

The rest of the book talks about things you can do with PHP, like getting form data, saving it into a database, creating reports, and making a log in system for security.

ServerCore will not make you a professional PHP programmer. But that’s not the goal.

ServerCore covers just the most important bits of PHP. It shows how PHP is used to solve real, though simplified, problems. When you finish ServerCore, you will know enough to:

So, let’s get started!

Why learn PHP with CoreDogs?

Learning anything takes time, and time is valuable. Why spend your time at this Web site, when you could be playing in the sun?

This chapter answers three questions:

You should know that:

CoreDogs will not turn you into a professional PHP programmer.

That takes years of learning and practical experience.

CoreDog’s goal is simpler:

Help you learn the core of PHP as it is used in the real world.

CoreDog’s PHP lessons can help you:

Let’s start by looking at what PHP does, and why it can help to know it. Then we’ll talk about the CoreDogs Way.

Why learn PHP?

Where are we?

Should you spend your time learning PHP at CoreDogs? This lesson looks at part of that question: why learn PHP at all?

This lesson’s goals

By the end of this lesson, you should:

  • Know what PHP does.
  • Know how learning PHP can help you.
  • Know why it’s better to start with PHP than other languages, like Java or ASP.NET.

What does PHP do?

It’s easiest to understand with an example.

Humans have a site called Facebook. Maybe you’re a human yourself (I won’t hold it against you), and have a Facebook account.

Suppose we want to create Snoutbook, like Facebook but for dogs. One feature will be like the Facebook wall. The wall is a place where people can put text, photos, and other stuff. Here’s part of my wall (please don’t tell the Facebook people that I’m really a dog):

My wall

Figure 1. My wall

Snoutbook will have the same thing, but we’ll call it the tree.

When Ivan posts a new entry to his tree, other dogs get to see it:

Ivan's tree

Figure 2. Ivan’s tree

Here’s how it will work:

Snoutbook

Figure 3. Snoutbook

Mazie types http://snoutbook.com into her browser. The Web server is the software that sends back the HTML for that page. The best known Web server is Apache. You can use it for free. We’ll talk more about that in the next chapter.

Web servers aren’t good at storing lots of data. But database servers are good at that. So the Web server sends Ivan’s post to the database server. When Mazie wants to look at Ivan’s tree, the Web server gets the data from the database server, and formats it.

There are many different database servers. One of the best known is MySQL. It’s free, too.

But wait, wait! There’s a problem. Web servers don’t know how to talk to database servers! Arghhhhh! What to do?

Enter PHP.

PHP code can be embedded directly into HTML files, just like JavaScript.

tree.php

Figure 4. tree.php

Unlike JavaScript, the PHP statements are run on the Web server. They can do servery things, like access databases.

The files usually have the extension .php instead of .html. So if Mazie wanted to look at Ivan’s tree, her browser would ask for tree.php rather than tree.html. But the browser would still get HTML code.

Here’s what happens when Mazie looks at Ivan’s tree.

Snoutbook's architecture

Figure 5. Snoutbook’s architecture

The browser asks the server for tree.php (1). The Web server – Apache, say – loads the file tree.php from disk into memory (2). The Web server sees that the file’s extension is .php, and sends the file to the PHP interpreter (3).

The PHP interpreter is a program, running on the same computer as Apache. It knows how to follow instructions written in the PHP language. These PHP instructions are written by a person – human, dog, or some other intelligent being.

So the PHP interpreter runs the PHP code in tree.php (4). This code asks the database server for Ivan’s tree postings. The database server returns the data. The PHP code wraps the data in HTML tags, like <p> and <div>.

When it has finished running all the code, the PHP interpreter sends the result back to the Web server (5). The result will be a bunch of HTML, created by the PHP code in tree.php. The server then sends the data to the browser (6).

The browser just gets HTML. It doesn’t know, or care, that it was generated by a program.

So that’s what PHP does. PHP code runs on a server, and generates HTML (usually – it can output other types of data as well).

So why is this important?

This is a Big Fat Hairy Deal

When Mazie tells her browser to get tree.php, she sees something different each time. What she sees depends on what is in the database. If Ivan has added something, the database will have changed, and tree.php will show different content.

Server-side programming technology makes the Web what it is today. It’s the ability to store data from one person and show the data to someone else that’s important.

This technology is behind YouTube, Amazon, Twitter, Facebook, and, well, just about every big site you’ve heard of. It makes the Web what it is today.

Server-side programming is also behind search engines like Google, Yahoo, and Bing. They grab Web pages and store them. When you do a search, a program runs on one of their servers, and accesses the stored data.

These sites don’t use PHP, necessarily. People use many languages for Web programming, including PERL, ASP.NET, C, C++, C#, Java, Ruby, and Python. More on this later.

Why will learning PHP help you?

Let’s talk about how learning PHP can help you.

Understand how the Web really works

You won’t know how the Web works unless you know something about server-side processing. There’ll be a big gap in your knowledge. You won’t know how most of the Web pages you see every day are created.

Understand business value on the Web

Much of the business value of the Web depends on the server side. Want to sell products online? Your product data will be in a database. Web pages describing products will be generated by programs written in PHP (or some other language).

If you set up a Web shop, you probably won’t write your own PHP shopping cart. You’d use one somebody else has written. But if you understand how it all works, you can manage the software more effectively. Which brings us to…

Install and manage software

You have thousands of PHP applications to choose from. Many of the most popular Web applications in the world are written in PHP: WordPress, Drupal, Joomla, and many others.

If you learn a little PHP, you’ll find it easier to install and manage these applications. The documentation and forum discussions will make more sense to you.

You’ll also be able to…

Customize and extend software

You can use PHP to extend existing applications. You can change them to match your exact requirements.

CoreDogs is like this. It’s a Drupal site. Drupal is a very powerful content management system. But it doesn’t do everything I wanted.

For example, CoreDogs embeds exercises directly in pages. It shows the exercises you have completed in your portfolio. Suppose you want to share some of your solutions with, say, your cousin Jim. You can choose the exercises you want him to see, and send him a URL that lists them.

Drupal doesn’t have these features. But it does let you add your own features. This was one of the reasons I chose Drupal. To add your own “modules” (as Drupal extensions are called), you need to be able to write PHP.

Write your own programs

You can write simple PHP programs for many tasks. For example, you can build a contact page. Or a page that helps users recommend a site to a friend. Even a simple chat feature is easy to write.

CoreDogs won’t turn you into a professional PHP programmer. But you’ll find that there are many basic day-to-day things you’ll be able to do with PHP.

Work with others more effectively

If you work for a big company, you can bet that PHP or another server-side language is used on many of the firm’s sites. If you haven’t run into one yet, you will.

You might be asked to work on one of those sites, as a document creator, graphic designer, project manager, or in some other role. You might work with PHP programmers. You’ll be able to work with them more effectively if you understand the tools they’re using.

If you run a small business Web site, you might hire and supervise PHP contractors. You’ll be able to do that better if you know a little PHP yourself. You’ll also be able to explain to them what you want, and understand their answers to your questions.

Decide if server-side programming is for you

There are many different Web jobs. Designer, artist, writer, community manager, usability tester, marketer, server admininstrator,... Oh, and programmer.

Learning about PHP will help you decide how much server-side programming you want in your career.

There are good reasons why it’s worth learning server-side programming. But…

Why PHP instead of Java, ASP.NET, Ruby, ...

There are two reasons it’s best to start with PHP.

PHP is widely used

PHP is likely the most widely used server-side programming language (see this article, for example). It’s used on millions of servers world wide.

Exercise: HotScripts

HotScripts is a collection of Web programs and related stuff. They list scripts (another word for programs) by language. What language has the most scripts? What percentage of their scripts are in PHP?

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

PHP is very popular in the open source world, where applications like WordPress and Drupal live. One reason is that PHP is itself open source.

This means that your PHP skills will have value to many different people (including you!).

Exercise: PHP apps on your hosting account

Open up the control panel on your shared hosting account. If you don’t have an account, get one.

You probably have something like Fantastico, that lets you install software on your account. Here’s what part of my Fantastico page looks like:

Fantastico

Figure 1. Fantastico

These are some of the applications Fantastico will install for me.

Pick ten or so applications at random. Find out what server-side programming language they use. List the number that use each different language.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

PHP is (relatively) easy to learn

Make no mistake: programming is hard. For most people, anyway.

What people have trouble with is not the programming languages, but the way of thinking about program design. This only comes with practice, practice, and more practice.

But still, some languages are easier to learn than others. Java is one of the more difficult. It’s a good language, but it’s very structured, and hard for beginners to get started with.

Two of the easiest languages to learn are JavaScript and PHP. Even a few lines of JavaScript and PHP can do useful things. You can start writing programs quickly, without having to learn of lot of stuff first.

Summary

PHP programs run on Web servers. They let Web pages store data on database servers. Server-side programs are crucial on today’s Web.

Learning PHP can help you:

  • Understand how the Web really works
  • Understand business value on the Web
  • Install and manage software
  • Customize and extend software
  • Write your own programs
  • Work with others more effectively
  • Decide on a career

PHP is widely used. It’s easy to learn, for a programming language.

What now?

Let’s talk about the CoreDogs Way of learning PHP. You should decide whether it’s right for you.

CoreDogs and PHP

Where are we?

You know roughly what PHP does, and why it is worth learning. But why learn with CoreDogs?

This lesson’s goals

This lesson explains:

  • How CoreDogs helps you use your learning time well.
  • That CoreDogs uses ideas from the science of learning. This makes it different from other sources.

Core beliefs

Learning how to create Web sites is worthwhile.

  • You can make a career out of it.
  • You can give a boost to another career, like writer, marketer, or teacher.
  • You can serve the greater good, from a neighborhood group to a national nonprofit.

But learning takes time. Time you could spend doing other things.

CoreDogs helps you spend your learning time well.

  • CoreDogs focuses on the real world. I emphasize how PHP helps you solve problems, rather than how it works internally.
  • CoreDogs flattens the learning curve. It’s important not to let things get too complex, too fast. Slow and easy is better. CoreDogs explains new ideas in easily digestible chunks. Go through the lessons at your own pace.
  • CoreDogs leaves a lot out. The lessons cover only the most important parts of PHP. This gives you a good framework for future learning, should you choose to go further.
  • CoreDogs encourages practice, practice, practice. There are dozens of exercises, all with solutions. There’s a dedicated discussion area for each exercise, so you can ask others about their solutions.

Why do all these things? Because of a core value of the CoreDogs project:

CoreDogs should use ideas from the science of learning.

There’s a lot of good research on how people learn. The last two decades have seen some amazing work. People into learning research will recognize terms like constructivism, scaffolding, phenomenography, and social learning.

CoreDogs uses ideas from this research. That means that CoreDogs is unlike other books or Web sites on PHP. They tend to be written by people with strong technical skills, but who haven’t studied learning.

CoreDogs has less technical material about PHP than other sources. What is here is just the important core. And the lessons are designed to help reduce your learning time.

Summary

Learning how to create Web sites is worthwhile. You can make a career out of it. You can give a boost to another career. You can serve the greater good.

Learning takes time. CoreDogs helps you spend your learning time well. It focuses on the real world. It flattens the learning curve. CoreDogs leaves a lot out, focusing only on the core of PHP. CoreDogs encourages practice, practice, practice.

CoreDogs uses ideas from the science of learning. It has less technical material about PHP than other sources. What is here is just the important core. The lessons help reduce your learning time.

What now?

To finish up this chapter, let’s talk about how you can use CoreDogs most effectively.

Moving forward

Where are we?

You know roughly what PHP does, and why it is worth learning. You’ve read about the CoreDogs Way. What if you like what you see, and decide to go further?

This lesson’s goals

By the end of this lesson, you should know how to get the most from your CoreDogs learning time.

Read in sequence

CoreDogs breaks up concepts into small chunks. Some ideas are scattered across chapters. For example, PHP’s if statement is explored a little here, a little there, and a little somewhere else.

This makes learning easier, since you don’t have to learn complicated new things all at once. But it also means that if you jump around the chapters, you might get confused.

It’s best to read the chapters in sequence. There are forward and backward links at the top and bottom of each lesson.

Do the exercises

I can’t stress this enough. PHP is about doing, not memorizing. You won’t learn unless you do the exercises.

Exercises are in two places. Some are mixed in with the lessons, usually following an example. They tend to be “near transfer” tasks. That is, tasks that are similar to the examples in the lessons.

There are also exercises at the end of each chapter. They are more “far transfer” tasks. They ask you to apply what you have learned to new situations.

Ideally, you should do both types of exercises.

Work with a group

Working with other people can help a great deal. You’ll be less frustrated when you can bounce ideas off other people. And it’s motivating to see other people working on the same stuff.

Some CoreDoggers have natural groups; they might be using CoreDogs in a college course, for example. Other CoreDoggers are not so lucky.

You can use Facebook to find other people to work with. You can use other sites, of course, but CoreDogs is Facebook-oriented.

If you work in a company, you might be able to find coworkers who want to learn about Web sites. Or maybe there are people in your neighborhood association. Ask around.

Take pride in your learning

You’ll learn a lot with CoreDogs. And it will be useful stuff.

From time to time, think back over what you have learned. Take a look at your exercise portfolio; click Your account and the portfolio tab. This is a visible record of your accomplishments.

Share some of your exercise solutions with others. For example, you might want to show your friend Jenny what you know how to do. You can choose the solutions you want to share, and email people a URL that will show those solutions. See the portfolio tab on the Your account page for details.

Send feedback

OK, this is more for me than for you. You might find mistakes, think that something should be explained better, have an idea for a good example,... Let me know!

There’s a feedback area to the right, below the notes and quick links.

It often takes me a while to fix errors. CoreDogs is evolving, with a lot of work still to be done. But I do appreciate your taking the time to send feedback.

Summary

This lesson explains how to make the most of CoreDogs. You should read in sequence, do the exercises, work with a group, take notes, and take pride in your learning. Please send feedback as well.

What now?

Let’s get going with PHP. We’ll start by reviewing what PHP does, and then look at tools that will make your PHP work easier.

The PHP ecosystem

This short chapter is about how you’ll do PHP work. First, it reviews what PHP does. Then it recommends some tools to make your PHP work easier.

How PHP pages run

This lesson’s goals

By the end of this lesson, you should:

  • Know that PHP programs run on Web servers.
  • Know that PHP programs can work with database servers.
  • Know that PHP can send email, read and write files, and interact with many other kinds of servers.

Snoutbook revisited

Recall that Snoutbook is Facebook for dogs. It has the tree, equivalent to Facebook’s wall. When Ivan posts to his tree, everyone can see his new entry.

Ivan's tree

Figure 1. Ivan’s tree

If Mazie wants to look at Ivan’s tree, she points her browser at tree.php. tree.php has PHP code embedded into HTML.

tree.php

Figure 2. tree.php

Here’s what happens when Mazie looks at Ivan’s tree.

Snoutbook's architecture

Figure 3. Snoutbook’s architecture

The browser asks the server for tree.php (1). The Web server – Apache, say – loads the file tree.php from disk into memory (2). The Web server sees that the file’s extension is .php, and sends the file to the PHP interpreter (3).

The PHP interpreter is a program, running on the same computer as Apache. It knows how to follow instructions written in the PHP language. These PHP instructions are written by a person – human, dog, or some other intelligent being.

So the PHP interpreter runs the PHP code in tree.php (4). This code asks the database server for Ivan’s tree postings. The database server returns the data. The PHP code wraps the data in HTML tags, like <p> and <div>.

When it has finished running all the code, the PHP interpreter sends the result back to the Web server (5). The result will be a bunch of HTML, created by the PHP code in tree.php. The server then sends the data to the browser (6).

The browser just gets HTML. It doesn’t know, or care, that it was generated by a program.

So that’s what PHP does. PHP code runs on a server, and generates HTML (usually – it can output other types of data as well).

Other servers

PHP code can work with a database server. But it can do other things as well.

  • PHP can send email. We’ll see this is a later chapter.
  • PHP can save data into files, and read it back later. We’ll do this, too.
  • PHP can interact with Google Maps, Amazon, Facebook, Twitter, Paypal, and lots of other services.

When you can write PHP code, you can do, well, all sorts of things.

Summary

PHP programs run on Web servers. They can work with database servers. PHP can also send email, read and write files, and interact with many kinds of other servers.

What now?

Let’s look at the tools you’ll need to make your PHP work easier.

Tools for writing PHP programs

Where are we?

You know what PHP programs do. Now let’s look at the tools you’ll need to write them.

This lesson’s goals

By the end of this lesson, you should:

  • Know about two approaches to development.
  • Know about two good tools for writing code.
  • Know about a good file transfer program.
  • Know how to install a development server on your computer.

Two development models

There are two general ways to end up with runnable PHP code on a server:

  • Edit your files directly on your hosting account’s server.
  • Get your PHP working on your own computer, then upload the files to your hosting account.

You can do the former, but I don’t recommend it. If you like the Unix command line, you can log in to your Web host and use pico or some other character-oriented editor. Or you can use an editor with FTP support.

There are two problems with this. First, other people might try to access your Web site as you’re working on it. They’ll see a broken site. Second, unless you’re careful with backups, it’s easy to damage a file, and not be able to get it back to a working state.

I recommend the second approach: Get your PHP working on your own computer, then upload the files to your hosting account. That means installing a Web server on your own computer. It’s easier than you think.

You’ll need at least three types of tools:

  • Tools for writing code
  • A file transfer program
  • A local Web server

Writing code

You’ll need software to write PHP code. The same tool should help you write HTML, CSS, and JavaScript as well.

There are two main types of tools you can use: text editors and integrated development environments. There of lots of different options. I’ll just recommend one of each type.

Notepad++ is a free Windows text editor. It has lots of nice features, including syntax highlighting and code completion. You can install it on a portable drive (like a USB stick), so you can carry it around and use it on any PC that is available.

TextMate is often recommended for the Mac, but it’s not free. Aptana and TextWrangler are free options.

An integrated development environment (IDE) is a more complete solution. The one I recommend at the moment is the PHP bundle of Netbeans. The editor is better than Notepad++, and it has things like an integrated debugger. It’s free. You can run Netbeans from a portable drive (like a USB stick), if you want.

You can start with Notepad++ if you like. But you should switch to Netbeans eventually. It will make you more productive. The best thing: it will find some typing mistakes for you! Yay!

File transfer

You can do file transfer from within both Notepad++ and Netbeans, but I prefer using a separate program. I generally get the PHP right, and then upload it as a separate step.

The program I use is WinSCP. It supports FTP, SFTP, and SCP. Easy to use, and free.

Development server

You can use your own computer as a development server. Just install Web server software, and off you go.

Let’s do that now. We’ll be looking at a Windows installation, but other platforms are similar.

Download XAMPP from ApacheFriends. There are Windows, Mac, and Linux versions. It’s easiest to grab the installer, since it does all the work for you.

Run the installer, and install XAMPP at c:\ (for Windows). It will install the Apache Web server, MySQL, and some other stuff.

XAMPP comes with a control panel you can use to start and stop Apache. Run it, and start Apache. It will look something like this:

XAMPP control panel

Figure 1. XAMPP control panel

Now you can use your browser to access Apache on your computer. But what is the URL?

Some IP addresses and domain names are reserved for special uses. The IP address 127.0.0.1 and the domain name localhost always map to the computer you are using. So start your browser, and type http://localhost into the address bar. You should see a welcome page from XAMPP.

W00f!

Now you have a development server. You can create pages, and upload them to your hosting account.

Add a page

You should be able to put files on your disk, and access them through your localhost. But you need to put them in the right place.

If you installed XAMPP at C:\xampp, the root of your Web – your DocumentRoot – will be C:\xampp\htdocs. To make a file available through your development server, all you have to do is copy it to somewhere under C:\xampp\htdocs.

Let’s try it. Create a directory under C:\xampp\htdocs. Called it testthing. Now create a text file with a joke in it, and save it in that directory, under the name joke.txt.

Here’s a screen shot:

Joke file

Figure 2. Joke file

Windows Explorer is in the background (you can see the path and file name). I just used Notepad to make the file. You can see the file’s contents.

Let’s see if it worked. Go to your browser, and enter the URL:

http://localhost/testthing/joke.txt

Joke in the browser

Figure 3. Joke in the browser

If you did everything right, then – wee hoo!

Summary

You can write PHP code directly on your Web hosting account’s server. But it’s better to install a development server on your own computer, and do your programming there.

Notepad++ is a good text editor for PHP work. Netbeans is a good IDE.

WinSCP is good for file transfer.

Download XAMPP from ApacheFriends. It’s an easy way to install a development server.

What now?

Time to dig into PHP. Let’s start with one of the most important uses of PHP, one that gives you a big productivity win: creating dynamic Web templates.

Web site templates

Where are we?

Our first PHP task is an easy one, but one of the most important uses of PHP.

Most Web pages have regions, like this:

Web page regions

Figure 1. Web page regions

Different parts of a page are put into the regions:

Using the regions

Figure 2. Using the regions

All of the pages on a site usually have the same structure. Some regions have the same content. For example, every page might have the same thing in the bottom region. CoreDogs is like this. No matter what page you look at, the bottom is the same.

Well, almost. The message on the right of the footer is chosen randomly for each page.

Suppose you have a Web site with, say, 200 pages. Every page has this in the bottom region:

Original footer

Figure 3. Original footer

Your client (or employer, school, sister, whoever) wants to change it to this, on every page:

New footer

Figure 4. New footer

If each page is a separate HTML, that means you have to change 200 files. Ack! What a pain.

But if you use PHP to set up a template for the site, you can change all 200 pages by just changing one file. You read that right – change the entire site by editing one file!

Talk about a productivity win.

If you use just one thing from this book, make it this one: creating active Web templates with PHP. That’s what you’ll learn in this chapter. And it’s one of the easiest things you can do with PHP.

This chapter’s goals

By the end of this chapter, you should:

One thing to keep in mind: this can get confusing! Not so much because the PHP for Web templates is complex. It’s not. It’s simple, in fact.

The problem is that there are so many pieces to a Web site. HTML files, CSS files, JavaScript files, link tags, image tags, and other things. They all need special treatment.

For this chapter to make sense, you’ll need to know how Web sites are put together. I’ll assume that you’ve worked through the ClientCore book, and more-or-less understand it. You should know about images, links, nav bars, CSS files, JavaScript files, a little jQuery, and page layouts.

Let’s get started!

PHP outputs HTML

This lesson’s goals

By the end of this lesson, you should:

  • Know that Web servers embed PHP output in HTML.
  • Be able to use PHP’s print statement.

What’s the time, server?

Let’s take a look at a simple PHP program. It will show the time on the server. This is quite likely to be in a different time zone from the one you’re in, but that’s OK.

To begin, click here. A new window (or tab) opens, and a page shows with the time on the server.

Now look at the page’s URL. It will look something like this:

http://coredogs.com/content_media/lessons/servercore/web-site-templates/server-time.php

Look at the extension of the file. Usually, we see .html. This one is .php. What’s the difference?

When a Web server gets a request for a URL, it looks at the extension to figure out what to do. Here is the process for an HTML file.

Get an HTML file

Figure 1. Get an HTML file

The browser sends a request for x.html to the server (1). The server looks on its hard disk, and reads the file (2). It sends the data it read back to the browser (3).

You can read about the details on the page Static Web pages.

But when the URL ends in .php, the server does something else.

Get a PHP file

Figure 2. Get a PHP file

The browser sends a request to the server, this time for x.php. The server reads the file from its disk drive (2). The server notices the file has the extension .php, so it sends the data it read to the PHP interpreter (3).

The PHP interpreter is a program that runs on the server, just like any other program. It can follow instructions written in the PHP programming language.

The PHP interpreter follows the instructions in the data it got from the server (4). Most of the time, PHP code is embedded inside HTML code.

The PHP interpreter sends its results back to the server (5). The server sends the data back to the browser (6).

All of this happened when you clicked on a link to http://coredogs.com/content_media/lessons/servercore/web-site-templates/server-time.php. The browser asked the Web server at coredogs.com for the the data at server-time.php (1 in Figure 2). The server read the file server-time.php from its disk drive (2), and passed the file’s contents to the PHP interpreter (3). The interpreter ran the code (4), and gave the results back to the Web server (5). The server then sent the data back to your browser (6).

That’s a lot of stuff happening, just because you clicked a link!

What the browser got

Have a look at the HTML code that the browser got from this page. (In Firefox, hit Control-U. In other browsers, right-click and select View source or Show source, or whatever it says.)

You’ll see something like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Server time</title>
  </head>
  <body>
    <p>The time at the server is 
    04:10:22 PM    </p>
  </body>
</html>

Figure 3. The HTML

It looks just like regular HTML, although the formatting around line 8 is a little strange. There is no PHP or anything here, just plain HTML.

The PHP in the file server-time.php generates normal HTML. The browser can’t tell the difference between a page manually typed in by a person, and one that’s generated. That’s the beauty of PHP; the browser just gets HTML, a language it already understands.

Now click the server time link again. You’ll get a different time, assuming that a few seconds have elapsed. Have a look at the HTML source of the new page. It’s almost the same as it was. Just the time is a little different.

In fact, each time your browser asks for server-time.php, it will get plain HTML, but it will get different HTML (if the requests are at least a second apart). The URL is always the same:

http://coredogs.com/content_media/lessons/servercore/web-site-templates/server-time.php

But the PHP in the file server-time.php creates different HTML each time.

Inside server-time.php

Here’s what’s in the file server-time.php on the server’s hard disk:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Server time</title>
  </head>
  <body>
    <p>The time at the server is 
    <?php 
    print date('h:i:s A');
    ?>
    </p>
  </body>
</html>

Figure 4. server-time.php

Lines 1 to 8 and 12 to 14 are regular HTML. The PHP interpreter just passes them back to the server unchanged. The magic happens in lines 9 to 11.

Let’s make sure we understand what I wanted the page to show. I wanted the server to send back HTML that looked like this:

<p>The time at the server is (time goes here)</p>

When you program in PHP, you are programming indirectly. You write PHP code that creates HTML, or text that gets embedded in HTML. The HTML then gets returned to the browser for rendering. So remember:

You write PHP that writes HTML.

Line 9 is the PHP opening tag: <?php. It tells the PHP interpreter that some PHP is about to appear. The interpreter runs the code until it hits the close tag in line 11: ?>.

Line 10 is:

print date('h:i:s A');

print is a PHP statement that says “Output what follows.” date() is a PHP function that gets the current date and time, and returns it. The stuff in the parentheses (()) is a format, telling date how to show the date and time. In this case, the hours, then a colon (:), then the minutes, then another colon, then the seconds, then a space, and then an AM/PM indicator.

The statement ends with a semicolon (;). All statements in PHP end this way. If you leave off the semicolon, the PHP interpreter will get confused, and complain.

The result? The print statement outputs the current time.

Exercise: Web server time

Upload the server time program on to your own server. One way is to:

  • Copy the code in Figure 4 onto the clipboard.
  • Paste it into a new file in Notepad++ (or whatever editor you use).
  • Save the file. Remember, I recommend you create a new directory for every exercise. For example, you might create a directory called coredogs/servercore/web-templates/server-time, and save the file there. coredogs is this site, servercore is the book on CoreDogs, web-templates is the chapter in the book, and server-time is the exercise in the chapter. It seems like a lot of typing, but it’s very important to keep your files organized.

Name the file whatever you want. If you name it index.php, it will be the default file in that directory. You can read more about default files.

If you installed XAMPP on your computer, and you put the file somewhere under your htdocs directory, you can access it through your browser. For example, suppose you saved the file as:

C:\xampp\htdocs\coredogs\servercore\web-templates\server-time\server-time.php

This is a file path for a Windows machine. If you use a Mac or Linux box, your path will have a slightly different format.

Remember that C:\xampp\htdocs\ is the root of the Web server on your computer.

Make sure your Apache server is running (use the XAMPP control panel to start it). In your browser, type the URL:

http://localhost/coredogs/servercore/web-templates/server-time/server-time.php

http://localhost/ maps to C:\xampp\htdocs\. The rest of the path maps to your PHP file.

Once the program works, upload the file to your hosting account, and point your browser there. (If you didn’t install XAMPP on your computer, you will need to upload the file before you can test it.) I recommend that you use the same file structure on your hosting account that you used on your own computer.

Now, change the page. Add an <h1> that says something like:

Renata’s first PHP program

Of course, replace Renata with your own name.

Upload the new version. Enter the URL of your first PHP program below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Show IP addresses

Write a PHP page that will show the IP addresses of your browser, and the server the page is running on.

You can get the IP addresses with:

$_SERVER['REMOTE_ADDR']

$_SERVER['SERVER_ADDR']

Hint: base this on the server time example.

You can run my solution.

Upload your solution to your server. Put the result below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • When a Web server gets a URL with a .php extension, it sends the file to the PHP interpreter.
  • You embed PHP code in HTML pages.
  • Most PHP code outputs HTML, which then gets sent to the browser for rendering.

What now?

Outputting stuff will help you create dynamic Web templates. The next thing you need to know is how to insert one file inside another.

Onward! To glory!

Inserting files

Where are we?

You saw how the print statement puts data into the HTML. Let’s see how you can put entire files into the HTML.

This lesson’s goals

By the end of this lesson, you should:

  • Know how to use PHP to insert a file into the HTML output.
  • Know how to use a file path to insert the same file in pages that are spread out across a directory tree.

Inserting files

PHP has several statements that insert files: include, require, and require_once. We’ll just talk about one of them: require.

Suppose we create a file that contains HTML to show a footer for every page on a Web site. Here it is:

<div id="footer">
  Copyright © 2010  |  All rights reserved
</div>

Figure 1. Footer code.

This is not a complete HTML page, just a fragment of HTML. It wouldn’t work by itself. Let’s call the file footer.inc. The .inc reminds us that the file is meant to be included in another file.

Here’s the PHP page include-footer.php:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Including a footer</title>
  </head>
  <body>
    <h1>Hi there!</h1>
    <p>I am a page. Aren't I wonderful?</p>
    <?php 
    require 'footer.inc';
    ?>
  </body>
</html>

Figure 2. Including a footer

The require statement reads the file it’s given, and inserts it. Any PHP in the included file is executed. If the PHP interpreter can’t find the file, it shows an error message.

You can try it. Make sure you have a look at the HTML source. As far as the browser knows, the content of footer.inc was typed directly into include-footer.php.

Inserting files from other directories

The code in Figure 2 is in a file called include-footer.php. It has the line:

require 'footer.inc';

There is no directory given, just a file name. So where will the PHP interpreter look for footer.inc?

The answer: in the same directory as include-footer.php. So if include-footer.php is in /my/directory/of/doom/, then the PHP interpreter will look for /my/directory/of/doom/footer.inc.

When you’re using dynamic Web templates for real, this won’t work very well. Suppose you were creating a site with this structure:

Site structure

Figure 3. Site structure.

The file at /index.php is the home page of the site. The file at /articles/index.php shows a list of articles. There are other things like blog entries, products, and so on, but we don’t need to consider them here.

Suppose you put this line into both /index.php and /articles/index.php:

require 'footer.inc';

You would need one footer.inc for /index.php and another for /articles/index.php:

Duplicate footer files

Figure 4. Duplicate footer files.

But what’s the point of that? If we wanted to change the footer text across the entire site, we’d have to change all of the footer.inc files.

What we really want is for both /index.php and /articles/index.php to refer to the same footer.inc.

Just one footer file

Figure 5. Just one footer file.

footer.inc is in a directory called library. PHP developers often move shared files to a directory like this, and call it library, lib, includes, common, or something like that.

But we have to change the require statements to make this work. Here is the require statement from /index.php:

require 'library/footer.inc';

Here is the one for /articles/index.php:

require '../library/footer.inc';

The .. means “go up a level.”

To brush up on how to use paths to navigate between directories, see the discussion of absolute, relative, and root relative links.

An example

Let’s see how this would work in an entire Web site.

Let’s create a simple site called The Dog Site. It has a home page and two sections: dog profiles, and articles.

Overview

Figure 6. Site overview.

Each of the two sections of the site – dog profiles and articles – has a main page. Each one is linked from the home page.

Here is the home page:

Home page

Figure 7. Home page.

Here is the main page for the dog profile section:

Dog profile main page

Figure 8. Dog profile main page.

Here is the main page for the articles section:

Articles main page

Figure 9. Articles main page.

You can try the site.

Let’s create the directory tree for the site, that is, the directories on the server that all the files will go into. We’ll make a directory for each section of the site: dog profiles and articles.

The header is the same on every page, so let’s pull that out and put it in its own file. Here’s the code:

<!-- Header file, to be included into every page. -->
<h1>The Dog Site</h1>
<h2>Bringing you happiness since last week</h2>
<hr>

Figure 10. HTML for the header.

Let’s put the code into the file header.inc, and put the file into a library directory.

Here is the directory tree so far.

Initial tree

Figure 11. Initial tree.

The home page is index.php. Recall that if a browser sends a URL to a Web server, and the URL does not have a file name, then the browser uses a default name. In ClientCore, we used index.html as the default. index.php can be the default as well.

So if a browser asks for:

http://dogthing.com/play/

the server will run:

http://dogthing.com/play/index.php

Let’s see what the tree looks like when all the files are added:

Complete tree

Figure 12. Complete tree.

All of the PHP files are going to have a require statement, to insert header.inc. But since the PHP files are in different directories, they will use different paths to navigate to header.inc. Here are the paths for some of the files.

File Path in require
/index.php library/header.inc
/articles/index.php ../library/header.inc

Figure 13. Paths in some of the files.

Exercise: The rest of the paths

Complete figure 13. Give the path in the require statement for every PHP file.

You can check your solution by downloading a zip file of the site. Expand the zip file to see the PHP code.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Inserting files

Download and expand this zip file. It will create a directory tree like this:

Directory tree

Figure 1. Directory tree

Open up wombat.php. Add require statements to create a page that looks like this:

Output

Figure 2. Output

Use only relative file paths.

Upload the entire tree to your server. Put the URL below.

You can see my solution, but try it yourself first.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

In this lesson, you learned:

  • How to use PHP to insert a file into the HTML output, using the require statement.
  • How to use a file path to insert the same file in pages that are spread out across a directory tree.

What now?

But there’s a problem. It’s easy to break links to images and other resources. Let’s fix that in the next lesson.

Inserting files with links and images

Where are we?

You’ve seen how to use the require statement to insert one file into another. But do it the wrong way, and your site won’t work. Let’s look at the problem and a solution.

This lesson’s goals

By the end of this lesson, you should:

  • See how inserting files can break images, links, and other paths in your site.
  • Be able to fix the problem with a PHP variable.
  • Understand why this is a Big Win.

The dog site

Let’s look again at the dog site. Remember that it has two sections: articles and dog profiles. Here’s the directory tree:

Directory tree

Figure 1. Directory tree

The file header.inc is inserted in all of the other PHP files. For example, here’s how it is inserted into playing-with-dogs.php:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Playing with dogs | The Dog Site</title>
  </head>
  <body>
    <?php 
    require '../library/header.inc';
    ?>
    <h2>Article: Playing with dogs.</h2>
    <p>Pretend there is some content here.</p>
    <p><a href="index.php">Back to the article list</a></p>
  </body>
</html>

Figure 2. Code for playing-with-dogs.php

Line 9 inserts a header file that is the same across the entire site. The path says “go up to the parent directory, then down into the library directory, then insert the file header.inc.”

Here’s the header:

<!-- Header file, to be included into every page. -->
<h1>The Dog Site</h1>
<h2>Bringing you happiness since last week</h2>
<hr>

Figure 3. HTML for header.inc

You can try the site. You can also download a zip file with all of the PHP and other files.

The client wants to add the following logo to the header of every page:

Logo

Figure 4. Logo

“No problem,” we say. We put the image file (logo.png) in the library directory.

Logo in the library directory

Figure 5. Logo in the library directory

We change header.inc to:

<!-- Header file, to be included into every page. -->
<h1><img src="logo.png" alt="Logo">The Dog Site</h1>
<h2>Bringing you happiness since last week</h2>
<hr>

Figure 6. First try at the new header.inc

But this doesn’t work. Look at the home page. You’ll see something like:

Broken logo

Figure 7. Broken logo

Exactly what you see depends on how your browser handles broken image links.

So what’s the problem? Tell your browser to show you the HTML of the page (Control+U, or right click), and you’ll see:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Welcome | The Dog Site</title>
  </head>
  <body>
    <!-- Header file, to be included into every page. -->
<h1>The Dog Site</h1>
<h2><img alt="Logo" src="logo.png">Bringing you happiness since last week</h2>
<hr>    <p>Welcome to the dog site.</p>
    <p>See some <a href="dog-profiles/index.php">dogs</a>.</p>
    <p>Read an <a href="articles/index.php">article</a>.</p>
  </body>
</html>

Figure 8. HTML of index.php with broken logo

Again, the layout is a little strange around line 9, because the HTML here is inserted by PHP.

Line 10 contains the logo HTML. It’s exactly what we put into header.inc. So what’s the problem?

It has to do with the directory that logo.png is in. The file is in the library directory (See Figure 5). But the browser gets this code:

<img alt="Logo" src="logo.png">

There is no path, just logo.png. So the browser tries to load the image from the same directory that index.php is in.

index.php is in the root directory of the site (/). So the browser tries to load /logo.png.

Oops.

Every page on the site has the same problem. For example, /articles/why-dogs-are-great.php also has the line:

<img alt="Logo" src="logo.png">

inserted by the PHP require statement. The browser tries to load logo.php from the same directory as why-dogs-are-great.php. So it tries to load /articles/logo.png.

Argh!

How to fix it?

One choice is to copy logo.png to every directory, but that’s a really bad idea. If we wanted to change the logo for the site, we’d have to find and change every copy of logo.png.

Root relative URLs

A better choice (but not the best) is to change the inserted code to:

<!-- Header file, to be included into every page. -->
<h1><img src="/library/logo.png" alt="Logo">The Dog Site</h1>
<h2>Bringing you happiness since last week</h2>
<hr>

Figure 9. Root relative path in header.inc

The src attribute on line 2 has changed. It has the full path to the logo. This is a root relative URL. It says “Go to the root of the Web site, then go down into the library directory, then find logo.png.

This works! But…

Root relative URLs cause their own problems. They reduce the portability of a site, that is, your ability to move all of the files in the site around.

Remember that part of CoreDogs is not just learning the tech, but learning to think like a Weber. That is, to understand how people who create Web sites do their work.

On just about every project, you want to have a separate test version of the site. You tell your client “go look at the test version to see what I’m doing.” Then you can talk about changes.

Let’s say you put a test version of the site on http://mytestsite.com/dogsite/. You also have test versions of other sites, like http://mytestsite.com/catsite/ and http://mytestsite.com/hipposite/. This is common Weber practice; keep test versions on different domains from the product site.

But there’s a problem. You’ve moved the files into a directory called dogsite. The root relative path of the logo file is now /dogsite/library/logo.png. Your library file (header.inc) has /library/logo.png. So you have to change it to /dogsite/library/logo.png. Argh!!

But every time you copy the test version of the site to the production version, you have to remember to change back every root relative path. Double argh!!

And you have to do this for every root relative path! Not just the logo, but CSS files, JavaScript files, ...

Triple argh!!!

There are ways around this, too, but the fact is that root relative URLs are a pain.

But there’s a good solution. One that works well for template-based, dynamic Web sites.

Using a root path variable

What we’re going to do is change the HTML code that header.inc inserts. Instead of just src="logo.png", it will insert src="library/logo.png", or src="../library/logo.png", or src="../../../library/logo.png", or whatever each page needs.

We’ll do this by creating a variable on each page, and having header.inc use that variable.

If you remember back to JavaScript, you learned that a variable is a piece of computer memory that’s given a name. For example, here’s some JavaScript that takes whatever the user typed into a form field, puts it into the variable user_name, and tests whether the variable is empty.

user_name = $("#user").val();
if ( user_name == "" ) {
  alert("Sorry, you must enter a user name.");
}

Figure 10. JavaScript variable

PHP has variables, too. They act much like JavaScript variables. One difference is that their names all begin with $ (a dollar sign).

Our solution to the logo problem is in two steps:

  • On every page, create a variable containing the path from that page to the Web site’s root.
  • Use that variable in header.inc.

Let’s have a look. Here’s the directory tree again.

Directory tree

Figure 5 (again). Logo in the library directory

See the file playing-with-dogs.php in the articles directory? Here is the new version:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Playing with dogs | The Dog Site</title>
  </head>
  <body>
    <?php 
    $path_to_root = '..';
    require $path_to_root . '/library/header.inc';
    ?>
    <h2>Article: Playing with dogs.</h2>
    <p>Pretend there is some content here.</p>
    <p><a href="index.php">Back to the article list</a></p>
  </body>
</html>

Figure 11. Code for playing-with-dogs.php

Line 9 creates a variable called $path_to_root. Remember that variables begin with $ in PHP. The string “..” is put into the variable. You can put any characters you like into a variable. You can put numbers, too, and other things.

Notice that the statement ends with a semicolon (;). All statements must have a semicolon at the end. JavaScript is forgiving about this. PHP is not.

Line 10 is:

require $path_to_root . '/library/header.inc';

The period (.) is PHP’s string concatenation operator. It sticks two strings together. So 'Big' . 'ger' would yield 'Bigger'.

JavaScript uses plus (+) to mean the same thing, but if you remember, that causes problems, because + in JavaScript also means “add two numbers together.” PHP doesn’t have that problem. PHP has two operators (. and +) that mean two different things.

Let’s look again:

$path_to_root = '..';
require $path_to_root . '/library/header.inc';

Part of Figure 10 (again). Code for playing-with-dogs.php

.. is the path from playing-with-dogs.php to the Web root. /library/header.inc is the path from the Web root to the header file. Put them together, and you get ../library/header.inc, which is the path from playing-with-dogs.php to the header file.

So far, so w00f. As long as $path_to_root is set correctly for every page, the header file will be found correctly.

Note that this variable is declared in files that other files (like the header) are inserted into. These are the files with require statements, not the files that are inserted by the require statements.

What about the files that are inserted? They use the variable that was declared. Let’s look at the new header.inc.

<!-- Header file, to be included into every page. -->
<h1><img src="<?php print $path_to_root; ?>/library/logo.png" alt="Logo">The Dog Site</h1>
<h2>Bringing you happiness since last week</h2>
<hr>

Figure 12. Code for header.inc

Remember that the print statement outputs stuff into the HTML stream. It can output anything, anywhere. Including file paths!

If $path_to_root has ‘..’ in it, then line 2 will produce:

<h1><img src="../library/logo.png"

As before, $path_to_root has the path from the file that the code in Figure 12 is inserted into, to the Web root. /library/logo.png is the path from the Web root to the logo file. Put them together, and you have the path from the page the header code is inserted into, to the logo.

You can try this version of the site. Look at the HTML for a few pages, and check out the paths to the logo. By the way, you know that “..” means “go up one level to the parent.” “.” means “stay where you are.” You’ll see that on the home page.

You can download a zip file of the site if you want.

This approach works for all paths, whether they’re for images, links, CSS files, JavaScript files, or anything else. To see that, suppose we wanted to add a contact page to the site, with a link from the header of every page:

Link to contact page in header

Figure 13. Link to contact page in header

The contact page is at /contact.php, that is, in the Web root, along with the site’s home page.

Contact page in directory tree

Figure 14. Contact page in directory tree

We can add a contact link to every page on the site by only changing header.inc! W00f!

Here is what the new header.inc would look like.

<!-- Header file, to be included into every page. -->
<h1><img src="<?php print $path_to_root; ?>/library/logo.png" alt="Logo">The Dog Site</h1>
<h2>Bringing you happiness since last week</h2>
<p><a href="<?php print $path_to_root; ?>/contact.php">Contact us</a></p>
<hr>

Figure 15. New code for header.inc

Line 4 does the trick. $path_to_root contains the path from the page the code is inserted into to the Web root. The contact page is right there in the Web root; that’s just /contact.php.

Why this is a Big Win

You see how we could add the contact link by changing only one file? It wouldn’t matter how many pages were in the site. 10? 100? 10,000? Change just one file, and everything gets changed.

This is a Big Win for someone who works on Web sites, especially someone who does it professionally. Webers don’t think just about the end result, that is, the site that users will see. They also think about the work processes that create that site.

Webers think about productivity. The more productive they are, the better value they give employers and clients.

Exercise: Inserting files again

Modify your solution to the first Wombat exercise. Add a variable to wombat.php that has the path to the Web root. Use the variable in all the require statements.

Upload the file to your server. Put the URL below.

You can see my solution, but try it yourself first.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • Inserting files with paths in them – to images, links to pages, whatever – can break your site.
  • You can fix it with root relative paths, but this makes your site less portable.
  • A better way it to create a variable on each page, giving the path from that page to the root of the site. Add that variable in the inserted files.
  • This gives you a big productivity win. You can change an entire site just by changing one inserted file.

What now?

Using a variable like $path_to_root works with all kinds of paths in the HTML. We’ve fixed paths to images (the logo) and links (the contact page) in this lesson.

But there are often paths in JavaScript code. To images, for example. We need to fix them as well. How?

The JavaScript connection

Where are we?

In the previous lesson, we saw how to use a PHP variable to keep links and images working in a dynamic templating system. We got the Big Win of being able to reuse things like page headers on all pages of a site.

Let’s look at reusing JavaScript files. The goal is the same. Have one JavaScript file that’s included across all pages. If we want to change the JavaScript, change that one file, and every page is changed.

This lesson’s goals

By the end of this lesson, you should:

  • See that JavaScript contains file paths. The paths can be broken when inserting files with PHP.
  • See how to fix the problem by creating a JavaScript variable that’s set with PHP.

The problem

Here’s a page with an image and a link.

Sample page

Figure 1. Sample page

Try it, but ignore the Another link for the moment. (We’ll get back to it.)

Move the mouse over the image, and it changes. Move the mouse out, and it changes back. There are two different images. They are swapped in and out when the mouse moves over an <img> tag (more on that later).

Here’s the directory tree for the site.

Tree

Figure 2. Tree

There are two pages. One is /index.html, the one we’ve been looking at. There’s another page, /another/index.html. We’ll get to that later.

The images are in the library directory. The JavaScript that handles the image changing is in the file js-example.js. It’s in the library directory as well.

Why are the files in a separate library directory? Because they’re going to be reused across the site.

Here’s the HTML. No PHP yet.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript" src="library/js-example.js"></script>
    <title>JavaScript Example - Broken</title>
  </head>
  <body>
    <h1>JavaScript Example - Broken</h1>
    <h2>Home</h2>
    <p><img id="message" src="library/welcome.png" alt="Welcome"></p>
    <p><a href="another/index.html">Another</a></p>
  </body>
</html>

Figure 3. HTML

Look at line 12. There’s the <img> tag that shows the current image. It has an id of message.

The JavaScript that changes the image is in the file /library/js-example.js, referenced in line 6.

(Line 5 is a reference to the jQuery library on a Google server. It uses an absolute URL, and is not affected by the templating system.)

Here’s the contents of /library/js-example.js. This is the JavaScript code that does the image switching.

$(document).ready(function() {
  $('#message').hover(
      function(){
        $('#message').attr('src', 'library/go_away.png');
      },
      function(){
        $('#message').attr('src', 'library/welcome.png');
      }
    );
});

Figure 4. JavaScript

When the mouse goes over the image, the <img> tag shows go_away.png (line 4). When the mouse leaves the image, it’s set back to welcome.png (line 7).

So far, so good. Now try the page again. But this time, follow the Another link. This loads the page /another/index.php. As you can see in Figure 2, it’s in a subdirectory.

The image shows up. But when you move the mouse over the image, go_away.png doesn’t appear. What’s the deal?

It’s the same problem we had on the previous lesson. It’s the file paths. Remember, you’re looking at the file:

/another/index.html

This JavaScript executes:

... attr('src', 'library/go_away.png')

library/go_away.png is a relative path, so the browser looks for the file relative to the current page. It tries to load:

/another/library/go_away.png

But the file is not here.

Argh! Yet again!

You can download all the files from this example.

A JavaScript Fix

Let’s look at a fix that uses just JavaScript, not PHP. It uses a similar approach, though: Set a variable with the path to the root.

Let’s add a few lines to /index.html. This is the first page we saw, the one that worked. Here is the new head section of the page.

  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript" src="library/js-example.js"></script>
    <script type="text/javascript">
      var path_to_root = ".";
    </script>
    <title>JavaScript Example - Fixed with JavaScript</title>
  </head>

Figure 5. JavaScript fix

Lines 7 to 9 are new. Line 8 creates a JavaScript variable called path_to_root. It’s set with – guess what? – the path from /index.html (the file the code is in) to the root of the Web site. /index.html is in the root directory. “.” means “stay right here.”

We change /another/index.php as well, the page that didn’t work. Here’s what it changes to:

<script type="text/javascript">
  var path_to_root = "..";
</script>

Figure 6. JavaScript fix for another file

The “..” says, “To get to the root, go up to the parent.”

Let’s say we had the file hippo/llama/lion/dog.html. It’s code would be:

<script type="text/javascript">
  var path_to_root = "../../../";
</script>

Figure 7. A long path

dog.html is in the subdirectory lion. Line 8 says, “To get to the root, go up a level, go up a level, and go up a level.”

So, on each page, we create a JavaScript variable with the path from that page to the root of the Web page.

Now, what do we do with that variable? Let’s change /library/js-example.js. Remember that’s the file with the JavaScript code that handles the image switching. Here’s the new code:

$(document).ready(function() {
  $('#message').hover(
      function(){
        $('#message').attr('src', path_to_root + '/library/go_away.png');
      },
      function(){
        $('#message').attr('src', path_to_root + '/library/welcome.png');
      }
    );
});

Figure 8. New JavaScript

path_to_root has the path from the page to the root. /library/go_away.png is the path from the root to the new image file. So:

path_to_root + '/library/go_away.png'

is the path from the page to the new image file.

You can try it. You can also download all the files. Note that everything works just fine.

W00f!

Doing it with PHP

We have a JavaScript line like this, to get the JavaScript code in the library files to work right:

var path_to_root = "..";

We did the same thing in the last lesson, to get the file paths in the library HTML files (like the header and footer) to work right. But we used PHP:

$path_to_root = '..';

We could combine them, and get rid of the duplication. We’d end up with something like this for the head section of a page:

<head>
<?php $path_to_root = '..'; ?>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
  <script type="text/javascript" src="../library/js-example.js"></script>
  <script type="text/javascript">
    var path_to_root = "<?php print $path_to_root; ?>";
  </script>
  <title>JavaScript Example - Fixed with JavaScript</title>
</head>

Figure 9. PHP and JavaScript

Line 4 sets a PHP variable with the root path. Line 9 puts that into a JavaScript variable. With this approach, we only need to remember to set the PHP variable. The JavaScript variable will be set correctly.

/library/js-example.js doesn’t change. The variable it needs – path_to_root – is still set correctly.

For this to work, you need to rename the HTML files, giving them a .php extension. Remember that PHP statements are only executed in files that have the extension .php. Here is the new directory tree:

New tree

Figure 10. New tree

You can try this approach. You can also download the files.

Notice what you are doing here. Look at this again.

<script type="text/javascript">
  var path_to_root = "<?php print $path_to_root; ?>";
</script>

Part of Figure 9 (again). PHP and JavaScript

This puts something like this in the code sent to the browser:

<script type="text/javascript">
   var path_to_root = "..";
</script>

What we are doing is using PHP code to write JavaScript code!

This is so weird. But it’s done all the time. Browsers know how to run JavaScript, just as they know how to display HTML. We take advantage of this. We write PHP that writes JavaScript, just as we write PHP that writes HTML. The browser then does its thing with what we send it.

This is a good example of how computers work in layers. The PHP layer creates stuff for the HTML/JavaScript layer.

Layers

Figure 11. Layers

Each layer has languages it understands. The layer underneath it creates stuff in those languages.

Exercise: Fix the dog head

Try this site with a broken logo animation. Move the mouse over the logo image. It works on the first page, but not the others.

Download a zip file of the site. Fix it, using PHP and JavaScript variables.

You can check my solution and download a zip file of it.

But try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • JavaScript files contain file paths, just like HTML files. The paths can be broken when you use PHP to insert library files.
  • You can fix the problem by creating a JavaScript variable in each file containing the path to the Web root. That variable is added to the JavaScript code in the library files.
  • The JavaScript variable can have its value set by PHP.

What now?

Let’s see where we are.

  • You learned how to insert one file into another with PHP’s require statement.
  • You learned that that causes problems with file paths.
  • You learned how to fix them. Use a PHP variable to store the path from a page to the root. Use that variable in library files, like headers and footers.
  • You learned that reusing JavaScript files can cause similar problems.
  • You learned how to fix them. Set a JavaScript variable from the PHP variable. Use the JavaScript variable in JavaScript library files, like files with nav bar code.

Let’s put this all together, and create a complete dynamic templating system for a Web site.

A complete template system

Where are we?

You know how to use require statements. You know how to use variables to fix broken paths.

Now let’s put it all together. Let’s make a site that has a complete PHP templating system.

This lesson’s goals

By the end of this lesson, you should:

  • Review a site with layout, images, and animated navigation.
  • See how to create a complete PHP template system for this site.
  • Understand the productivity wins this gives us.

Site review

Let’s remake the dog site we looked at a couple of lessons ago. Here is its overall structure:

Site structure

Figure 1. Site structure

There are two sections in the site: dog profiles and articles. The profiles section has profiles of several dogs. The articles section has articles on dog issues.

Here is the home page of the new site.

Home page

Figure 2. Home page

Note that the buttons on the nav bar match the site structure in Figure 1.

The dogs link on the home page, and the dogs button in the nav bar, link to the main page of the dogs section of the site. Here it is:

Main page of the dogs section

Figure 3. Main page of the dogs section

Each link shows a page about a dog. Here’s a sample:

Dog profile

Figure 4. Dog profile

Each dog profile has a photo of the dog.

The other section of the site in Figure 1 is the articles section. Here is its main page:

Main page of the articles section

Figure 5. Main page of the articles section

The page has links to each article. Here’s one of them.

An article

Figure 6. An article

There is one other page: the contact form. A link to it is in the footer of every page. Here it is:

Contact page

Figure 7. Contact page

You can try the site yourself. You can also download the site.

Page layout

Each page has the same layout:

Page layout

Figure 8. Page layout

There are four regions on the page: top, left, center, and bottom. There is no right region in this layout.

The top region has a header. It contains two images (the logo and header image), and some text (a site subtitle). The content of the header – the images and text – is the same on every page.

The left region has a nav bar that is the same on every page. It has three images. The images switch when the mouse hovers over them:

Mouse hover effect

Figure 9. Mouse hover effect

The bottom region has a footer that is the same on every page. It contains some text, and a link to the contact page.

The center region has the main content for the page. It is different for each page.

HTML components

Let’s open up the HTML for a page, and see how it is organized.

HTML organization

Figure 10. HTML organization

Inside the body tag are the four regions. Each has each own div. The colors match the ones in Figure 8.

Figure 10 uses code adapted from the liquid layout in the page layout lesson. The right region was removed, and widths, margins, and padding adjusted.

There’s some more HTML at the start and end of the page. The code at the start gives meta data like title, loads CSS and JavaScript files, and includes any page-specific CSS and JavaScript. The code at the end of the page just closes the body and html tags, although it might do more on other sites.

Remember the PHP!

The HTML for a page is not stored in one file. A key point to remember is that the HTML in Figure 10 is assembled by PHP code.

The HTML is broken up into pieces. Most of the pieces are shared by all pages. The header, for example, is the same across the site (apart from adjusting file paths). The only thing that is completely different for each page is the content.

That’s what gives us the Big Win. There will be one file containing the HTML for the header, no matter how many pages are on the site. 100 pages? One header file. 10,000 pages? One header file.

Change that one file, and every page on the site changes. All 100. Or 10,000. A big productivity win.

Where are we? You’ve seen:

Let’s see what the site’s directory tree looks like.

File organization

Here it is:

Site directory tree

Figure 11. Site directory tree

The root directory of the site just has two files: index.php and contact.php. All of the other files are in directories.

There is a directory for each major part of the site in Figure 1.

Why create a directory for each part of the site? This is another example of how Webers think about productivity. Suppose a user sends this email:

Email about spelling error

Figure 12. Email about spelling error

We want to be able to fix that error quickly and easily. Notice how easy it is to find the file with the error. All the articles are in the articles directory, so go there. The names of the files match the titles of the articles, so we open the file playing-with-dogs.php.

On the other hand, suppose we’d put all of the files in one directory, and named them things like file17.php. It would be harder to find the file with the spelling error.

So the organization in Figure 11 gives us another productivity win. W00f!

OK, back to the files. Here is the tree again.

Site directory tree

Figure 11 (again). Site directory tree

All of the shared files are in library. The file dogsite.css is a site-wide CSS file, with the colors, fonts, page layout style rules, etc. dogsite.js has the JavaScript code for the nav bar image switching.

The .inc files contain the HTML pieces for each page. There’s one file for each chunk in Figure 10:

  • The page start HTML (start_page.inc)
  • The top region HTML (header.inc)
  • The left region HTML (left_nav.inc)
  • The bottom region HTML (footer.inc)
  • The page end HTML (end_page.inc).

We’ll look at the code for each one in a moment.

The rest of the files in library are images for the header and the nav buttons.

Inside the PHP page

Now let’s open up /index.php (the home page), and look at the PHP. Here is the entire code:

<?php 
//Path from this page to the site root.
$path_to_root = '.';
//Title of this page.
$page_title = 'Welcome';
require $path_to_root . '/library/start_page.inc';
require $path_to_root . '/library/header.inc';
require $path_to_root . '/library/left_nav.inc';
?>
<div id="center_region">
  <h1>Welcome</h1>
  <p>See some <a href="dog-profiles/index.php">dogs</a>.</p>
  <p>Read an <a href="articles/index.php">article</a>.</p>
</div>
<?php
require $path_to_root . '/library/footer.inc';
require $path_to_root . '/library/end_page.inc';
?>

Figure 13. PHP for /index.php (the home page)

Line 2 is a comment. Comments begin with //.

Line 3 sets the variable $path_to_root. It contains the path from this file to the site root. /index.php is already at the root, so the variable is set to '.', which means “the current directory.”

Line 5 sets the variable $page_title to, well, the page title. This is used in the title tag, as we’ll see.

Lines 6 to 9 insert chunks of HTML. start_page.inc (line 6) goes down to the body tag. header.inc (line 7) is the page header. left_nav.inc (line 8) is the nav bar.

Line 9 ends the PHP code. Regular HTML follows.

Lines 10 to 14 are the main content of the page, in the center region. This is what varies the most across the pages of the site.

Line 15 starts PHP mode again. Line 16 inserts footer.inc, with the HTML for the page footer. Line 17 inserts end_page.inc, with the HTML to close the body and html tags.

Let’s have a look at /dog-profiles/renata.php, the page showing Renata’s profile. Here’s what it looks like in a browser:

Dog profile

Figure 4 (again). Dog profile

Here’s the code.

<?php 
//Path from this page to the site root.
$path_to_root = '..';
//Title of this page.
$page_title = 'Renata';
require $path_to_root . '/library/start_page.inc';
require $path_to_root . '/library/header.inc';
require $path_to_root . '/library/left_nav.inc';
?>
<div id="center_region">
  <h1>Dog profile: Renata</h1>
  <p>Here is Renata.</p>
  <p><img src="renata.jpg" alt="Renata"></p>   
  <p><a href="index.php">Back to the dog list</a></p>
</div>
<?php
require $path_to_root . '/library/footer.inc';
require $path_to_root . '/library/end_page.inc';
?>

Figure 14. PHP for /dog-profiles/renata.php

Most of it is the same as the code for /index.php, in Figure 13. Let’s look at the differences.

Line 3 has the path to the root. /dog-profiles/renata.php is in a subdirectory, so its path is “..”.

Line 5 has the page title. It is different, of course.

The content for the center region starts at line 10. That’s completely different from page to page, of course.

And that’s it. Those are the only differences.

Notice how easy it is to create a new page. Copy one of the existing pages, change the root path and title, and add the unique content. The header, footer, and nav bar are all set up.

This is another productivity gain. Creating new pages just got a lot simpler.

Let’s look inside the included files.

Here’s start_page.inc, the HTML up to the body tag (see Figure 10).

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title><?php print $page_title; ?> | The Dog Site</title>
    <link rel="stylesheet" type="text/css" href="<?php print $path_to_root; ?>/library/dogsite.css">
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      var path_to_root = '<?php print $path_to_root; ?>';
    </script>
    <script type="text/javascript" src="<?php print $path_to_root; ?>/library/dogsite.js"></script>
  </head>
  <body>

Figure 15. start_page.inc

The page title is added in line 5. The root path is used in lines 6, 9, and 11. The rest is just plain HTML.

Here’s the code for header.inc.

<div id="top_region">
  <div id="logo_images">
    <img src="<?php print $path_to_root; ?>/library/logo.png" alt="Logo">
    <img alt="The Dog Site" src="<?php print $path_to_root; ?>/library/header.png">
  </div>
  <div id="subtitle">Bringing you happiness since last week</div>
</div>

Figure 16. header.inc

The root path is used on lines 3 and 4, to get the right paths to the image files.

Here is left_nav.inc.

<div id="left_region">
  <ul class="vertical_menu">
    <li>
      <a href="<?php print $path_to_root; ?>/index.php">
        <img id="home_button" src="<?php print $path_to_root; ?>/library/home_up.png" alt="Home">
      </a>
    </li>
    <li>
      <a href="<?php print $path_to_root; ?>/dog-profiles/index.php">
        <img id="dogs_button" src="<?php print $path_to_root; ?>/library/dogs_up.png" alt="Dogs">
      </a>
    </li>
    <li>
      <a href="<?php print $path_to_root; ?>/articles/index.php">
        <img id="articles_button" src="<?php print $path_to_root; ?>/library/articles_up.png" alt="Articles">
      </a>
    </li>
  </ul>
</div>

Figure 17. left_nav.inc

Each link and image needs to be adjusted for the root path.

Here’s footer.inc.

<div id="bottom_region">
  <p>© 2010 Nobody at all  
  |  <a href="<?php print $path_to_root; ?>/contact.php">Contact</a></p>
</div>

Figure 18. footer.inc

The path to the contact page needs the root path. Nothing else changes.

CC
CC

Hey, wait a minute.

Kieran
Kieran

What’s up?

CC
CC

Well, I’m just thinking. This seems awfully complex. Is it worth the effort?

Renata
Renata

Yes, I agree. I’m not sure I’ll like PHP.

Kieran
Kieran

Good point. Let’s talk about that a bit.

The source of complexity

Have a look at the directory tree again.

Site directory tree

Figure 11 (again). Site directory tree

There are lots of pieces here. Look at the library directory. There are fifteen files in there! Ack!

But let’s look more closely. Six of the files are button images. Another two are images for the header region. Nothing to do with PHP there, just regular images.

The files dogsite.css and dogsite.js have nothing to do with PHP either. They’re just regular things we saw in ClientCore.

What about the five .inc files? They are mostly plain HTML. There is only one PHP statement in them: print. As in:

<?php print $path_to_root; ?>

In fact, there is only one other type of PHP statement in the entire site. That’s require, as in:

require $path_to_root . '/library/start_page.inc';

The complexity comes not from the PHP. It comes from the underlying complexity of HTML, CSS, and JavaScript itself.

Web sites are complex. There is no doubt about it. That’s why people pay other people to create them.

The complexity comes not from the fact that any one thing is complex. But even in a small site, there are dozens of things to get right. Each of those small things combines to make a complex whole.

Don’t feel bad if this seems messy to you. It is messy. There’s a lot going on. But it’s your mastery of this that will make you a valuable employee, consultant, or whatever.

And one more thing about complexity. We added the PHP not to make the Web site look better. We added it to make it easier to change. Not only do we have the complexity of HTML, CSS, JavaScript, jQuery, and PHP. We have the complexity of work processes as well.

Ack! It’s enough to drive you to catnip.

The productivity wins

There are some big productivity wins here. Let’s review.

Win: new pages are easy to create

We want a new page, like a new article. We just copy /index.html (or another of the PHP files), change the root path and the title, and add the new content. We just work on what is unique on that new page. We don’t even have to think about the header, nav bar, etc.

Win: making changes in individual pages

Remember the email we looked at earlier, the one that told us about the spelling error? We could find the file to change very easily, because:

  • The directory tree matches the structure of the site, and
  • The file names match the page titles.

This has nothing to do with PHP. It’s just another example of how Webers think. Organize now to reduce problems later.

Big win: making site-wide changes

We can change the entire site by changing one file. For example, let’s say we wanted to add a new section to the site, for products. We add a new button to left_nav.inc, and every page in the site has the new button!

This is a Big Win. Your employer or client will be glad they hired you, because you give good value for money.

Summary

In this lesson, you:

  • Reviewed a site with layout, images, and animated navigation.
  • Saw how to create a complete PHP template system for this site.
  • Read about the productivity wins this gives us.

What now?

Time to do some more exercises.

Exercises: Web site templates

Exercise: Three poets

Create a Web site about three poets, like this one. Use the same method we used in the complete template system.

Download the images from the site.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Old things

Create a Web site about old things, like this one. Use the same method we used in the complete template system.

Download the images from the site.

You can download a zip file of my solution, but try it yourself first.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Fundamental forms

One of the most important uses of PHP is processing form data. This chapter shows you how to do it.

The next three chapters are about forms. This chapter starts off looking at the HTML for forms. Then it shows you how to do simple things with form data in PHP.

The next chapter looks at a couple of ways of saving form data: sending it by email, and saving it to a file.

The final form chapter talks about validation, that is, how you check data that users put into forms. We also look at how to make forms look good.

Forms in HTML

Where are we?

Let’s start by reviewing HTML form tags.

This lesson’s goals

By the end of this lesson, you should:

  • Know how browsers send form data to Web servers.
  • Know the difference between get and post.
  • Know what URL encoding does.
  • Know the basic attributes of the <form>, <input>, and <button> tags.

Form data flow

Remember HTTP? That’s the protocol that Web browsers and servers use to talk to each other.

There are various types of HTTP messages that browsers and servers send to each other. A common one is GET. This is usually how a browser asks a server for a page.

For example, suppose a user is looking at this page at http://dogsite.com/articles/index.php:

Articles list

Figure 1. Articles list

Here’s part of the HTML for the page:

<h1>Articles</h1>
<ul>
  <li><a href="why-dogs-are-great.php">Why dogs are great</a></li>
  <li><a href="playing-with-dogs.php">Playing with dogs</a></li>
</ul>

Figure 2. HTML for articles list

The user clicks the link “Playing with dogs.” The browser contacts the server at dogsite.com, and sends:

GET /articles/playing-with-dogs.php HTTP/1.1

The server sends back the HTML for playing-with-dogs.php to the browser.

When the browser sends a message like GET to the server, the browser sends other data as well, besides the URL. For example, it tells the server what type of browser it is.

GET /articles/playing-with-dogs.php HTTP/1.1
Host: dogsite.com
User-Agent: Mozilla/5.0

Host and User-Agent are headers. Each one is on a different line.

There are many header types besides Host and User-Agent. They specify the date and time the request was sent, languages the browser would prefer, and other stuff. A typical GET might have a half dozen headers.

Here’s the important bit for this chapter: the total HTTP request – the URL and the headers – can include data the user typed into form fields. This gives a way for browsers to send data to servers (usually it’s the other way around).

When the user submits a form in a Web browser, the form data travels along with a URL.

Form data attached to URL

Figure 3. Form data attached to URL

So, the form data is sent along with a URL. But which URL? What page does the browser send the data to?

The browser sends form data to a page that is specially written to handle form data. The page knows how to extract the data, and do something with it, like save it to a database. That’s one of the things you do in PHP: write pages that can handle form data.

More on that later. Right now, we’re just looking at the form.

A form example

Let’s create a form like this:

Simple form

Figure 4. Simple form

There are two text fields, and a button. Clicking the button submits the form. This means that the browser sends the form’s data to the server.

Here’s the HTML:

<h1>Simple Form</h1>
<form action="process-simple-form-get.php" method="get">
  <p>First name:
    <input name="first_name" type="text" size="20">
  </p>
  <p>Surname:
    <input name="surname" type="text" size="20">
  </p>
  <p>
    <button type="submit">Save</button>
  </p>
</form>

Figure 5. Simple form HTML

The form is wrapped in a <form> tag. This one has two attributes: action and method. There are other attributes we could use, but we don’t care about them yet.

The action says where the data should be sent. Remember that form data gets attached to a URL. This is the URL the data gets attached to.

The method attribute says how the form data will be attached. There are two ways: get and post.

The get method

get tacks the form data on to the URL itself. Suppose the user filled in the form like this:

Simple form with data

Figure 6. Simple form with data

The browser would create this URL:

/process-simple-form.php?first_name=Buffy&surname=Summers

Why this URL? Let’s look at the HTML again.

<h1>Simple Form</h1>
<form action="process-simple-form-get.php" method="get">
  <p>First name:
    <input name="first_name" type="text" size="20">
  </p>
  <p>Surname:
    <input name="surname" type="text" size="20">
  </p>
  <p>
    <button type="submit">Save</button>
  </p>
</form>

Figure 5 (again). Simple form HTML

The main part of the URL is in the form’s action property: process-simple-form.php. The browser adds a ? on the end, to indicate the start of the form data.

Then it adds the data in form fields. There’s the name of a field, an equals sign (=), and the field data. An ampersand (&) separates the fields.

So we end up with:

Get action

Figure 7. Get action

Try it. Notice that you can see the data in the address field of your browser.

Address bar for get

Figure 8. Address bar for get

The post action

Instead of the get action:

<form action=... method="get">

you can use post:

<form action=... method="post">

It works almost the same as get, except that the form data is passed through in an HTTP header, rather than concatenated to the URL. So the message from the browser to the server might look like this:

POST /articles/playing-with-dogs.php HTTP/1.1
Host: dogsite.com
User-Agent: Mozilla/5.0
Content-Length: 32
Content-Type: application/x-www-form-urlencoded

first_name=Buffy&surname=Summers

Figure 9. post message example

The form data itself has the same pattern; field name then value, field name then value, etc.

The advantage of the post method is that the data doesn’t show up in the browser’s address bar:

Address bar for post

Figure 10. Address bar for post

Only the URL shows. Headers do not.

You can try it. You won’t see the form data in the address bar of your browser.

Most Webers use method="post" because the form data doesn’t show up in the browser’s address bar. It’s a little more secure, and less annoying.

Exercise: Address form

Create a form like this:

Address form

Figure 1. Address form

Use the get method. Set the action to the page itself. For example, if you called the file ralph-the-wonder-form.html, use action="ralph-the-wonder-form.html".

When the form is filled in, it should look something like this:

Address form with data

Figure 2. Address form with data

Have a look at how the browser attaches data to the URL.

You can see my solution, but try it yourself first.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

URL encoding and character sets

Let’s do an experiment. Open the get version of the form. Try typing some special characters into the form. A special character is anything that is not a letter or a digit. For example:

Special characters in a form

Figure 11. Special characters in a form

Click the button, and look at the URL on the form processing page. You’ll see something like this:

process-simple-form-get.php?first_name=Renata+is+cute!&surname=Yes%2C+I+agree.

Some of the characters have been changed. The spaces became plus signs (+). The comma (,) became %2C.

This is called “URL encoding.” Browsers automatically URL encode data before sending it in a get or a post.

Why do browsers do this? URLs use a small character set. A “character set” is a limited group of characters to choose from. For example, when computers were first developed, most used the old US-ASCII character set. It has just the letters A to Z, the digits, punctuation(,.!; etc.) and a few other things. If you wanted to send characters with accents (like é) or currency symbols (like ¥), you were out of luck.

Other character sets were developed. Perhaps the most common international character set these days is UTF-8. It can represent thousands of characters; Cyrillic, Kanji, you name it, it’s probably in UTF-8.

The trouble is, URLs still use US-ASCII. It’s efficient, and is universally supported. So how do you send special characters in a URL?

The answer: URL encoding. It converts some special characters to a code. For example, it changes commas (,) into %2C. It also changes spaces into plus signs.

Browsers are not consistent in their URL encoding. Here’s how different browsers encode the data in Figure 11:

Firefox
first_name=Renata+is+cute!&surname=Yes%2C+I+agree.

Internet Explorer
first_name=Renata+is+cute%21&surname=Yes%2C+I+agree.

Chrome
first_name=Renata+is+cute!&surname=Yes,+I+agree.

Safari
first_name=Renata+is+cute%21&surname=Yes%2C+I+agree.

Only IE and Safari are the same.

Usually, you won’t need to worry about URL encoding. It happens automatically. But sometimes you’ll need to do it yourself in PHP. More on that later.

Exercise: Special characters in the address form

Open the page you created for the address form exercise. Type special characters into the fields. See how they are encoded in the URL generated by the form.

Make some notes below on how different characters (like %, $, +, and #) are encoded.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Text input fields

Let’s go back and look at the HTML for the text input fields.

<h1>Simple Form</h1>
<form action="process-simple-form-get.php" method="get">
  <p>First name:
    <input name="first_name" type="text" size="20">
  </p>
  <p>Surname:
    <input name="surname" type="text" size="20">
  </p>
  <p>
    <button type="submit">Save</button>
  </p>
</form>

Figure 5 (again). Simple form HTML

Line 4 sets up the first name field. <input> says it’s an input field. There are several types of input fields. Let’s stick with type="text" for now.

size sets the width of the field in number of characters. It does not limit the number of characters the user can type; size just affects the width of the field on the screen.

name is an important one. It’s the name given to the value when the data is sent to the server. You can see it in action on the last line of Figure 9.

The button

Nothing happens until the user hits the button. The HTML for our button is:

<button type="submit">Save</button>

Notice the type attribute. Earlier, we’ve seen this as type="button". When type="submit", the browser sends form data to the server when the button is pressed.

Summary

In this lesson, you learned:

  • Browsers send form data to Web servers along with URLs.
  • get attaches form data to a URL. post puts it in a separate HTTP header.
  • URL encoding handles special characters in URLs.
  • The basic attributes of <form> are action and method.
  • The basic attributes of <input> are type, name, and size.
  • The basic attribute of <button> is type.

What now?

Let’s see how you can write PHP to handle form data.

Form data in PHP

Where are we?

We just looked at the HTML tags <form>, <input>, and <button>, and saw how browsers send form data to Web servers. Let’s see how you can retrieve that data using PHP.

This lesson’s goals

By the end of this lesson, you should:

  • Know how PHP fetches form data.
  • Know how to fetch data from post and get forms.

Retrieving form data

Here’s some HTML from the previous lesson.

<h1>Simple Form</h1>
<form action="process-simple-form-post.php" method="post">
  <p>First name:
    <input name="first_name" type="text" size="20">
  </p>
  <p>Surname:
    <input name="surname" type="text" size="20">
  </p>
  <p>
    <button type="submit">Save</button>
  </p>
</form>

Figure 1. Simple form HTML

When the user clicks the button, the browser gets the data from the form, and sends it to the page given in the form’s action attribute. That’s process-simple-form-post.php.

You can try it.

Here’s process-simple-form-post.php.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
  </head>
  <body>
    <?php
    $first_name = $_POST['first_name'];
    $surname = $_POST['surname'];
    ?>
    <p>Your name is <?php print $first_name . ' ' . $surname; ?>.</p>
  </body>
</html>

Figure 2. process-simple-form-post.php

Line 9 fetches the data for one of the fields and puts it into a variable. Here is the statement again:

Fetching from $_POST

Figure 3. Fetching from $_POST

$_POST is an array holding all of the form data. You don’t need to know much about arrays yet; just follow the pattern above.

In the [''], you give the name of the HTML input field. This was specified in the HTML:

<input name="first_name" type="text" size="20">

There is a variable name in Figure 3. That’s where the form data will go.

You can name the PHP variable anything you like. Common practice is to give it the same name as the HTML form field. This makes it easier to match the HTML code with the PHP code.

Line 10 fetches the other form field’s data. It works the same way.

Line 12 in Figure 2 outputs the values. As before, print just inserts stuff into the PHP program’s output stream. Let’s look at this:

$first_name . ' ' . $surname

It’s the string concatenation operator at work again. The line tells PHP to:

Take the contents of $first_name, stick a space on the end of that, and stick the contents of $surname on the end of that.

The space (’ ‘ in the statement) is a string constant. The characters between the quotes are used exactly as-is, with no changes. For example:

$first_name . ' (Killer) ' . $surname

If $first_name was “Bugs” and $surname was “Bunny”, you would get:

Bugs (Killer) Bunny

The spaces inside the quotes are important. If we left them out:

$first_name . '(Killer)' . $surname

We’d get:

Bugs(Killer)Bunny

The spaces outside the quotes don’t matter to the PHP interpreter. We could have:

$first_name.' (Killer) '.$surname

It would still work. Some people think the extra spaces make the line more readable.

Single versus double quotes

Here’s part of line 12 again:

print $first_name . ' ' . $surname;

We could have written it like this:

print "$first_name $surname";

This version has double quotes. What’s the difference?

  • When PHP sees single quotes ('), it returns exactly what is between the quotes, with no changes.
  • When PHP sees double quotes ("), it replaces variable names with their contents, and then returns the result.

JavaScript doesn’t do this. Only PHP.

Here are some more examples.

<?php
$a = 'Willow';
$b = 'Rosenberg';
print "<p>$a $b</p>";
  //Prints <p>Willow Rosenberg</p>
print '<p>$a $b</p>';
  //Prints <p>$a $b</p>
print '<p>'. $a . ' ' . $b . '</p>';
  //Prints <p>Willow Rosenberg</p>
?>

Figure 4. Single versus double quotes

You can try it.

Post or get?

Here’s one of those statements again:

$first_name = $_POST['first_name'];

That only works for forms that use method="post":

<form action="..." method="post">

If you used get:

<form action="..." method="get">

then you fetch the form data in PHP like this:

$first_name = $_GET['first_name'];

If your PHP code isn’t fetching form data, check that you are using the right method.

Exercise: Address form data

In a previous exercise, you created a form like this:

Address form

Figure 1. Address form

Add a PHP page that processes this data, producing something like:

Address output

Figure 2. Address output

You can try my solution. You can also download the files.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • Use $_POST[‘name’] to fetch data from forms submitted with the post method.
  • Use $_GET[‘name’] to fetch data from forms submitted with the get method.

What now?

Let’s see how you deal with an annoying thing PHP does to quotes in form data.

Magic quotes and strings

Where are we?

You know how to get form data into a PHP program. Let’s talk about an annoying thing that PHP does to text strings.

This lesson’s goals

In this lesson, you learn:

  • That PHP’s magic quotes feature adds extra backslashes to text.
  • How to use the stripslashes() function to fix the problem.

Strings with quotes

Look at the name form again. Here it is, with some data:

Input

Figure 1. Input

When the user clicks the button, the form data is sent to this page:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
  </head>
  <body>
    <?php
    $first_name = $_POST['first_name'];
    $surname = $_POST['surname'];
    ?>
    <p>Your name is <?php print $first_name . ' ' . $surname; ?>.</p>
  </body>
</html>

Figure 2. Process form data

Here is the output:

Output

Figure 3. Output

No problem.

But suppose the user has a name with a quote (’) in it, like this:

Input with quote

Figure 4. Input with quote

This is the result:

Output with quote

Figure 5. Output with quote

There’s a backslash in the output (\). You can try it.

Huh? Where did that backslash come from?

The magic quotes setting

PHP has a bunch of settings that change how it works. For example, you can set the maximum time a PHP program is allowed to run.

One of the settings is “magic quotes.” It’s either on or off. When it’s on, PHP looks at all the text data coming in from forms. If it finds a quote, it will put a backslash in front of it. So it converts De'ev to De\'ev.

If magic quotes is off, no conversion happens. De'ev stays De'ev.

Here’s the output again:

Output with quote

Figure 4 (again). Output with quote

This came from a PHP program running on a server. The server had magic quotes on. Hence the backslash.

I turned magic quotes off, and ran the program again. I got:

No backslash

Figure 5. No backslash

Renata
Renata

Er, why did the PHP people do this magic quotes thing?

Kieran
Kieran

It helps with security in some situations. But it causes problems, as well. For De’ev, and others.

Many people think that magic quotes was a bad idea. It didn’t solve many security problems. And anyway, there are better ways to handle those security issues it did solve.

Magic quotes will be dropped in the next major version of PHP.

Getting rid of the backslashes

There are two ways to get rid of the extra backslashes. One way is to turn the magic quotes setting off in PHP. But to do that, you need access to a special settings file. You may or may not have access, depending on your hosting account.

The second way is to call PHP’s stripslashes() function. It does what its name says. You give it a string, and it goes through and removes the backslashes.

Here’s the code again, with a slight change:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
  </head>
  <body>
    <?php
    $first_name = $_POST['first_name'];
    $first_name = stripslashes($first_name);
    $surname = $_POST['surname'];
    $surname = stripslashes($surname);
    ?>
    <p>Your name is <?php print $first_name . ' ' . $surname; ?>.</p>
  </body>
</html>

Figure 6. Using stripslashes()

Lines 10 strips out the slashes from $first_name, and puts the result back into $first_name. Line 12 does the same for $surname.

You could also write it like this:

<?php
$first_name = stripslashes($_POST['first_name']);
$surname = stripslashes($_POST['surname']);
?>
<p>Your name is <?php print $first_name . ' ' . $surname; ?>.</p>

Figure 7. Using stripslashes() again

It works the same. Line 9 takes the value of the form field first_name, strips out the backslashes, and puts the result into $first_name.

You could also do this:

<?php
$first_name = $_POST['first_name'];
$surname = $_POST['surname'];
?>
<p>Your name is <?php print stripslashes($first_name) . ' ' . stripslashes($surname); ?>.</p>

Figure 8. Using stripslashes() again again

This version moves the calls to stripslashes() to the output.

Exercise: Remove the slashes

Write an HTML page with a form that has one field, like this:

Input

Figure 1. Input

When the button is clicked, the data is sent to a PHP page that shows whatever data the user typed. If there is a quote (’) in the data, no slashes appear in the output. Like this:

Output

Figure 2. Output

You can try my solution to see how it works. You can download my solution, but do the exercise yourself before you look at the files.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

In this lesson, you learned:

  • That PHP’s magic quotes feature adds extra backslashes to text.
  • How to use the stripslashes() function to fix the problem.

What now?

Let’s see how you can do computations on form data.

Doing computations

Where are we?

You know how to create forms, and get user input into PHP programs. Now let’s do something with the data.

This lesson’s goals

By the end of this lesson, you should:

  • Know how to use PHP variables in computations.
  • Be able to control format of output numbers.
  • Handle radio button input.

A simple order form

Suppose we start a company that sells two dog toys, frisbees and giant chew ropes. Frisbees are the great flying disks from Wham-o. Many dogs love chasing frisbees!

We want to sell the toys over the Web. Let’s create a form like this:

Order form

Figure 1. Order form

The user enters the number of each item s/he wants to order:

Order form with data

Figure 2. Order form with data

The user clicks the button, and a confirmation page appears.

Order confirmation

Figure 3. Order confirmation

We’ll fix the number formats later.

Here’s the HTML for the order form.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Simple Order Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <h1>Simple Order Form</h1>
    <form action="process-order.php" method="post">
      <p>
        Frisbees:
        <input type="text" name="frisbees" size="3">
        at $8.95 each
      </p>
      <p>
        Giant chew ropes:
        <input type="text" name="giant_chew_ropes" size="3">
        at $12.95 each
      </p>
      <p>
        <button type="submit">Order</button>
      </p>
    </form>
  </body>
</html>

Figure 4. Order form HTML

Nothing new here. Just two text input fields, as before. One field is for the number of frisbees the user wants. The other is for the number of giant chew ropes.

Here’s the code for process-order.php, the file listed in the action attribute of the form.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Order Processed</title>
  </head>
  <body>
    <h1>Order Processed</h1>
    <?php
    //Get the input.
    $frisbees = $_POST['frisbees'];
    $giant_chew_ropes = $_POST['giant_chew_ropes'];
    //Compute totals.
    $frisbees_total = $frisbees * 8.95;
    $giant_chew_ropes_total = $giant_chew_ropes * 12.95;
    $order_total = $frisbees_total + $giant_chew_ropes_total;
    //Output totals.
    ?>
    <p>Thank you for your order.</p>
    <table cellpadding="5" cellspacing="0" border="1">
      <tr>
        <th>Product</th>
        <th>Number ordered</th>
        <th>Unit price</th>
        <th>Total price</th>
      </tr>
      <tr>
        <td>Frisbees</td>
        <td><?php print $frisbees; ?></td>
        <td>$8.95</td>
        <td>$<?php print $frisbees_total; ?></td>
      </tr>
      <tr>
        <td>Giant chew ropes</td>
        <td><?php print $giant_chew_ropes; ?></td>
        <td>$12.95</td>
        <td>$<?php print $giant_chew_ropes_total; ?></td>
      </tr>
      <tr>
        <td colspan="3">Total</td>
        <td>$<?php print $order_total; ?></td>
      </tr>
    </table>
  </body>
</html>

Figure 5. Order processing code

Lines 11 and 12 fetch the data from the form, and put them into PHP variables:

$frisbees = $_POST['frisbees'];
$giant_chew_ropes = $_POST['giant_chew_ropes'];

Line 14 works out the total cost of the frisbees:

$frisbees_total = $frisbees * 8.95;

It’s the number of frisbees ordered (in $frisbees) times the price of one frisbee (8.95). The asterisk (*) means “multiply.” The result is put into $frisbees_total.

Line 15 works out the total cost of the giant chew ropes:

$giant_chew_ropes_total = $giant_chew_ropes * 12.95;

It’s the number of giant chew ropes ordered (in $giant_chew_ropes) times the price of one giant chew rope ($12.95). The result is put into $giant_chew_ropes_total.

Line 16 works out the total cost of the order:

$order_total = $frisbees_total + $giant_chew_ropes_total;

It’s the total cost of the frisbees (in $frisbees_total) plus the total cost of the giant chew ropes (in $giant_chew_ropes_total). The order total is put into the variable $order_total.

The results are output in a <table>. You learned about tables in the lesson A Web page with tables.

Exercise: Circle area

Write a program that computes the area of a circle. Create a form like this:

Form

Figure 1. Form

The user enters a number, and clicks the button:

Form with data

Figure 2. Form with data

The user then sees:

Output

Figure 3. Output

The formula is:

area = 3.14159 x radius x radius

You can try my solution, and download the files. Of course, try it yourself first.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Fixing the number formats

We’ve seen strange number formats, like this:

Order confirmation

Figure 3 (again). Order confirmation

The prices are messed up. For example, 17.9 should be 17.90.

This is easy to fix with the number_format() function. You tell it the number you want to format and how many decimals you want, and it will return a formatted value. For example:

$x = 17.9;
print number_format($x, 2);

This will output 17.90.

We can change the order processing program like this:

<tr>
  <td>Frisbees</td>
  <td><?php print $frisbees; ?></td>
  <td>$8.95</td>
  <td>$<?php print number_format($frisbees_total, 2); ?></td>
</tr>
<tr>
  <td>Giant chew ropes</td>
  <td><?php print $giant_chew_ropes; ?></td>
  <td>$12.95</td>
  <td>$<?php print number_format($giant_chew_ropes_total, 2); ?></td>
</tr>
<tr>
  <td colspan="3">Total</td>
  <td>$<?php print number_format($order_total, 2); ?></td>
</tr>

Figure 6. Formatting numbers

You can try it.

Exercise: Cylinder volume

Write a program that computes the volume of a cylinder. Create a form like this:

Form

Figure 1. Form

When the user clicks the button, the result appears:

Output

Figure 2. Output

Make sure that the volume is shown to three decimal places.

You can try my solution. You can download the files. Try it yourself first, of course.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Adding shipping

Let’s add shipping to our form.

Order form with shipping

Figure 7. Order form with shipping

The form uses radio buttons for the shipping method. The user can select only one. You can try it.

Here’s the output:

Order confirmation with shipping

Figure 8. Order confirmation with shipping

Here’s the new HTML.

<div>
  <p>Shipping method</p>
  <p>
    <input type="radio" name="shipping_method" value="standard">
    Standard ($5)
  </p>
  <p>
    <input type="radio" name="shipping_method" value="overnight">
    Overnight ($10)
  </p>
</div>

Figure 9. HTML for shipping method

type="radio" tells the browser you want to use a radio button.

Only one of the radio buttons can be selected. When one is clicked, the other is turned off.

There can be more than one group of radio buttons on a form. Like this:

Radio button groups

Figure 10. Radio button groups

The user can select one button in each group:

Selected buttons

Figure 11. Selected buttons

If the user clicks on a radio button, the others are turned off. But which others? Only the ones in the same group.

Selecting different buttons

Figure 12. Selecting different buttons

How does the browser know which radio buttons belong together? Here’s the HTML:

<p>Favorite animal</p>
<blockquote>
  <input type="radio" name="animal" value="Dog">Dog<br>
  <input type="radio" name="animal" value="Cat">Cat<br>
  <input type="radio" name="animal" value="Horse">Horse<br>
  <input type="radio" name="animal" value="Maggot">Maggot<br>
</blockquote>
<p>Favorite color</p>
<blockquote>
  <input type="radio" name="color" value="Green">Green<br>
  <input type="radio" name="color" value="Blue">Blue<br>
  <input type="radio" name="color" value="Purple">Purple<br>
</blockquote>

Figure 13. Radio button HTML

The name attribute of the radio buttons defines the groups. Radio buttons what belong together have the same name.

Let’s go back to the shipping method for the dog toys. Here is the HTML:

<div>
  <p>Shipping method</p>
  <p>
    <input type="radio" name="shipping_method" value="standard">
    Standard ($5)
  </p>
  <p>
    <input type="radio" name="shipping_method" value="overnight">
    Overnight ($10)
  </p>
</div>

Figure 9 (again). HTML for shipping method

Both radio buttons have the same name: shipping_method (lines 24 and 28). That’s how the browser knows they belong together.

The PHP page will get one piece of form data called shipping_method. It’s value will be either standard or overnight. Which one the PHP gets will depend on what the user chose.

Here’s the PHP page.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Order Processed</title>
  </head>
  <body>
    <h1>Order Processed</h1>
    <?php
    //Get the input.
    $frisbees = $_POST['frisbees'];
    $giant_chew_ropes = $_POST['giant_chew_ropes'];
    $shipping_method = $_POST['shipping_method'];
    //Compute product totals.
    $frisbees_total = $frisbees * 8.95;
    $giant_chew_ropes_total = $giant_chew_ropes * 12.95;
    //Compute shipping cost.
    if ( $shipping_method == 'standard' ) {
      $shipping_cost = 5;
    }
    else {
      $shipping_cost = 10;
    }
    $order_total = $frisbees_total 
      + $giant_chew_ropes_total
      + $shipping_cost;
    //Output totals.
    ?>
    <p>Thank you for your order.</p>
    <table cellpadding="5" cellspacing="0" border="1">
      <tr>
        <th>Product</th>
        <th>Number ordered</th>
        <th>Unit price</th>
        <th>Total price</th>
      </tr>
      <tr>
        <td>Frisbees</td>
        <td><?php print $frisbees; ?></td>
        <td>$8.95</td>
        <td>$<?php print number_format($frisbees_total, 2); ?></td>
      </tr>
      <tr>
        <td>Giant chew ropes</td>
        <td><?php print $giant_chew_ropes; ?></td>
        <td>$12.95</td>
        <td>$<?php print number_format($giant_chew_ropes_total, 2); ?></td>
      </tr>
      <tr>
        <td colspan="3">Shipping</td>
        <td>$<?php print number_format($shipping_cost, 2); ?></td>
      </tr>
      <tr>
        <td colspan="3">Total</td>
        <td>$<?php print number_format($order_total, 2); ?></td>
      </tr>
    </table>
  </body>
</html>

Figure 14. Code for PHP file

Line 13 gets the shipping_method. Lines 18 to 23 work out the shipping cost.

PHP’s if statement is much like JavaScript’s if statement, that we covered earlier. The general form is:

if ( expression ) {
    Do this if expression is true;
}
else {
    Do this if expression is false;
}

In line 18,

$shipping_method == 'standard'

is true if $shipping_method has exactly “standard” in it. Not “STANDARD” or “Standard” or “ standard “ – just “standard”.

Note the ==. Don’t use just =. = means “take the thing on the right and put it in the variable on the left.” Like lines 11, 12, 13, 15, 16, etc. If you want to compare, use ==.

You can use other things besides ==. For example:

if ( age > 21 ) {
if ( age <= 65 ) {
if ( color != 'blue') {

> is greater than. <= is less than or equal to. != means “is not equal to.”

Summary

You learned:

  • Know how to use PHP variables in computations.
  • Be able to control format of output numbers.
  • Handle radio button input.

What now?

Time for some exercises.

Exercises: Fundamental forms

Exercise: Order ship models

Create an order form for model ships. The form should look like this:

Order form

Figure 1. Order form

After clicking the button, you see:

Output

Figure 2. Output

You can try my solution. You can also download the files. Try it yourself first, of course.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Saving form data

Send form data by email

Where are we?

You learned how to create forms, and use PHP to process form data. This lessons looks at how to email it.

This lesson’s goals

By the end of this lesson, you will learn:

  • How to use the <textarea> tag.
  • How to use PHP’s mail function to send an email message.

A contact form

In an earlier chapter, we created a template-based site with PHP. It included a contact form that looked like this:

Incomplete contact page

Figure 1. Incomplete contact page

Now we can finish it. This is what we want:

Complete contact page

Figure 2. Complete contact page

Here’s the code for the form.

<?php 
//Path from this page to the site root.
$path_to_root = '.';
//Title of this page.
$page_title = 'Contact';
require $path_to_root . '/library/start_page.inc';
require $path_to_root . '/library/header.inc';
require $path_to_root . '/library/left_nav.inc';
?>
<div id="center_region">
  <h1>Contact</h1>
  <p>Please complete the form and click Send.</p>
  <form action="send-contact-message.php" method="post">
    <p>
      Subject<br>
      <input type="text" name="subject" size="40">
    </p>
    <p>
      Comment<br>
      <textarea name="message" rows="5" cols="40"></textarea>
    </p>
    <p>
      <button type="submit">Send</button>
    </p>
  </form>
  <p><a href="index.php">Home</a></p>
</div>
<?php
require $path_to_root . '/library/footer.inc';
require $path_to_root . '/library/end_page.inc';
?>

Figure 3. Code for contact form

Lines 1 to 9 and 28 to 31 are the templating system at work. The form itself is the HTML from lines 13 to 35.

There’s a new form element on line 20: <textarea>. It creates the multi-line field in Figure 2. The rows attribute specifies the number of rows the field has. The cols attribute specifies the number of columns.

If we want the field to have contents when the page loads, we could add it inside the tag. For instance:

<textarea>(Type your message here)</textarea>

would show this when the page loads:

textarea with content

Figure 4. <textarea> with content

Emailing the message

send-contact-message.php (named in the action property of the form) will send the message. Here’s the code:

<h1>Contact</h1>
<?php
//Get message data.
$subject = $_POST['subject'];
$message = $_POST['message'];
//Email it.
mail(
  'email address',           //Where to send
  "Contact form - $subject", //Email subject
  $message                   //Message body
);
?>
<p>Thank you! Your message has been sent.</p>

Figure 5. send-contact-message.php

I’ve omitted the PHP that adds the header, nav bar, etc.

Lines 4 and 5 get the subject and the message. Nothing new there.

Lines 7 to 11 sends the email using the mail() function. We give the function three arguments:

  • The email address to send to. The usual form, e.g., you@yoursite.com.
  • The subject of the email.
  • The message.

You should replace the email address with your own, if you want to test the program.

A couple of things to notice. First, I split the function call over a few lines, to make it easier to read. I added comments at the end of each line, explaining the arguments.

Line 9 adds the text “Contact form – “ in front of the subject. This will help the recipient know where the message came from.

The mail() function will work only if your server is set up correctly. PHP has to know what program on the server to use to send email.

Most shared hosting servers will be set up correctly. However, your test server on your local machine (e.g., your XAMPP server) might have some trouble. That’s OK; it’s just for testing anyway.

You can try it.

Sending to Gmail

I’ve had trouble sending email to Gmail using the basic mail() function. Here’s the code I used in an application that worked:

$to_address = 'email address'; 
$subject = 'Card order';
$message = "Stuff stuff, thing thing";
$from = 'another email address';
$headers = "MIME-Version: 1.0\n";
$headers .= "Content-type: text/plain; charset=iso-8859-2\n";
$headers .= "From: $from\n" . "Reply-To: $from\n" 
  . "X-Mailer: PHP/" . phpversion() . "\n";
mail($to_address, $subject, $message, $headers);

Figure 6. Sending email to Gmail

You may need to use this approach for some other services as well.

Summary

In this lesson, you learned:

  • How to use the <textarea> tag.
  • How to use PHP’s mail function to send an email message.

What now?

Let’s see how you can store form data to a file.

Saving form data to a file

Where are we?

You just learned how to send form data by email. But sometimes you want that data to be immediately available on your server. Let’s see one way to do that.

This lesson’s goals

By the end of this lesson, you should:

  • Know how to append form data to a file.
  • Be able to read back data from the file.
  • Know how to do some basic security stuff.

Comments – What the user sees

Let’s say you want people to be able to add comments to a Web page. Here’s what the user sees when there are no comments:

No comments

Figure 1. No comments

There are two pieces to the comment area:

Comment area pieces

Figure 2. Comment area pieces

First, there’s the current comments. There are none in this case.

Second, there’s a form for adding new comments. This will always be the same, no matter how many comments there are.

Users add comments by typing in the text field and clicking the button:

Adding a comment

Figure 3. Adding a comment

When they click Save, the page reloads, and shows the new comment in the comment area.

Comment showing

Figure 4. Comment showing

Where do comments go?

We need a place to keep comments that people enter, so we can read them back and show them on Renata’s page.

For this example, we’ll store them in a file. When there’s a new comment, we’ll append it to the file. Append means “add to the end.”

When Renata’s page – renata.php – loads, we’ll show the comments. We’ll put some PHP on the page that will read the comments in and insert them into the HTML.

This will work most easily if the comments file already contains HTML tags. But people who type comments don’t type HTML. They just type text:

Adding a comment

Figure 3 (again). Adding a comment

So we’ll add some HTML tags around what they type.

Whenever you save data that people type, you have to worry about security. You don’t want hackers typing comments that, for example, contain JavaScript. We’ll address that later.

Flow of control

Let’s draw a picture of what happens behind the scenes. It will show us the flow of control, that is, what happens first, what happens next, and so on.

Comment control flow

Figure 5. Comment control flow

The browser asks the server for renata.php (1). PHP code in renata.php reads the comments file, and inserts the comments into the HTML (2). renata.php also contains HTML to show the form (the <form> tag and friends).

Once the browser has shown the page, it waits for the user to do something. Suppose the user types a comment, and clicks the Save button (3). The browser looks at the action property of the form: action="save-renata-comment.php". So it asks the server for that page, and sends the form data along with the request (4).

The server loads save-renata-comment.php. Code in that file takes the new comment, wraps it in a <p> tag, and appends it to the comments file. Then it redirects the browser. That is, it tells the browser to load another page. Which one? renata.php (6).

So the browser loads renata.php (1). Code in renata.php reads the comments file (2), which now has the new comment on the end. So the new comment appears.

Phew! That’s a lot of work! But it’s a lot of work for the computers, not for us. We tell them what to do, and they do it.

Let’s have a look at the pieces.

Showing comments

Here’s what the comments file looks like after a comment has been added:

<p>You are so cute!</p><hr>

Figure 6. Comments file with one comment

We need to read that in to the right place in renata.php (2 in Figure 5). Here’s the code for the entire file. (I’ve wrapped it in the template code we saw earlier.)

<?php 
//Path from this page to the site root.
$path_to_root = '..';
//Title of this page.
$page_title = 'Renata';
require $path_to_root . '/library/start_page.inc';
require $path_to_root . '/library/header.inc';
require $path_to_root . '/library/left_nav.inc';
?>
<div id="center_region">
  <h1>Dog profile: Renata</h1>
  <p>Here is Renata.</p>
  <p><img src="renata.jpg" alt="Renata"></p> 
  <h2>Comments</h2>
  <?php
  if( file_exists('renata_comments.txt') ) {
    readfile('renata_comments.txt');
  }
  else {
    print '<p>There are no comments yet.</p>';
  }
  ?>
  <h2>Add a comment</h2>
  <form action="save-renata-comment.php" method="post">
    <p>
      <textarea rows="3" cols="30" name="comment"></textarea>
    </p>
    <p>
      <button type="submit">Save</button>
    </p>
  </form>
  <p><a href="index.php">Back to the dog list</a></p>
</div>
<?php
require $path_to_root . '/library/footer.inc';
require $path_to_root . '/library/end_page.inc';
?>

Figure 7. renata.php

Everything up to line 9 is templating code. The real stuff starts on line 10.

Lines 10 to 14 are simple HTML. That’s the main content of the page. The comment section follows.

There may be comments, or there may not be. If there are comments, we want to show them. Otherwise, we want to tell the user that there are no comments yet.

We’re going to store comments in the file renata_comments.txt. The file will be created when the first comment is added. If there are no comments, the file won’t even exist.

Here’s line 16:

if( file_exists('renata_comments.txt') ) {

file_exists() is a function that returns true if the file is found. If it is, line 17 runs:

readfile('renata_comments.txt');

This reads the file, and inserts it into the HTML output stream.

If the file does not exist, then file_exists('renata_comments.txt') is false, and line 20 runs:

print '<p>There are no comments yet.</p>';

This outputs the text in the quotes. Remember that we need to include the HTML tags.

The rest of the page uses stuff we’ve seen before.

Adding a new comment

The user types a comment and clicks the same button. The browser takes the URL in the form’s action attribute – save-renata-comment.php – and goes there, sending the contents of the comment field (4 in Figure 5).

Here’s save-renata-comment.php:

<?php
//Get the user's comment.
$comment = $_POST['comment'];
//Append it to the comments file.
$f = fopen('renata_comments.txt', 'a');
fwrite($f, "<p>$comment</p><hr>");
fclose($f);
//Jump back to Renata's page.
header('location:renata.php');
?>

Figure 8. save-renata-comment.php

Here’s what happens when the user clicks the Send button.

Browser
Browser
Web server
Web server
PHP interpreter
PHP interpreter
Gimme save-renata-comment.php.
And here's some form data:
comment=You+are+so+cute!
   
  (Fetches save-renata-comment.php from disk.)
Oh! This is a PHP file.
Hey, PHP interpreter! Run this.
 
    Get the form data.
Save data to the file.
Hey, server, here's the result. No HTML, but I have an HTTP header for you:
Location: renata.php
  Hey, browser. Here's the contents of save-renata-comment.php. Just an HTTP header:
Location: renata.php
 
OK.
Hey, send me renata.php.
   
  (Fetches renata.php from disk.)
Oh! This is a PHP file.
Hey, PHP interpreter! Run this.
 
    HTML, send it through.
Here's some PHP.
Read the file, put it in the HTML.
More HTML.
Hey, server, here's the result.
  Hey, browser. Here's the contents of renata.php.  
OK.
(Render for user.)
   

Figure 9. After the Save button is pressed

Let’s have a look at the code again.

<?php
//Get the user's comment.
$comment = $_POST['comment'];
//Append it to the comments file.
$f = fopen('renata_comments.txt', 'a');
fwrite($f, "<p>$comment</p><hr>");
fclose($f);
//Jump back to Renata's page.
header('location:renata.php');
?>

Figure 8 (again). save-renata-comment.php

Line 3 gets the user’s comment, and puts it into the variable $comment.

Line 5 is new. We want to write to the comments file. But before we do that, we need to open it. That’s what the fopen() function does.

We give fopen() two arguments:

  • The name of the file to open.
  • The mode to open the file in.

The 'a' means the file is opened in append mode. That means that anything we write to the file will be added to the end.

Take another look at the line:

$f = fopen('renata_comments.txt', 'a');

What is $f for? fopen() returns a file handle. This is a special object that refers to the file. You don’t need to know what it is, exactly. Just use it.

The next line is:

fwrite($f, "<p>$comment</p><hr>");

This writes data to $f. Because the program opened the file in append mode, the data is written to the end of the file.

Remember that it’s easier if the file contains HTML. But the user types just plain text into the comment field. So we wrap the comment in a <p> tag. We also add a horizontal rule (<hr>) to separate comments from each other.

Finally, the program closes the file in line 9. PHP will close files for you automatically if you forget to do it.

The page that isn’t a page

One more thing to notice about the code.

<?php
//Get the user's comment.
$comment = $_POST['comment'];
//Append it to the comments file.
$f = fopen('renata_comments.txt', 'a');
fwrite($f, "<p>$comment</p><hr>");
fclose($f);
//Jump back to Renata's page.
header('location:renata.php');
?>

Figure 8 (again). save-renata-comment.php

This program does not send any HTML to the browser. The program adds the new comment, then tells the browser to jump to another page.

PHP files don’t have to generate an <html> tag. Most do, but this one doesn’t. In future chapters, you’ll see other programs that have the same pattern:

  • Get some data from $_POST.
  • Do something with it.
  • Jump to another URL.

Is save-renata-comment.php really a Web “page?” Well, it’s a PHP file that has a URL. But it doesn’t show anything to the user. save-renata-comment.php does its work, then generates an HTTP header that says to the browser, “Sorry, you need to go to another place. Here is the URL.”

I don’t care whether you call it a page or not. The important thing to understand is that:

Some PHP programs don’t create HTML.

Being secure

There’s a problem with the code, though. Evil people could cause some problems.

  • They could enter so much data that the comments file gets too big, and uses all available disk space.

Actually, what they would do is write a program that repeatedly loaded our page and shoved a lot of data into the comments field. Do that 100,000 times, and the disk could get full.

  • They could enter JavaScript into the comments field. When someone else looked at that page, their code would execute.

Let’s make the following changes.

  • Limit the total size of the comments file to 50,000 characters.
  • Limit each individual comment to 2,000 characters.
  • Make sure any JavaScript that anyone types into the comment field can’t run.

Here’s the new version of save-renata-comment.php.

<?php
//Make sure the file exists.
touch('renata_comments.txt');
//Check the length of the comments file.
if ( filesize('renata_comments.txt') < 50000 ) {
  //Get the user's comment.
  //  Limit it to 2,000 characters.
  $comment = substr($_POST['comment'], 0, 2000);
  //Convert special characters to HTML entities.
  $comment = htmlentities($comment);
  //Append comment to the comments file.
  $f = fopen('renata_comments.txt', 'a');
  fwrite($f, "<p>$comment</p><hr>");
  fclose($f);
}
//Jump back to Renata's page.
header('location:renata.php');
?>

Figure 10. Secure version of save-renata-comment.php

Look at line 5:

if ( filesize('renata_comments.txt') < 50000 ) {

The filesize() function returns the number of bytes in the file. If this number is less than 50,000, the statements inside the braces ({}) will run. The braces extend from lines 5 to 15, and include the code that adds data to the comments file.

You can have as many statements as you want in the braces. You can have other if statements, if you want. We’ll see that later.

There is one problem with the filesize() function. It will cause an error if the file does not exist. That’s what line 3 does. The touch() function creates the file if it doesn’t exist.

Have a look at line 8:

$comment = substr($_POST['comment'], 0, 2000);

$_POST['comment'] returns the contents of the comment form field, as before. But rather than just putting that in $comment, something is done with it first.

substr() is a function that returns part of a string. It takes three arguments. The first is the string to process. The next two are numbers. One is the start position of the string. The other is the number of characters to return.

Here are some examples.

$x = 'Lion King';
print substr($x, 0, 3);

The first character is at position 0, not 1. So this says “Start at character 0, and return three characters. The result is:

Lio

How about this one?

print substr('Renata is cute', 2, 4);

This says “Start at character 2 and return 4 characters”. Character 2 is the third character (the first one is character 0). So the result is:

nata

One more.

$r = 'Rupert Giles';
$s = substr($r, 10, 8);
print $s;

This says “Start at the 11th character, and return 8 characters.” The 11th character is the “e” in “Giles.” The result will be:

es

substr() cannot return 8 characters, because there aren’t that many. So it returns what it can.

Back to the code. The line is:

$comment = substr($_POST['comment'], 0, 2000);

This says “Start at character 0 (the first character) and return the first 2,000 characters.” If there are, say, 23 characters, then $comment will contain all 23. If there are 4,321 characters, $comment will contain 2,000 characters.

The last thing we need to do is make sure that any JavaScript that the user types into the comment field will not run when the page is shown. There are various ways to do this; we’ll use the simplest.

For JavaScript to run, it has to have a <script> tag around it, like this:

<script>
alert("Evil JavaScript");
</script>

As you know, the < and the > have special meaning to the browser. They mean “Here is some HTML.” If you want to show a less than sign in your content, you need to replace < with &lt;. This is an HTML entity. It displays a less than sign, that isn’t interpreted as the start of a tag.

We can tell PHP to replace all the characters that have special meaning in HTML, like <, with their HTML entities. This is harmless:

&lt;script&gt;
alert("Evil JavaScript");
&lt;/script&gt;

It won’t be executed, because there is no < to indicate the start of an HTML tag.

Here’s what we do on line 10:

$comment = htmlentities($comment);

htmlentities() is a function that will go through a string, and replace all of the special characters with HTML entities.

Let’s say someone types this into the comment field:

Evil JavaScript

Figure 11. Evil JavaScript

Here is what would be stored into the comments file:

<p>&lt;script&gt;
alert(\&quot;Evil JavaScript\&quot;);
&lt;/script&gt;</p><hr>

The only HTML tags are the <p> and <hr> we added ourselves.

Summary

This lesson shows you how to add form data to a file. PHP gets the form data, opens the file in append mode, and writes the data.

We also looked as some security measures. We limited the total size of the comments file, the size of any one comment, and made sure that JavaScript typed into a comment would not execute.

What now?

Time for some exercises.

Exercises: Saving form data

Exercise: Email light bulb joke

Write a PHP program that emails a light bulb joke. Here is the form:

Form

Figure 1. Form

When the user clicks the button, some PHP sends an email somewhere, and then shows this:

Output

Figure 2. Output

Only use your own email address! Don’t spam.

You can try my version (it doesn’t actually send email, just shows the output). You can download the files.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Joke file

Create an application to save jokes to a file. The main menu is like this:

Main page

Figure 1. Main page

Click “See joke list” and you see the jokes file:

Show jokes

Figure 2. Show jokes

The user can also type a new joke into the main page:

Adding a joke

Figure 3. Adding a joke

Clicking the button will add the joke to the file, and show a confirmation page:

Joke added

Figure 4. Joke added

You can try my solution, though it doesn’t actually save new jokes. You can also download the files.

Upload your solution to your server. Put the URL of the main page below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Quote collector

Write a PHP application that lets you collect quotes you like. It should let you:

  • See quotes you have collected.
  • Write quotes to a file.

You can try the application.

The page that shows the quotes should say “No quotes yet” if there are none. Otherwise, it should show the quotes.

Separate quotes with <hr> tags.

save-quotes.php is the file that saves a new quote. It should:

  • Use stripslashes() to remove PHP’s “helpful” backslashes.
  • Replace new lines with <br> tags.

These two lines will help:

$quote = stripslashes($_POST['quote']);
$quote = str_replace("\n", '<br>', $quote);

You can download a zip file that has everything except two PHP files: see-quotes.php and save-quotes.php.

You can also download my solution, but do the exercise yourself first!

Put the URL of your solution below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Ticket booth

A zoo has a ticket booth. Visitors come up to the booth, and order tickets for adult and children (e.g., 1 adult and two children). The booth attendant collects the entry fee, and prints out the tickets.

Write a PHP application for the attendant. It starts out like this:

Empty form

Figure 1. Empty form

Notice that the focus is in the first input field.

You can download the background image. It’s from a design on OSWD, by GGGDesign.

The attendant enter the numbers of adults and children, and clicks the Total button. The page shows the total, and a Print button:

Form with data

Figure 2. Form with data

When the attendant clicks the Print button, three things happen.

  • A small printer prints the tickets (OK, just pretend this happens).
  • A record of the transaction is appended to a log file.
  • The blank form (Figure 1) is shown again.

Clicking on the “Show log” link shows the transaction log:

Log

Figure 3. Log

Each log entry has the date and time, as well as the number of tickets ordered. This code might be useful:

date('l, F jS Y, h:i:s A')

You can try the application (although I have disabled the logging, to keep the file size down).

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Checking form data

Client-side validation

Where are we?

In a Web application, data validation takes place on the browser and on the server. You learned how to do some validation with JavaScript in a ClientCore lesson.

This lesson reminds you how to do validation in JavaScript. It also tells you how to do better error reporting.

This lesson’s goals

By the end of this lesson, you should:

  • Know how to get form data into JavaScript variables.
  • Be able to check that data for errors.
  • Report errors to the user.

A JavaScript and jQuery review

Let’s review some JavaScript and jQuery. Let’s start with a page like this:

Empty form

Figure 1. Empty form

Notice that the input focus is on the first field; you can tell because the cursor is there.

The user completes the field and clicks the Go button. Here is the result.

Complete form

Figure 2. Complete form

The output area only appears after the button is clicked. It appears with an animated effect.

You can try the page.

Here is the code.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Review</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
      /* Output area starts out hidden. */
      #output_area {
        display: none;
      }
      /* Used to highlight the user's name. */
      .highlight {
        font-weight: bold;
        color: red;
      }

    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        //Put the input focus in the first field.
        $("#first_name").focus();
        //Set up the button click.
        $("#go").click(function(){
          //Get the input.
          var first_name = $("#first_name").val();
          var last_name = $("#last_name").val();
          //Compute the full name.
          var full_name = first_name + ' ' + last_name;
          //Show output.
          $("#full_name")
            .text(full_name)
            .addClass('highlight');
          $("#output_area").show('medium');
        });
      });
    </script>
  </head>
  <body>
    <h1>Review</h1>
    <p>
      First name:
      <input type="text" id="first_name" size="20">
    </p>
    <p>
      Last name:
      <input type="text" id="last_name" size="20">
    </p>
    <p>
      <button type="button" id="go">Go</button>
    </p>
    <div id="output_area">
      <p>Your full name is <span id="full_name"></span>.</p>
    </div>
  </body>
</html>

Figure 3. Code for the page

Line 23 loads the jQuery library, from one of Google’s fast servers.

Line 25 starts the JavaScript. The line is:

$(document).ready(function() {

The code in the ready() function runs when the page has loaded.

Line 27:

$("#first_name").focus();

sets the input focus on to the HTML element with the id of first_name. That element is:

<input type="text" id="first_name" size="20">

That’s how the input focus appears in the first name field when the page first appears.

The field has an id attribute so that jQuery can reference the fields. But it doesn’t have a name attribute. name is used when sending data to a server. We’re not doing that here, so no name attribute is needed.

There is no <form> tag around the <input> elements. It’s only needed when data is being sent to a server, which we aren’t doing.

This line (number 29):

$("#go").click(function(){

attaches some code to the button’s click event. The code runs when the user clicks the button.

The tag for the button says type="button". The forms we’ve seen recently have used type="submit". A submit button is only used when sending (submitting) data to a server. We’re not doing that, so type="button" makes more sense.

What do we want to happen when the user clicks the button? Here’s some pseudocode.

Get the first and last name the user typed.
Put them together to make the full name.
Show the full name.

Figure 4. click pseudocode

It’s a good idea to write pseudocode for every function you create. This helps:

  • Be clear about the goal of the function, that is, what it does.
  • Plan the code, without getting bogged down in the syntax details.

Here’s the JavaScript:

//Get the input.
var first_name = $("#first_name").val();
var last_name = $("#last_name").val();
//Compute the full name.
var full_name = first_name + ' ' + last_name;
//Show output.
$("#full_name")
  .text(full_name)
  .addClass('highlight');
$("#output_area").show('medium');

Figure 5. click code

Let’s see how this achieves the steps given in the pseudocode (Figure 4).

The first step in the pseudocode is:

Get the first and last name the user typed.

I added a comment in line 30, explaining what the code is doing.

Line 31 gets the value the user typed into the first_name field, and puts it into the JavaScript variable first_name:

var first_name = $("#first_name").val();

The names of the field and the variable don’t have to be the same, but it makes the code easier to understand. Line 32 gets the last name.

That’s the first step in the pseudocode: get the user input.

The second step in the pseudocode (Figure 4) is:

Put them together to make the full name.

Line 34 takes the first name, appends a space to it, and then appends the last name. The result gets put into the variable full_name:

var full_name = first_name + ' ' + last_name;

The third step in the pseudocode (Figure 4) is:

Show the full name.

Line 36 finds an element with an id of full_name:

$("#full_name")

That element is the <span>:

<p>Your full name is <span id="full_name"></span>.</p>
Next, set the text of that element to the contents of the variable full_name:

$("#full_name")
   .text(full_name)

Then add the class highlight to the element:

$("#full_name")
   .text(full_name)
   .addClass('highlight');

The class is defined in the CSS as:

.highlight {
   font-weight: bold;
   color: red;
}

Lines 36 to 38 use method chaining (“method” is a type of function). The statement could have been typed all on one line, like this:

$("#full_name").text(full_name).addClass('highlight');

Spreading across three lines makes it easier to read.

Line 39 is:

$("#output_area").show('medium');

It makes the element with the id of output_area appear. It uses an animated effect at medium speed.

Let’s look at the structure of the output area again, just to be sure what is going on.

<div id="output_area">
  <p>Your full name is <span id="full_name"></span>.</p>
</div>

Part of Figure 3 (again). Code for the page

output_area is a container for, well, the output area. Inside it are places for the individual output pieces. There is only one in this example: full_name. But there could be more.

This is a common pattern.

Create a container for the output.
Have individual output elements inside the container.
Fill in the individual output elements.
Show the entire output container at once.

Figure 6. Pattern for output with HTML and JavaScript

You can try the page.

Exercise: Your favorite animals

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user fills in the form and clicks the button:

Form with data

Figure 2. Form with data

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Checking numbers

Let’s see how you can use JavaScript to check the data that users type into form fields. Start with this one:

Age check form

Figure 6. Age check form

The user types something in the field, and clicks the button. Some JavaScript checks the value, and shows a message, like this:

Age error

Figure 7. Age error

You can try it.

Here is the code:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Check age 1</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#age").focus();
        $("#go").click(function(){
          var age = $("#age").val();
          if ( age == "" ) {
            alert("Sorry, please enter your age.");
          }
          else if ( isNaN(age) ) {
            alert("Sorry, please enter a number.");
          }
          else if ( age < 0 || age > 120 ) {
            alert("Sorry, please enter a valid age.");
          }
          else if ( age < 21 ) {
            alert("Sorry, you are too young.");
          }
          else {
            alert("Thanks! You can proceed.");
          }
          $("#age").focus();
        });
      });
    </script>
  </head>
  <body>
    <h1>Check age 1</h1>
    <p>
      Age:
      <input type="text" id="age" size="3">
    </p>
    <p>
      <button type="button" id="go">Go</button>
    </p>
  </body>
</html>

Figure 8. Code for age error check

Line 18 put the value the user typed into the variable age.

var age = $("#age").val();

Here is the first check:

if ( age == "" ) {

This is true if the variable is an empty string. Remember:

  • Use == and not =.
  • "" is an empty string. The quotes are together with no space between them. This is not the same as " ", which is a string with a space in it.

Line 22 is:

else if ( isNaN(age) ) {

The isNaN() function returns true if the parameter is Not a Number.

This (line 25):

else if ( age < 0 || age > 120 ) {

means “if age is less than 0 or greater than 120.” || means “or,” && means “and,” and ! means “not.”

Here’s another example.

width > 10 && width < 50

width is more than 10 and less than 50. Note that leaving out the second width will not work:

width > 10 && < 50

Some more.

x < 10 || y == 10

x is less than 10, or y is equal to 10. You can have as many different variables as you want in an if test.

(x + y) < 100 && z != 11

x plus y is less than 100, and z does not equal 11. You can do computations like this in if statements.

Exercise: Check song time

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user clicks the button, JavaScript code checks the values in the fields. Each field must have a valid number. Minutes must be from 0 to 120. Seconds must be from 0 to 59.

Here’s an error message:

Error

Figure 2. Error

If the data is OK, show this:

Data OK

Figure 3. Data OK

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Checking strings

Time to see how you can check strings. Here’s a log in form.

Empty log in form

Figure 9. Empty log in form

Here’s an error message showing.

Log in error

Figure 10. Log in error

Here’s the code:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Check log in 1</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#user_name").focus();
        $("#login").click(function(){
          var user_name = $("#user_name").val();
          var password = $("#password").val();
          if ( user_name == "" || password == "" ) {
            alert("Sorry, you must enter both your user name and password.");
          }
          else if ( user_name.length < 6 ) {
            alert("Sorry, the user name is too short.");
          }
          else if ( password.length < 6 ) {
            alert("Sorry, the password is too short.");
          }
          else if ( user_name.toLowerCase() == "renata" ) {
            alert("Sorry, Renata! You need to get permission.");
          }
          else {
            alert("Thanks! You can proceed.");
          }
          $("#user_name").focus();
        });
      });
    </script>
  </head>
  <body>
    <h1>Check log in 1</h1>
    <p>
      User name:
      <input type="text" id="user_name" size="20">
    </p>
    <p>
      Password:
      <input type="password" id="password" size="20">
    </p>
    <p>
      <button type="button" id="login">Login</button>
    </p>
  </body>
</html>

Figure 11. Log in code

Line 48 has something new:

<input type="password" id="password" size="20">

A field with a type of password is just like a regular text field, except that it hides what the user types.

Password field in action

Figure 12. Password field in action

I gave the field an id of password. It could have been anything, but this id makes sense.

Lines 18 and 19:

var user_name = $("#user_name").val();
var password = $("#password").val();

get the values the user typed into the form.

This (line 20):

if ( user_name "" || password "" ) {

checks whether user_name or password are empty.

Here’s line 23:

else if ( user_name.length < 6 ) {

user_name.length returns the number of characters in a string.

Here’s line 29:

else if ( user_name.toLowerCase() == "renata" ) {

user_name.toLowerCase() returns a string user_name, but with all of the characters converted to lower case. So the if statement will succeed if the user enters renata, Renata, RENATA, renatA, etc.

This does not change the value of user_name. If we wanted to do that, we could write:

user_name = user_name.toLowerCase();

Exercise: New dog name

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user clicks the button, JavaScript code checks the value in the field. The field cannot be empty. It must contain a value that is longer than one character, and shorter than 21 characters.

Here’s an error message:

Error

Figure 2. Error

If the data is OK, show this:

Data OK

Figure 3. Data OK

Hint: this combines the error checking in this section with the output method we saw earlier – showing an output container.

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Reporting errors

So far, we have used simple alert() statements to report errors. But we can do better.

Let’s change the age page. The new page starts off like this:

Empty form

Figure 13. Empty form

When there is an error, a message appears in the content of the page, right below the field the message is about. Like this:

Showing an error

Figure 14. Showing an error

Try the page. You’ll see that the error message appears with an animated effect.

Here is the code.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Check age 2</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
      #error_message {
        font-weight: bold;
        color: red;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#age").focus();
        $("#error_message").hide();
        $("#go").click(function(){
          var age = $("#age").val();
          if ( age == "" ) {
            $("#error_message").text("Sorry, please enter your age.");
            $("#error_message").show('medium');
          }
          else if ( isNaN(age) ) {
            $("#error_message").text("Sorry, please enter a number.");
            $("#error_message").show('medium');
          }
          else if ( age < 0 || age > 120 ) {
            $("#error_message").text("Sorry, please enter a valid age.");
            $("#error_message").show('medium');
          }
          else if ( age < 21 ) {
            $("#error_message").text("Sorry, you are too young.");
            $("#error_message").show('medium');
          }
          else {
            $("#error_message").hide();
            $("#ok_message").text("Thanks! You can proceed.");
          }
          $("#age").focus();
        });
      });
    </script>
  </head>
  <body>
    <h1>Check age 2</h1>
    <p>
      Age:
      <input type="text" id="age" size="3"><br>
      <span id="error_message"/>
    </p>
    <p>
      <button type="button" id="go">Go</button>
    </p>
    <p id="ok_message"/>
  </body>
</html>

Figure 15. Code for improved error reporting

Let’s look at the HTML first. There’s a place for an error message after the input field (see line 53):

<input type="text" id="age" size="3"><br>
<span id="error_message"/>

The span will show an error message when there is one.

Note that you can use <span id="error_message"/> as a shortcut for <span id="error_message"></span>, when the tag is empty.

The error message is hidden when the page loads:

$(document).ready(function() {
   ...
   $("#error_message").hide();

error_message is highlighted by some CSS (lines 12 to 15).

#error_message {
   font-weight: bold;
   color: red;
}

If there is an error, some text is put into error_message, and it is shown. Here are lines 25 and 26:

$("#error_message").text("Sorry, please enter your age.");
$("#error_message").show('medium');

All of the errors are handled the same way. For example:

$("#error_message").text("Sorry, please enter a number.");
$("#error_message").show('medium');

What if there is no error? Here’s what happens (line 41):

$("#error_message").hide();
$("#ok_message").text("Thanks! You can proceed.");

This hides whatever error message was showing (if any). Then an “OK” message shows in a different place.

Exercise: New dog name, again

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user clicks the button, JavaScript code checks the value in the field. The field cannot be empty. It must contain a value that is longer than one character, and shorter than 21 characters.

Here’s an error message:

Error

Figure 2. Error

If the data is OK, show this:

Data OK

Figure 3. Data OK

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Using a function

Have another look at the error checking code.

var age = $("#age").val();
if ( age == "" ) {
  $("#error_message").text("Sorry, please enter your age.");
  $("#error_message").show('medium');
}
else if ( isNaN(age) ) {
  $("#error_message").text("Sorry, please enter a number.");
  $("#error_message").show('medium');
}
else if ( age < 0 || age > 120 ) {
  $("#error_message").text("Sorry, please enter a valid age.");
  $("#error_message").show('medium');
}
else if ( age < 21 ) {
  $("#error_message").text("Sorry, you are too young.");
  $("#error_message").show('medium');
}
else {

Part of Figure 15 (again). Code for improved error reporting

There’s a lot of repeated code:

$("#error_message").text( A message );
$("#error_message").show('medium');

It would be better to create a function. Here’s the new code:

var age = $("#age").val();
if ( age == "" ) {
  show_error("Sorry, please enter your age.");
}
else if ( isNaN(age) ) {
  show_error("Sorry, please enter a number.");
}
else if ( age < 0 || age > 120 ) {
  show_error("Sorry, please enter a valid age.");
}
else if ( age < 21 ) {
  show_error("Sorry, you are too young.");
}
else {

...

//Show an error message.
function show_error(message) {
  $("#error_message").text(message);
  $("#error_message").show('medium');
}

Figure 16. Error function

This moves the repeated code into a function. Whatever message you send to the function will show in error_message. For example:

show_error("Sorry, you have fleas.");

show_error("Sorry, your " + toy_name + " is broken.");

The main advantage of this is that we can change the error processing all at once. For example, suppose we want to change the animation speed of all the error messages. So we want:

$("#error_message").show('medium');

to be:

$("#error_message").show('slow');

In Figure 15, we have several lines to change. It would be easy to miss one.

But with the function, we just change medium to slow on one line:

function show_error(message) {
   $("#error_message").text(message);
   $("#error_message").show('slow');
}

Functions give us another productivity win when changing a page.

Exercise: New dog name, once more with functions

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user clicks the button, JavaScript code checks the value in the field. The field cannot be empty. It must contain a value that is longer than one character, and shorter than 21 characters.

Here’s an error message:

Error

Figure 2. Error

Use a function to show error messages. Call the same function for every error message.

If the data is OK, show this:

Data OK

Figure 3. Data OK

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

  • Less code to change.
  • Less chance of making a mistake.

W00f!

Summary

In this lesson, you learned:

  • How to get form data into JavaScript variables.
  • How to check that data for errors.
  • How to report errors to the user.

What now?

This lesson looked at client-side validation. Let’s start learning about server-side validation.

Basic server-side validation

Where are we?

We just looked at client-side validation with JavaScript. Time to start working on the server side.

This lesson’s goals

By the end of this lesson, you should:

  • Understand why you need server-side and client-side validation.
  • Know how to check whether a value sent to the server is a valid number.

The need for server-side validation

You need to validate form data on the server (with PHP) as well as on the client (with JavaScript). Why? Three reasons.

Reason 1: There are some checks you can only do on the server.

Suppose you have an online store selling dog toys. A user enters the number of toys s/he wants:

Ordering toy

Figure 1. Ordering toy

Maybe you’ve sold out of the Giant Chew Rope. You want to tell the user:

Out of stock

Figure 2. Out of stock

The code for this will be something like:

if number_ordered > current_inventory then

(This is pseudocode, of course.)

You get number_ordered from the form the user filled out. But where does current_inventory come from?

As you’ll learn later, you look that up in a database. This is typical:

DB lookup

Figure 3. DB lookup

The if statement above is usually done by PHP code on the server, not the browser. The server has access to the database. The browser does not.

Reason 2: Security.

This is the second reason why you do validation on the server as well as the browser.

Remember that every page a user sees in his or her browser is downloaded to his or her computer. That includes the JavaScript that has the validation code.

A clever hacker might be able to create a new version of your page, without the JavaScript checking. S/he could then fool your server into accepting invalid data.

Reason 3: Coding mistakes.

You might make a mistake coding the JavaScript. For example, you might write:

if ( age = "" ) { //WRONG!
   Tell the user the field is empty
}
else {
   Accept the data
}

The first line is wrong. It should be:

if ( age == "" ) {

An easy thing to miss, but it could mean that you get bad data in your databases. This can mess up sales, event registration, or whatever business your Web site supports.

So, even though you know how to do validation by writing JavaScript that runs on a browser, you also need to know how to do it on a server. Because:

  • There are some checks you can only do on the server.
  • Security.
  • Coding mistakes.

Simple validation

Let’s start out easy (as usual). Let’s make this:

Empty order form

Figure 4. Empty order form

The user types a number and clicks the button:

Order form with data

Figure 5. Order form with data

S/he sees this:

Order output

Figure 6. Order output

But what if the user enters an invalid number?

Bad input

Figure 7. Bad input

This is the result:

Result from bad input

Figure 8. Result from bad input

You can try it.

Here’s the page:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Order Processing</title>
  </head>
  <body>
    <h1>Order Processing</h1>
    <?php
    //Get the input.
    $giant_chew_ropes = $_POST['giant_chew_ropes'];
    //Validate input.
    if ( ! is_numeric($giant_chew_ropes) ) {
      print '<p>Invalid number.</p>';
      print '<p>Please click the Back button on your browser and try again.</p>';
    }
    else {
      //Input OK, show the order.
      //Compute total.
      $giant_chew_ropes_total = $giant_chew_ropes * 12.95;
      //Output total.
      ?>
      <p>Thank you for your order.</p>
      <table cellpadding="5" cellspacing="0" border="1">
        <tr>
          <th>Product</th>
          <th>Number<br>ordered</th>
          <th>Unit price</th>
          <th>Total price</th>
        </tr>
        <tr>
          <td>Giant chew ropes</td>
          <td><?php print $giant_chew_ropes; ?></td>
          <td>$12.95</td>
          <td>$<?php print number_format($giant_chew_ropes_total, 2); ?></td>
        </tr>
      </table>
    <?php
    }
    ?>
  </body>
</html>

Figure 9. Simple validation

Line 11 gets the value the user typed into the form field:

$giant_chew_ropes = $_POST['giant_chew_ropes'];

The if statement in line 13 checks it:

if ( ! is_numeric($giant_chew_ropes) ) {
   print '<p>Invalid number.</p>';
   print '<p>Please click the Back button on your browser and try again.</p>';
}
else {
   Do this if number is OK.
}

The function is_numeric() is true if the argument it’s given is a valid number. Otherwise, it returns false. Recall that ! means “not.” So, if $giant_chew_ropes is not numeric, then output the HTML for an error message.

Remember the JavaScript isNaN() function? It returns true when a value is not a number. So isNaN() is like a backwards is_numeric(). is_numeric() is true when a value is a number, while isNaN() is true when a value is not a number.

There are some other things to notice about Figure 9. Look at the else on line 17. There’s a lot of code in the else, all the way up to line 39. That’s OK. You can put as much code as you like in braces.

Now look at line 22. This switches back from PHP to HTML mode. But it’s still inside the else! So the <table> is only shown if the user’s input passes the validation test.

The opening brace ({) in line 17 needs a closing brace (}) to go along with it. You can see that in line 39. But notice that the brace is a PHP brace. So you need to switch back to PHP mode to close the brace in line 17.

Switching modes like this is very common in PHP land. But it can be tricky for beginners. Remember that when you need to close a brace, make sure you’re in PHP mode.

Exercise: Dog weight

Create a page with a form:

Input

Figure 1. Input

Send the form data to a PHP page. If the user enters an invalid number, show this:

Error

Figure 2. Error

If there’s a valid number, show this:

Output

Figure 3. Output

You can see my solution. You can also download the files. Of course, do the exercise yourself before you look at my solution.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

You learned:

  • Why you need server-side and client-side validation.
  • How to check whether a value sent to the server is a valid number.

What now?

Let’s see how you can do better validation.

Improving validation

Where are we?

You learned to send a form value to a PHP page, and do one check on it. Let’s see how to do a bunch of checks.

More thorough checks

Here’s the order form again:

Order form

Figure 1. Order form

On the previous page we checked whether the user typed a non-numeric value into the field. But there are other errors the user could make, like:

  • Leave the field empty.
  • Type a negative number.
  • Order more than we have in the warehouse (if this is an “error” – it might not be).

Let’s improve the validation, so that it checks for these things. You can try the improved version.

Here’s the validation code:

//Get the input.
$giant_chew_ropes = $_POST['giant_chew_ropes'];
//Validate input.
$error_message = '';
if ( $giant_chew_ropes == '' ) {
  $error_message = 'Please enter the number you want to order.';
}
else if ( ! is_numeric($giant_chew_ropes) ) {
  $error_message = 'Please enter a valid number.';
}
else if ( $giant_chew_ropes <= 0 ) {
  $error_message = 'Please enter a positive number.';
}
else if ( $giant_chew_ropes > 10 ) {
  $error_message = 'Sorry, we don\'t have that many in stock.';
}
//Any error made?
if ( $error_message != '' ) {
  print "<p>$error_message</p>";
  print '<p>Please click the Back button on your browser and try again.</p>';
}
else {
  //Input OK, show the order.
...

Figure 2. Improved validation code

The first thing to notice is the variable $error_message. It’s created on line 13, and set to an empty string.

$error_message = '';

Then there’s a bunch of error checking if statements. Any one of them could find an error, and give $error_message a value, like this:

if ( there is something wrong ) {
   $error_message = ' An error message ';
}

There can be as many error checks as are needed: 3, 8, 18, whatever.

What happens after all of the error checks have been run? If there is any error at all, then $error_message will have some text in it. If there have been no errors, then $error_message will have the same empty string it started with.

This is checked in line 27:

if ( $error_message != '' ) {
   print "<p>$error_message</p>";
   print '<p>Please click the Back button on your browser and try again.</p>';
}

If $error_message does not equal an empty string, it means that one of the if statements found an error, and put something in $error_message.

Line 28 has double quotes (”) in it:

print "<p>$error_message</p>";

Remember, this means that PHP will insert the value of the variable $error_message before printing anything.

Notice how flexible this is. We can add and remove as many error checks as we like. As long as each one uses $error_message, it will all work.

Here’s line 23:

else if ( $giant_chew_ropes > 10 ) {

This is the inventory level check. It assumes that we have 10 items to sell. In a real program, the 10 would probably be pulled from a database.

Actually, in a real business, we would probably allow back orders.

Exercise: Dog weight again

Create a page with a form:

Input

Figure 1. Input

Send the form data to a PHP page. If the user enters an invalid number, show an error message:

Error

Figure 2. Error

Check for:

  • An empty field
  • A non-numeric value.
  • A value of zero or less.
  • A value greater than 500.

If there’s a valid number, show this:

Output

Figure 3. Output

You can see my solution. You can also download the files. Of course, do the exercise yourself before you look at my solution.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

In this lesson, you learned how to do a sequence of checks on a value passed to a PHP page.

What now?

You’ve learned a lot about validation. But we still need to make it better. For example, one problem is that, when there is an error, you get a message like this:

Error message

Figure 3. Error message

This isn’t very good. Let’s make it better, more like something you see in a real application.

But before we do that, let’s take a detour, and talk about functions in PHP. It will make our work easier.

PHP functions

Where are we?

You know how to run a bunch o’ validation in PHP. We can improve the code by using functions. Let’s see how.

This lesson’s goals

By the end of this lesson, you should:

  • Know what a function is.
  • Know why functions are Good Things.
  • Be able to create a function library.

What is a function?

A function is a piece of code that has three things:

  • A name, e.g., validate_order, or compute_area.
  • Some data going in (optional).
  • Some data coming out (optional).

Functions do three Good Things for us.

  • They reduce duplicate code, making Web sites easier to maintain.
  • They let us create reusable libraries we can drop into any page.
  • They clump PHP statements together, making a program easier to understand.

Let’s see an example. Here’s a page that computes the area of some rectangles.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Rectangle Areas</title>
  </head>
  <body>
    <h1>Rectangle Areas</h1>
    <?php
    $r1_width = 7;
    $r1_length = 3;
    $r1_area = rectangle_area($r1_width, $r1_length);
    print "<p>A rectangle with sides of $r1_width and $r1_length has an area of $r1_area.</p>";
    
    $r2_width = 5;
    $r2_length = 6;
    $r2_area = rectangle_area($r2_width, $r2_length);
    print "<p>A rectangle with sides of $r2_width and $r2_length has an area of $r2_area.</p>";
    
    function rectangle_area($width, $length) {
      $area = $width * $length;
      return $area;
    }
    ?>
  </body>
</html>

Figure 1. Area code

The functions starts in line 20. Its name is rectangle_area. It has two input parameters: $width, and $length. The return statement stops the function, and sends back some data.

A function call sends some data to the function, and (maybe) does something with the result that comes back from the function’s return statement. For example:

$r1_area = rectangle_area($r1_width, $r1_length);

The values in $r1_width and $r1_length go into the function. The return statement sends back a result, which gets stored in $r1_area.

The arguments in the call and the function match by position, not name.

Argument matching

Figure 2. Argument matching

That’s why the function can be called with different variables each time.

You can try the program.

Here’s part of Figure 1 again:

<?php
$r1_width = 7;
$r1_length = 3;
$r1_area = rectangle_area($r1_width, $r1_length);
print "<p>A rectangle with sides of $r1_width and $r1_length has an area of $r1_area.</p>";

$r2_width = 5;
$r2_length = 6;
$r2_area = rectangle_area($r2_width, $r2_length);
print "<p>A rectangle with sides of $r2_width and $r2_length has an area of $r2_area.</p>";

function rectangle_area($width, $length) {
  $area = $width * $length;
  return $area;
}
?>

Figure 1 (again). Area code

The function is after the rest of the code on the page. But it can be in other places as well. For example, it could be above the other code, like this:

<?php
function rectangle_area($width, $length) {
  $area = $width * $length;
  return $area;
}

$r1_width = 7;
$r1_length = 3;
$r1_area = rectangle_area($r1_width, $r1_length);
print "<p>A rectangle with sides of $r1_width and $r1_length has an area of $r1_area.</p>";

$r2_width = 5;
$r2_length = 6;
$r2_area = rectangle_area($r2_width, $r2_length);
print "<p>A rectangle with sides of $r2_width and $r2_length has an area of $r2_area.</p>";
?>

Figure 3. Moving the function up

Or it could be in the middle:

<?php
$r1_width = 7;
$r1_length = 3;
$r1_area = rectangle_area($r1_width, $r1_length);
print "<p>A rectangle with sides of $r1_width and $r1_length has an area of $r1_area.</p>";

function rectangle_area($width, $length) {
  $area = $width * $length;
  return $area;
}

$r2_width = 5;
$r2_length = 6;
$r2_area = rectangle_area($r2_width, $r2_length);
print "<p>A rectangle with sides of $r2_width and $r2_length has an area of $r2_area.</p>";
?>

Figure 4. Moving the function to the middle

The function can even be in another file. As we’ll see.

Creating a function library

The function rectangle_area works, but is so simple that it is hardly worth using. Let’s change things by:

  • Creating a more complex function.
  • Moving the function into a library file.

Here is the page:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Normal probability density function</title>
  </head>
  <body>
    <?php
    require 'normal-pdf.inc';
    print normal_pdf(.5, 0, 1);
    ?>
  </body>
</html>

Figure 5. Another example

Line 10 calls a function called normal_pdf and sends it three arguments. It prints the return value. Note that you can do anything with the return value: print it, put it in a variable, use it in an if statement, and so on.

The function isn’t in Figure 5. It’s in a separate file, normal-pdf.inc. Here’s the contents of that file.

<?php
function normal_pdf($x, $mu, $sigma) {
  $denominator = sqrt(2 * pi()) * $sigma;
  $numerator = exp(-($x - $mu) * ($x - $mu) / ( 2 * $sigma));
  $density = $numerator / $denominator;
  return $density;
}
?>

Figure 6. The function

Being in a separate file, it can be inserted into any number of PHP pages. And it’s a lot more complex than rectangle_area.

The function returns a probability value from a normal distribution. It was adapted from code on this page.

When variables are created inside a function, they are called “local variables.” They are not available outside the function. They are destroyed when the function exits. So if you tried to print the variable $denominator outside the function, you would get an error. It only exists inside the function.

This is a Good Thing. It means you can write a program like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Normal probability density function</title>
  </head>
  <body>
    <?php
    require 'normal-pdf.inc';
    $numerator = 1;
    $denominator = 2;
    $p = normal_pdf($numerator/$denominator, 0, 1);
    ?>
    <p>Numerator: <?php print $numerator; ?></p>
    <p>Denominator: <?php print $denominator; ?></p>
    <p>p: <?php print $p; ?></p>
  </body>
</html>

Figure 7. Local variables in action

If you try the program, you will see that it works. Neither $numerator nor $denominator – names that appear in both the function and the code that calls it – get clobbered.

Why is there no confusion between $numerator in line 10 of Figure 7, and $numerator in line 4 of the inserted file, shown in Figure 6? When the function normal_pdf runs, it creates a new $numerator variable all of its own. It gets wiped out when the function exits.

Why is this a Good Thing? Because it makes it easier to use function libraries. You can insert normal-pdf.inc into any PHP file you want, without worrying about whether variables will get hurt. W00f!

String functions

Here’s another one:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Farm animal plurals</title>
  </head>
  <body>
    <h1>Farm animal plurals</h1>
    <p>The plural of "dog" is <?php print farm_animal_plural('dog'); ?>.</p>
    <p>The plural of "cat" is <?php print farm_animal_plural('cat'); ?>.</p>
    <p>The plural of "goose" is <?php print farm_animal_plural('goose'); ?>.</p>
    <?php
    function farm_animal_plural($animal) {
      if ( $animal == 'sheep' ) {
        return 'sheep';
      }
      if ( $animal == 'goose' ) {
        return 'geese';
      }
      if ( $animal == 'fish' ) {
        return 'fish';
      }
      return $animal . 's';
    }
    ?>
  </body>
</html>

Figure 8. String function

The function takes the name of a farm animal, and returns the plural. It deals with a few special cases, and then just returns the input value, with an “s” attached.

It isn’t a very good function.

Some things to notice about this example:

  • There are many return statements. As soon as one is encountered, PHP exits.
  • You can return any value, not just a plain variable. Line 23 attaches something to a variable before returning it.
  • You can output the return value of a function directly. You don’t need to put it in a variable first. Lines 9, 10, and 11 do this.

Summary

This lesson explains:

  • What a function is.
  • Why functions are Good Things.
  • How to create a function library.

What now?

Let’s see how you can write a PHP validation function.

A PHP validation function

Where are we?

You know how to do some validation. You know what a PHP function is. Let’s put them together, and create a PHP validation function.

This lesson’s goals

By the end of this lesson, you should:

  • Be able to write a PHP validation function.
  • Be able to call it.

Two products

Suppose we have two products. Here is a form:

Form

Figure 1. Form

As usual, the user enters the number of each product s/he wants, and clicks the button.

With the data in Figure 1, the user would get:

Output

Figure 2. Output

But what if the user typed this?

Form with bad data

Figure 3. Form with bad data

S/he would see:

Error report

Figure 4. Error report

Notice that each product has its own error line.

You can try the program. You can also download the files.

Repeating the validation tests

The same tests apply to both fields. For example:

  • The value should be a number.
  • The number should not be less than zero.

We could have separate code to test each field. But that would duplicate work. Instead, let’s use the same function to test both fields.

Here’s the function:

//Check the number ordered.
//Input:
//  $number_ordered: The number the user wants.
//  $number_on_hand: The number in inventory.
//Returns:
//  An error message, or an empty string if there was no error.
function check_number_ordered($number_ordered, $number_on_hand) {
  if ( $number_ordered == '' ) {
    return 'Please enter the number you want to order.';
  }
  else if ( ! is_numeric($number_ordered) ) {
    return 'Please enter a valid number.';
  }
  else if ( $number_ordered <= 0 ) {
    return 'Please enter a positive number.';
  }
  else if ( $number_ordered > $number_on_hand ) {
    return 'Sorry, we don\'t have that many in stock.';
  }
  return '';
}

Figure 5. Validation function

The comments from lines 1 to 6 explain what the function does and how to use it. They describe the parameters going into the function, and the return value that comes out.

We need two pieces of information to check whether the number ordered is valid. First, we need the number ordered. We can do most of the checks just with that. But we also need to know the number on hand, for one of the checks. That’s why the function has two parameters.

There are four if statements, each one checking for a different error. Let’s look at the first one.

if ( $number_ordered == '' ) {
   return 'Please enter the number you want to order.';
}

If $number_ordered is empty, the function ends immediately, sending back a message describing the error. It doesn’t get beyond line 9. Otherwise, the next if statement runs.

If there are no errors, none of the returns in the if statements will be run. So what do we do? The last line of the function is:

return '';

If the program gets to this point, none of the if statements has been true (because otherwise one of them would have exited the function already). That means there are no errors; the data is valid. The function returns an empty string, to show that there were no errors.

We could have done something else to show there were no errors. For example, we could have returned “NO ERRORS!” Returning an empty string is common practice, but not the only choice.

Let’s see how the function is used.

Calling the validation function

Here’s the code, with some less interesting stuff omitted:

<h1>Order Processing</h1>
<?php
//Get the input.
$frisbees = $_POST['frisbees'];
$giant_chew_ropes = $_POST['giant_chew_ropes'];
//Validate input.
$error_message_frisbees = check_number_ordered($frisbees, 15);
if ( $error_message_frisbees != '' ) {
  print "<p>Frisbees: $error_message_frisbees";
}
$error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);
if ( $error_message_giant_chew_ropes != '' ) {
  print "<p>Giant chew ropes: $error_message_giant_chew_ropes";
}
//Any error made?
if ( $error_message_frisbees != '' || $error_message_giant_chew_ropes != '') {
  print '<p>Please click the Back button on your browser and try again.</p>';
}
else {
  //Input OK, show the order.
  //Compute total.
  $frisbees_total = $frisbees * 8.95;
  $giant_chew_ropes_total = $giant_chew_ropes * 12.95;
  $order_total = $frisbees_total + $giant_chew_ropes_total;
  //Output.
  ?>
  <p>Thank you for your order.</p>
  ...

Figure 6. Calling the function

Line 14 is:

$error_message_frisbees = check_number_ordered($frisbees, 15);

This calls the function for the frisbees, and puts the return value in $error_message_frisbees. Line 18 is:

$error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);

This does the same thing for giant chew ropes. It puts the function’s return value in a different variable, $error_message_giant_chew_ropes.

As you can see, we’ve used the same validation function both times. If we had, say, 23 products on the page, we’d call check_number_ordered() 23 times. But we’d only write it once. If we needed to change the code, we’d only need to change one thing, and all the checks on the page would change.

Here are lines 15 to 17 in Figure 6:

if ( $error_message_frisbees != '' ) {
   print "<p>Frisbees: $error_message_frisbees";
}

This checks to see what the function sent back for frisbees. If it sent back an empty string (''), there was no error. If the function sent back something else, there was an error. Line 16 shows it.

The same check is done for $error_message_giant_chew_ropes is lines 18 to 21.

Now, if there was an error, any error at all, we want to ask the user to go back to the order page. It doesn’t matter which field the error was in. Could be with frisbees, or giant ropes, or both.

Look at lines 23 to 25:

if ( $error_message_frisbees != '' || $error_message_giant_chew_ropes != '') {
   print '<p>Please click the Back button on your browser and try again.</p>';
}

Line 23 says “if $error_message_frisbee is not empty, or $error_message_giant_chew_ropes is not empty, then ask the user to go back.”

This is what we want. If there is any error at all, go back to the order page. We only show the order output if there were no errors.

Summary

You learned:

  • How to write a PHP validation function.
  • How to call it.

What now?

All of our examples use two pages:

  • An HTML page with a form for input.
  • A PHP page to validate and process the data.

Webers often use one page for both input and server-side validation. Let’s see how that’s done.

Form and PHP validation on one page

Where are we?

You know how to get data from a form, and validate it. You know how to use a validation function. Let’s improve the way that’s done. Again.

This lesson’s goals

By the end of this lesson, you should:

  • Know how to have the HTML for the form and the PHP validation code on the same page.
  • Know how to collect error messages in a single variable, rather than treating them all separately.

The form/processor pattern

Up to now, we’ve separated the page with the input form from the page that does validation and processing. Like this:

Pattern so far

Figure 1. Pattern so far

The .hmtl page has a <form>. The action attribute of the form has the name of the processing page. When the user clicks the submit button, the form data is sent to the processing page.

The processing page validates the data. If there is a problem, it shows a message to the user, and suggests going back to the order form again.

If all the form data is OK, the order is processed. This means telling the shipping department to send the order, processing payment, and other businessy things.

A new pattern

But there’s another way of doing things. The new pattern is to put both the form and the server-side PHP validation code on the same page.

The strangest thing about this is that the page with the form submits data to itself.

A new pattern

Figure 2. A new pattern

This is helpful when you do both client-side and server-side validation, and want to show both types of errors to the user in the same way. The new pattern lets you be more consistent in the way you show errors to users.

The key to this is that the page works in two modes:

  • New input mode: The user wants to fill in the form with some new data.
  • Process input mode: Check data the user has already entered.

The page uses if statements to act differently, depending on which mode it’s in.

What are these “modes” you speak of?

Let’s be sure we know what I mean by “modes” here. You’re used to thinking about modes that are built in to devices. For example, you might have an SUV that works in two-wheel or four-wheel drive. Two different modes. You use a switch (or whatever) to choose the mode you want.

The engineers who designed the vehicle built those modes into it.

Another example is the camera in your cell phone. It can work in still photo mode. Or it can work in video mode, where it shoots movies. You choose the modes you want.

The engineers who designed the cell phone built those modes into it.

But when you write PHP, you are the engineer. You get to create modes yourself. You decide:

  • What the modes are
  • What each one does
  • How each mode is activated

You do this by the way you write your code. There is no special “mode” statement in PHP. The modes are created by the way you put together if statements and other things.

Let’s see how all this works.

What the user sees

Here’s an empty order form, from order.php.

Empty order form

Figure 3. Empty order form

Suppose the user enters some bad data into order.php:

Bad data

Figure 4. Bad data

The form data is sent from order.php to order.php; the page sends the form data to itself. Here is what it shows:

Error messages

Figure 5. Error messages

Now, what if the user enters valid data?

Valid data

Figure 6. Valid data

Here is the result:

Order confirmation

Figure 7. Order confirmation

You can try it. Enter different combinations of good and bad data into the fields, and see what you get.

Some code

Here’s order.php.

<?php
if ( $_POST ) {
  //There is order data to validate.
  require 'validation-library.inc';
    //Warning: Extra characters at the end of validation-library.inc
    //will break this page!
  //Get the order amounts.
  $frisbees = $_POST['frisbees'];
  $giant_chew_ropes = $_POST['giant_chew_ropes'];
  //Validate input.
  $error_message_frisbees = check_number_ordered($frisbees, 15);
  $error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);
  //Jump if no errors.
  if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
    $destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";
    header("Location:$destination_url");
    exit();
  }
}?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Order Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <h1>Order Form</h1>
    <?php
    //Any error messages to display?
    if ( $error_message_frisbees != '' ) {
      print "<p>Frisbees: $error_message_frisbees</p>";
    }
    if ( $error_message_giant_chew_ropes != '' ) {
      print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
    }
    ?>
    <form method="post" action="order.php">
      <p>
        Frisbees:
        <input type="text" name="frisbees" size="3"
        <?php
        if ( $_POST['frisbees'] ) {
          print ' value="' . $_POST['frisbees'] . '"';
        }
        ?>
        >
        at $8.95 each
      </p>
      <p>
        Giant chew ropes:
        <input type="text" name="giant_chew_ropes" size="3"
        <?php
        if ( $_POST['giant_chew_ropes'] ) {
          print ' value="' . $_POST['giant_chew_ropes'] . '"';
        }
        ?>
        >
        at $12.95 each
      </p>
      <p>
        <button type="submit">Order</button>
      </p>
    </form>
  </body>
</html>

Figure 8. order.php

Line 2:

if ( $_POST ) {

checks whether there is any form data coming in. If there is no post data, then $_POST will be empty, which, in PHP, is like bring false.

This is the mode check, if you want to think of it that way. If there is post data, we enter “check input” mode. Otherwise, we’re in “empty form” mode.

We created a validation function on the previous page. We’ll put it in a separate file, and bring it in like this (lines 4):

require 'validation-library.inc';

We’ll talk about the warning in lines 5 and 6 later.

Lines 8 and 9 get the form data into variables, as usual. Here’s one of the lines:

$frisbees = $_POST['frisbees'];

As before, the function check_number_ordered() checks the value passed in, and returns either an error message or a blank string (line 11):

$error_message_frisbees = check_number_ordered($frisbees, 15);

The next step is to check whether there are any errors. There were no errors if both the frisbees value and the giant ropes value were OK.

Here is the code:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
  $destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";
  header("Location:$destination_url");
  exit();
}

Part of Figure 8 (again). order.php

Line 14 has the test. Remember that the validation function check_number_ordered() returns an empty string. So that if both $error_message_frisbees and $error_message_giant_chew_ropes are empty strings, there were no errors.

If the test is true, we need to tell the browser to jump to the order processing page (order.php). To jump to another page, send the HTTP header location, like this:

header('location: Destination ');

So to jump to the order processing page (line 16):

header('location: order.php');

But we also need to send the order data to order.php. How?

By doing a pretend get.

Renata
Renata

Huh?

Remember that there are two ways to send form data:

<form method="get">

and

<form method="post">

It’s the get method we care about here.

The get method attaches form data to the URL, like this:

order.php?frisbees=2&giant_chew_ropes=4

The destination page – order.php – can retrieve the data like this:

$frisbees = $_GET['frisbees'];
$giant_chew_ropes = $_GET['giant_chew_ropes'];

OK, so how to send the data? We make a pretend get by attaching data to the URL ourselves, rather than having a form do it. Like this:

$destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";

Remember that PHP replaces variables with their values in strings with double quotes (”). So if $frisbees had 2 in it, and $giant_chew_ropes had 4 in it, we would get:

$destination_url = "process.php?frisbees=2&giant_chew_ropes=4";

This is the same URL that a form with method="get" would produce. W00f!

Renata
Renata

Cool!

Once we have the URL, we can send it to the browser:

header("Location:$destination_url");

There is no point processing the rest of the page, so the next line is:

exit();

This tells the PHP interpreter to stop processing the page immediately, and return whatever it has so far to the Web server. All that it has is the header. The Web server returns that to the browser. The browser jumps to process.php, sending the order along for the ride.

Handling errors

If there are errors, we want this:

Error messages

Figure 5 (again). Error messages

We need to do three things:

  • Show error messages.
  • Show the form again.
  • Fill in the form’s values with what the user had typed.

Here’s how the error messages are shown.

<h1>Order Form</h1>
<?php
//Any error messages to display?
if ( $error_message_frisbees != '' ) {
  print "<p>Frisbees: $error_message_frisbees</p>";
}
if ( $error_message_giant_chew_ropes != '' ) {
  print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
}
?>

Part of Figure 8 (again). order.php

Now the second thing, showing the form. We use the usual HTML tags: <form>, <input>, etc.

The third thing we need to do is restore the data the user had typed into the fields. Whether the data was valid or not.

CC
CC

Why put data the user typed back into the input fields? Especially when some of it is invalid.

Two reasons.

  • There are two fields, and one of them might be correct. We don’t want the user to have to retype that data, just because the other field was bad.
  • The user might have typed several correct characters into a field, with just one of them wrong. It would be annoying if everything in the field just vanished.

So how to do it? You need to know about another attribute of the <input> tag: value. It lets you set the contents of a form field in HTML, without the user having to type anything. For example, here’s some HTML:

<input type="text" value="Dogs rock!">

This renders as:

<code>value</code> attribute

Figure 9. value attribute

You can try it. Notice that text appears in the field, without your typing anything.

Here is some code for our order form:

Frisbees:
<input type="text" name="frisbees" size="3"
<?php
if ( $_POST['frisbees'] ) {
  print ' value="' . $_POST['frisbees'] . '"';
}
?>
>

Part of Figure 8 (again). order.php

If $_POST['frisbees'] has, say, 7 in it, the code will create the HTML:

<input type="text" name="frisbees" size="3" value="7">

That’s the end of the page. W00f!

Improving error message collection

Here’s the error checking and reporting code.

//Validate input.
$error_message_frisbees = check_number_ordered($frisbees, 15);
$error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);
//Jump if no errors.
if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
...
//Any error messages to display?
if ( $error_message_frisbees != '' ) {
  print "<p>Frisbees: $error_message_frisbees</p>";
}
if ( $error_message_giant_chew_ropes != '' ) {
  print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
}

Part of Figure 8 (again). order.php

In this code, we have a different error message variable for each product. That’s fine, because we have only two products.

What if we had more than two products? For each one, we’d need to create a new variable. And Line 14 would get more complex. And we’d need to remember to add another if statement to report the error.

Here’s a better way of doing it.

//Validate input.
$messages = ''; //Variable to accumulate messages.
$error_message = check_number_ordered($frisbees, 15);
if ( $error_message != '' ) {
  $messages .= "<p>Frisbees: $error_message</p>";
}
$error_message = check_number_ordered($giant_chew_ropes, 10);
if ( $error_message != '' ) {
  $messages .= "<p>Giant chew ropes: $error_message</p>";
}
//Jump if no errors.
if ( $messages == '' ) {
...
<h1>Order Form</h1>
<?php
//Any error messages to display?
if ( $messages != '' ) {
  print $messages;
}
?>

Figure 10. Improved error reporting

Line 11 creates a variable called $messages. It’s used to collect error messages. When a new error is detected, some text is added to this variable.

Line 12 checks the frisbees:

$error_message = check_number_ordered($frisbees, 15);

Remember that check_number_ordered() returns an empty string if $frisbees is OK, otherwise it returns an error message.

Here’s the next bit of code:

if ( $error_message != '' ) {
   $messages .= "<p>Frisbees: $error_message</p>";
}

.= is a shortcut operator. Remember that . means string concatenation, so:

'this' . 'that'

is:

thisthat

The line:

$x .= $y;

is a shortcut for:

$x = $x . $y;

So .= appends something to an existing variable.

This line:

$messages .= "<p>Frisbees: $error_message</p>";

creates an HTML paragraph tag with an error message in it, and adds it to whatever is already in $messages.

The next few lines are:

$error_message = check_number_ordered($giant_chew_ropes, 10);
if ( $error_message != '' ) {
   $messages .= "<p>Giant chew ropes: $error_message</p>";
}

If there is a problem with $giant_chew_ropes, an error message gets added to whatever is already in $messages.

If more products are added to the page, we can just repeat these few lines as needed. So if, for example, we started selling dog collars, we would have:

$error_message = check_number_ordered($dog_collars, 8);
if ( $error_message != '' ) {
   $messages .= "<p>Dog collars: $error_message</p>";
}

There is no need to create another variable for a dog collar error message. If there is an error, just add the new message to the other messages.

After checking each individual form field, we need an if statement to decide whether to jump to the order processing page. The browser should jump only if there were no errors.

Here’s what we had in the original code:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {

Each time we add a new product, we’d have to change this line. For example, if we had five products:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '' && $error_message_dog_collars == '' && $error_message_squeaky_balls == '' && $error_message_hide_bone == '' ) {

Ack! What a pain!

But with the new approach, we don’t need to do that. All error messages are added to the variable $messages. So if we want to check whether there have been any errors, we can just check that one variable (line 21 in Figure 10):

if ( $messages == '' ) {

Ten products? Twenty? It doesn’t matter. This line will never change.

When it comes to reporting errors to the user, we have this code:

if ( $messages != '' ) {
   print $messages;
}

Again, it doesn’t matter how many products there are, or how many potential error messages. Our code has collected all the error messages in $messages.

This is another example of how Webers think about productivity. Web pages change. Products are added and removed. This approach – accumulating error messages in a variable – makes it easier to make those changes.

Summary

In this lesson, you learned:

  • How to have the HTML for the form and the PHP validation code on the same page.
  • How to collect error messages in a single variable, rather than treating them all separately.

What now?

Here’s the error message display again:

Error messages

Figure 5 (again). Error messages

That’s, well, yucky. Let’s improve the user’s experience.

Better client-side error display

This lesson’s goals

By the end of this lesson, you should:

  • Know how to report errors in a professional way.
  • Understand how page-wide, global error messages help the user.
  • Understand how JavaScript functions make error reporting easier to program.

The goal

In the last couple of lessons, we improved the server-side error processing. We came up with something like this:

Ugly error messages

Figure 1. Ugly error messages

This is, well, blah. Let’s improve it. We want the order form to look like this:

Empty order form

Figure 2. Empty order form

Suppose there are errors:

Order form with errors

Figure 3. Order form with errors

Let’s show error messages like this:

Error messages

Figure 4. Error messages

Try it, and you’ll see the error messages are animated.

Field and global error messages

Each field has its own place for error messages, right below the field. In addition, at the top of the form, there’s what I am calling a “global error message.” It shows that there is some error on the page.

Why have a global error message? Two reasons.

Some errors aren’t about a particular field

Let’s change the way a blank field is interpreted. If a user leaves a field blank, we’ll assume the user doesn’t want to order any of the product. We won’t make the user enter zero into the field.

But this creates a problem. Suppose the user leaves all the fields blank and clicks the order button. There’s an error; nothing was ordered, so clicking the order button makes no sense. But the error isn’t associated with any one field.

Having a global error message gives us a place to put an error message like this:

Nothing ordered

Figure 5. Nothing ordered

Drawing attention

You might have a long form with, say, 12 fields. This gives you a long page. The user won’t be able to see all of the fields at the same time.

Browser window

Figure 6. Browser window

The user has to scroll to see the entire page.

The global error message makes it easier for the user to see whether there are any errors. The small error icon:

Error icon

helps the user scan down the page and find information about specific errors.

Let’s look at the client side error reporting: the HTML, CSS, and JavaScript. We’ll combine it with PHP in the next lesson.

HTML for the error messages

Let’s look at the HTML code for the global error message. That’s the message at the top of the page, telling the user whether there were any errors at all. Remember that it should look like this when displayed:

Global error message

Figure 7. Global error message

Here’s the HTML:

<p id="global_error_message_container" class="message_container">
  <img src="error.png" alt="Error">
  <span id="global_error_message"/>
</p>

Figure 8. HTML for the global error message

There’s a <span> inside a <p>. The <span> is used to show the text of the error message. The <p> is a container for both the <span> and an error icon. Showing and hiding the <p> will affect both the icon and the message, treating them as a single unit.

Notice how I have named them. The <span> has an id of global_error_message. The <p> has an id of global_error_message_container. I’ve used that convention throughout the page. The id of the outer element ends in _container.

The icon came from the famfamfam silk icon set. It’s one of the most complete icon sets you can find.

That was the global error message. The HTML for each field’s error message is similar. Here’s one of them.

<p>
  <input type="text" name="frisbees" id="frisbees" size="3">
  Frisbees ($8.95 each)<br>
  <span id="frisbees_message_container" class="message_container">
    <img src="error.png" alt="Error">
    <span id="frisbees_message"/>
  </span>
</p>

Figure 9. HTML for the frisbee message

The error message starts on line 136. Again, there’s an error message element and a container element. This time, the container is a <span>.

The product error messages have an extra naming convention. For product x, the elements are called x_message and x_message_container. So for frisbees, the ids are frisbees_message and frisbees_message_container.

CC
CC

Why do you use these naming rules?

Kieran
Kieran

Because it makes programming easier. You’ll see why later.

JavaScript

Here’s the code that’s executed when the user clicks the Order button.

$("#order_button").click(function() {
  //Set flag showing everything is OK.
  var data_ok = true;

  //Check the frisbees.
  var frisbees = $("#frisbees").val();
  var frisbees_message = check_order_value(frisbees);
  if ( frisbees_message != '' ) {
    data_ok = false;
    show_product_error_message(frisbees_message, 'frisbees');
  }
  else {
    hide_error_message('frisbees');
  }

  //Check the giant chew ropes.
  var giant_chew_ropes = $("#giant_chew_ropes").val();
  var giant_chew_ropes_message = check_order_value(giant_chew_ropes);
  if ( giant_chew_ropes_message != '' ) {
    data_ok = false;
    show_product_error_message(giant_chew_ropes_message, 'giant_chew_ropes');
  }
  else {
    hide_error_message('giant_chew_ropes');
  }

  //Check the squeaky balls.
  var squeaky_balls = $("#squeaky_balls").val();
  var squeaky_balls_message = check_order_value(squeaky_balls);
  if ( squeaky_balls_message != '' ) {
    data_ok = false;
    show_product_error_message(squeaky_balls_message, 'squeaky_balls');
  }
  else {
    hide_error_message('squeaky_balls');
  }

  //Make sure something was ordered.
  if ( frisbees + giant_chew_ropes + squeaky_balls == 0 ) {
    show_global_error_message("Sorry, you must order at least one item.");
    data_ok = false;
  }
  
  if ( data_ok ) {
    hide_global_error_message();
    alert('Everything is OK... so far.')
  }
});

Figure 10. JavaScript

Line 25:

var data_ok = true;

creates a variable that keeps track of whether the data is OK, that is, whether there has been an error. When we get to the end of the validation, we can check this variable to know whether there have been any errors.

Variables like this are often called “flags.” They are usually either true or false, like a physical flag being up or down.

Flag up Flag up, data OK. Variable data_ok is true.

Flag down Flag down, data not OK. Variable data_ok is false.

When the flag is up (the data is OK), we want to do one thing. When the flag is down (there has been an error), we want to do another thing.

Look at lines 28 and 29:

var frisbees = $("#frisbees").val();
var frisbees_message = check_order_value(frisbees);

The first one gets the value the user typed into the frisbee field and puts it into the variable frisbees. Line 29 calls the check_order_value() function, sending in frisbees.

check_order_value() is a JavaScript version of the PHP validation function we saw earlier. It does the same thing; returns either an error message, or an empty string if the the value is OK.

Here is the code:

//Check whether an order value is valid.
//Input
//  value_to_check: the value to check.
//Return: An error message, or empty string if no error.
function check_order_value(value_to_check) {
  if ( isNaN(value_to_check) ) {
    return 'Please enter a number';
  }
  if ( value_to_check != Math.round(value_to_check) ){
    return 'Please enter whole numbers only';
  }
  if ( value_to_check < 0 ){
    return 'Please enter positive numbers only';
  }
  return '';
}

Figure 11. check_order_value()

Line 80 contains something new.:

if ( value_to_check != Math.round(value_to_check) ){

This tests whether value_to_check is an integer, that is, a whole number. Math.round() rounds a number up or down. So:

Math.round(1.4) is 1

Math.round(1.7) is 2

Math.round(1) is 1

If you round a number that is already a whole number, you get the same number you started with. That is why the test works.

You can try round().

Back the to button pressing script. Here’s some more of it.

//Check the frisbees.
var frisbees = $("#frisbees").val();
var frisbees_message = check_order_value(frisbees);
if ( frisbees_message != '' ) {
  data_ok = false;
  show_product_error_message(frisbees_message, 'frisbees');
}
else {
  hide_error_message('frisbees');
}

Part of Figure 10 (again). JavaScript

Line 29 puts either an error message or an empty string in frisbees_message.

If frisbees_message isn’t empty (line 30), the flag gets set to false (line 31), so we can know later that there’s been a problem. Then this is run:

show_product_error_message(frisbees_message, 'frisbees');

This shows the message in the frisbees error spot. It might end up looking like this:

Frisbee error

Figure 11. Frisbee error

I want the show_product_error_message() function to work for every product. That’s why I pass in the product name (frisbees in this case), so the function will know where to show the error message.

Here’s what show_product_error_message() does, in pseudocode.

Set the product error text.
Show the text.
Set the global error text.
Show it.

Here’s the code:

//Show an error message for a product.
//Input
//  message: The message to show.
//  product_name: The product name the message is about.
//Return: nothing.
function show_product_error_message(message, product_name) {
  //Show the error message in the product's message area.
  $('#' + product_name + '_message').text(message);
  if ( $('#' + product_name + '_message_container').is(':hidden') ) {
    $('#' + product_name + '_message_container').show('medium');
  }
  //Show the global error message at the top of the page.
  show_global_error_message("Sorry, I can't process this order.");
}

Figure 13. show_product_error_message()

Look at line 96:

$('#' + product_name + '_message').text(message);

product_name is something like frisbee. So line 96 becomes:

$('#frisbee_message').text(message);

This matches the id in the HTML element:

<p>
  <input type="text" name="frisbees" id="frisbees" size="3">
  Frisbees ($8.95 each)<br>
  <span id="frisbees_message_container" class="message_container">
    <img src="error.png" alt="Error">
    <span id="frisbees_message"/>
  </span>
</p>

Figure 9 (again). HTML for the frisbee message

There it is, in line 138.

Renata
Renata

Aha! So that’s why you used those naming rules!

Kieran
Kieran

Yes! That’s it.

Here are the next lines in show_product_error_message() (Figure 12):

if ( $('#' + product_name + '_message_container').is(':hidden') ) {
  $('#' + product_name + '_message_container').show('medium');
}

Part of Figure 12 (again). show_product_error_message()

This will detect whether the message’s container is hidden, and, if it is, show it.

Remember the pseudocode.

Set the product error text.
Show the text.
Set the global error text.
Show it.

We’ve done the first two. How about the last two? Like this:

show_global_error_message("Sorry, I can't process this order.")

That’s right, another function.

//Show a global error message. It applies to the entire
//  page, not just one product.
//Input
//  message: The message to show.
//Return: nothing.
function show_global_error_message(message) {
  $("#global_error_message").text(message);
  if ($('#global_error_message_container' ).is(':hidden') ) {
    $('#global_error_message_container').show('medium');
  }
}

Figure 13. show_global_error_message()

It’s much like show_product_error_message(), but without the product name part.

Let’s look at the last part of a field’s validation:

//Check the frisbees.
var frisbees = $("#frisbees").val();
var frisbees_message = check_order_value(frisbees);
if ( frisbees_message != '' ) {
  data_ok = false;
  show_product_error_message(frisbees_message, 'frisbees');
}
else {
  hide_error_message('frisbees');
}

Part of Figure 10 (again). JavaScript

If there’s no error, then line 35 runs:

hide_error_message('frisbees');

Here’s the function:

//Hide a product error message.
//Input
//  product_name: The product name the message is about.
//Return: nothing.
function hide_error_message(product_name) {
  $('#' + product_name + '_message_container').hide('medium');
}

Figure 14. hide_error_message()

Send it a product_name, and it hides the error message for that product.

There is one more check we have to do. What if the user orders none of every product?

//Make sure something was ordered.
if ( frisbees + giant_chew_ropes + squeaky_balls == 0 ) {
  show_global_error_message("Sorry, you must order at least one item.");
  data_ok = false;
}

Figure 15. Was anything ordered?

What if there are no errors?

if ( data_ok ) {
  hide_global_error_message();
  alert('Everything is OK... so far.')
}

Figure 16. Nothing wrong

hide_global_error_message() does what you think. Here it is:

//Hide global error message.
//Return: nothing.
function hide_global_error_message() {
  if ($('#global_error_message_container' ).is(':visible') ) {
  $('#global_error_message_container').hide('medium');
  }
}

Figure 17. Hide the global error message

hide() only runs if the error message is visible.

Nothing else happens, except for an alert(). We’ll add the PHP in the next lesson.

The entire page

Here’s all the code together. Run through it again in your mind, so you see how it all fits together.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Order Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
      .message_container {
        display: none;
        font-weight: bold;
        color: red;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#global_error_message_container").hide();
        $("#frisbees").focus();
        $("#order_button").click(function() {
          //Set flag showing everything is OK.
          var data_ok = true;

          //Check the frisbees.
          var frisbees = $("#frisbees").val();
          var frisbees_message = check_order_value(frisbees);
          if ( frisbees_message != '' ) {
            data_ok = false;
            show_product_error_message(frisbees_message, 'frisbees');
          }
          else {
            hide_error_message('frisbees');
          }

          //Check the giant chew ropes.
          var giant_chew_ropes = $("#giant_chew_ropes").val();
          var giant_chew_ropes_message = check_order_value(giant_chew_ropes);
          if ( giant_chew_ropes_message != '' ) {
            data_ok = false;
            show_product_error_message(giant_chew_ropes_message, 'giant_chew_ropes');
          }
          else {
            hide_error_message('giant_chew_ropes');
          }

          //Check the squeaky balls.
          var squeaky_balls = $("#squeaky_balls").val();
          var squeaky_balls_message = check_order_value(squeaky_balls);
          if ( squeaky_balls_message != '' ) {
            data_ok = false;
            show_product_error_message(squeaky_balls_message, 'squeaky_balls');
          }
          else {
            hide_error_message('squeaky_balls');
          }

          //Make sure something was ordered.
          if ( frisbees + giant_chew_ropes + squeaky_balls == 0 ) {
            show_global_error_message("Sorry, you must order at least one item.");
            data_ok = false;
          }
          
          if ( data_ok ) {
            hide_global_error_message();
            alert('Everything is OK... so far.')
          }
        });
      });

      //Check whether an order value is valid.
      //Input
      //  value_to_check: the value to check.
      //Return: An error message, or empty string if no error.
      function check_order_value(value_to_check) {
        if ( isNaN(value_to_check) ) {
          return 'Please enter a number';
        }
        if ( value_to_check != Math.round(value_to_check) ){
          return 'Please enter whole numbers only';
        }
        if ( value_to_check < 0 ){
          return 'Please enter positive numbers only';
        }
        return '';
      }

      //Show an error message for a product.
      //Input
      //  message: The message to show.
      //  product_name: The product name the message is about.
      //Return: nothing.
      function show_product_error_message(message, product_name) {
        //Show the error message in the product's message area.
        $('#' + product_name + '_message').text(message);
        if ( $('#' + product_name + '_message_container').is(':hidden') ) {
          $('#' + product_name + '_message_container').show('medium');
        }
        //Show the global error message at the top of the page.
        show_global_error_message("Sorry, I can't process this order.");
      }

      //Show a global error message. It applies to the entire
      //  page, not just one product.
      //Input
      //  message: The message to show.
      //Return: nothing.
      function show_global_error_message(message) {
        $("#global_error_message").text(message);
        if ($('#global_error_message_container' ).is(':hidden') ) {
          $('#global_error_message_container').show('medium');
        }
      }

      //Hide a product error message.
      //Input
      //  product_name: The product name the message is about.
      //Return: nothing.
      function hide_error_message(product_name) {
        $('#' + product_name + '_message_container').hide('medium');
      }

      //Hide global error message.
      //Return: nothing.
      function hide_global_error_message() {
        if ($('#global_error_message_container' ).is(':visible') ) {
          $('#global_error_message_container').hide('medium');
        }
      }
    </script>
  </head>
  <body>
    <h1>Order Form</h1>
    <p>What would you like?</p>
    <p id="global_error_message_container" class="message_container">
      <img src="error.png" alt="Error">
      <span id="global_error_message"/>
    </p>
    <p>
      <input type="text" name="frisbees" id="frisbees" size="3">
      Frisbees ($8.95 each)<br>
      <span id="frisbees_message_container" class="message_container">
        <img src="error.png" alt="Error">
        <span id="frisbees_message"/>
      </span>
    </p>
    <p>
      <input type="text" name="giant_chew_ropes" id="giant_chew_ropes" size="3">
      Giant chew ropes ($12.95 each)<br>
      <span id="giant_chew_ropes_message_container" class="message_container">
        <img src="error.png" alt="Error">
        <span id="giant_chew_ropes_message"/>
      </span>
    </p>
    <p>
      <input type="text" name="squeaky_balls" id="squeaky_balls" size="3">
      Squeaky balls to chase ($1.95 each)<br>
      <span id="squeaky_balls_message_container" class="message_container">
        <img src="error.png" alt="Error">
        <span id="squeaky_balls_message"/>
      </span>
    </p>
    <p>
      <button id="order_button" type="button">Order</button>
    </p>
  </body>
</html>

Figure 18. The entire page

Make sure you try the page. Type in invalid data, and see what happens.

Exercise: Sign-up form

Make a sign-up page for a Web service. It starts out like this:

Empty form

Figure 1. Empty form

All fields must be completed. Suppose the user does this:

Empty field

Figure 2. An empty field

Here is the result:

Error messages

Figure 3. Error messages

Hint: Make a function called check_value() that returns either an error message or an empty string. The only error is an empty field.

You can see my solution, but don’t look at the source code until you do it yourself first!

Upload your solution to your server. Enter the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

You learned:

  • How to report errors in a professional way, with colors, icons, and animated effects.
  • How page-wide, global error messages help the user.
  • How JavaScript functions make error reporting easier to program.

What now?

Let’s put everything together. We’ll do form validation that:

  • Has professional-looking error messages.
  • Shows client-side and server-side error messages in the same way. There’s no need to bother the user with the difference.

Complete validation

Thanks to Miles Thompson for his help with this lesson.

Where are we?

OK, let’s bring it all together.

  • We’ll use the nice error reporting we saw in the previous lesson.
  • We’ll use JavaScript to check for client-side errors (like non-numbers in numeric fields), and PHP to check for errors on the server side.
  • Both client-side and server-side errors will be reported in the same way.

What the user sees

Here’s how things start:

Empty form

Figure 1. Empty form

Client-side checking makes error messages like this:

Error messages

Figure 2. Error messages

What if the user wants to order more than we have on hand?

Too many

Figure 3. Too many

The error message in Figure 3 tells the user how many we have on hand.

Again, this may not be good business practice, but it’s good for learning about PHP.

Try the page. Try making various kinds of errors. You can also download the files.

The overall pattern

The overall processing pattern is the same as before:

Overall pattern

Figure 4. Overall pattern

The form page passes data to itself for validation. If everything is OK, the page passes the data to a processing page.

There are a few improvements over the client-side only version on the previous page.

  • Empty fields are handled better.
  • Check on-hand inventory.
  • Server-side errors (like ordering more than are on hand) are reported to the user with the client-side error reporting mechanism (the animated messages with the icon).

The complete order.php code

Here’s the complete code. We’ll talk about the changes in the next section.

<?php
if ( $_POST ) {
  //There is order data to process.
  require 'validation-library.inc';
  //Get the order amounts.
  $frisbees = $_POST['frisbees'];
  if ( $frisbees == '' ) {
    $frisbees = 0;
  }
  $giant_chew_ropes = $_POST['giant_chew_ropes'];
  if ( $giant_chew_ropes == '' ) {
    $giant_chew_ropes = 0;
  }
  $squeaky_balls = $_POST['squeaky_balls'];
  if ( !isset($squeaky_balls) || $squeaky_balls == '' ) {
    $squeaky_balls = 0;
  }

  //Flag for data OK.
  $data_ok = true;
  //JavaScript to run after page load.
  $js = '';

  //Check frisbees.
  $frisbees_message = check_number_ordered($frisbees);
  if ( $frisbees_message != '' ) {
    $data_ok = false;
    $js .= 'show_product_error_message("'.$frisbees_message . '", "frisbees");';
  }

  //Check giant chew ropes.
  $giant_chew_ropes_message = check_number_ordered($giant_chew_ropes);
  if ( $giant_chew_ropes_message != '' ) {
    $data_ok = false;
    $js .= 'show_product_error_message("'.$giant_chew_ropes_message . '", "giant_chew_ropes");';
  }

  //Check squeaky balls.
  $squeaky_balls_message = check_number_ordered($squeaky_balls);
  if ( $squeaky_balls_message != '' ) {
    $data_ok = false;
    $js .= 'show_product_error_message("'.$squeaky_balls_message . '", "squeaky_balls");';
  }

  //Check that at least one item was ordered.
  if ( $frisbees + $giant_chew_ropes + $squeaky_balls == 0 ) {
    $data_ok = false;
    $js .= 'show_product_global_error_message("Sorry, you must order at least one item.");';
  }

  //Check number on hand.
  //Normally number on hand would be loaded from a database.
  //For simplicity, I just made up some numbers.
  $frisbees_on_hand = 5;
  $giant_chew_ropes_on_hand = 4;
  $squeaky_balls_on_hand = 8;
  if ( $frisbees > $frisbees_on_hand ) {
    $data_ok = false;
    $js .= "show_product_error_message('Sorry, we only have $frisbees_on_hand on hand.', 'frisbees');";
  }
  if ( $giant_chew_ropes > $giant_chew_ropes_on_hand ) {
    $data_ok = false;
    $js .= "show_product_error_message('Sorry, we only have $giant_chew_ropes_on_hand on hand.', 'giant_chew_ropes');";
  }
  if ( $squeaky_balls > $squeaky_balls_on_hand ) {
    $data_ok = false;
    $js .= "show_product_error_message('Sorry, we only have $squeaky_balls_on_hand on hand.', 'squeaky_balls');";
  }

  //Is everything OK?
  if ( $data_ok ) {
    //Process the order.
    $destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes&squeaky_balls=$squeaky_balls";
    header("Location:" . $destination_url);
    exit();
  }
}
?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Order Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
      .message_container {
        display: none;
        font-weight: bold;
        color: red;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#global_error_message_container").hide();
        $("#frisbees").focus();
        <?php print $js; ?>
        $("#order_form").submit(function() {
          //Set flag showing everything is OK.
          var data_ok = true;

          //Check the frisbees.
          var frisbees = $("#frisbees").val();
          var frisbees_message = check_order_value(frisbees);
          if ( frisbees_message != '' ) {
            data_ok = false;
            show_product_error_message(frisbees_message, 'frisbees');
          }
          else {
            hide_error_message('frisbees');
          }

          //Check the giant chew ropes.
          var giant_chew_ropes = $("#giant_chew_ropes").val();
          var giant_chew_ropes_message = check_order_value(giant_chew_ropes);
          if ( giant_chew_ropes_message != '' ) {
            data_ok = false;
            show_product_error_message(giant_chew_ropes_message, 'giant_chew_ropes');
          }
          else {
            hide_error_message('giant_chew_ropes');
          }

          //Check the squeaky balls.
          var squeaky_balls = $("#squeaky_balls").val();
          var squeaky_balls_message = check_order_value(squeaky_balls);
          if ( squeaky_balls_message != '' ) {
            data_ok = false;
            show_product_error_message(squeaky_balls_message, 'squeaky_balls');
          }
          else {
            hide_error_message('squeaky_balls');
          }

          //Make sure something was ordered.
          if ( frisbees + giant_chew_ropes + squeaky_balls == 0 ) {
            show_global_error_message("Sorry, you must order at least one item.");
            data_ok = false;
          }
          return data_ok;
        });
      });

      //Check whether an order value is valid.
      //Input
      //  value_to_check: the value to check.
      //Return: An error message, or empty string if no error.
      function check_order_value(value_to_check) {
        if ( isNaN(value_to_check) ) {
          return 'Please enter a number';
        }
        if ( value_to_check != Math.round(value_to_check) ){
          return 'Please enter whole numbers only';
        }
        if ( value_to_check < 0 ){
          return 'Please enter positive numbers only';
        }
        return '';
      }

      //Show an error message for a product.
      //Input
      //  message: The message to show.
      //  product_name: The product name the message is about.
      //Return: nothing.
      function show_product_error_message(message, product_name) {
        //Show the error message in the product's message area.
        $('#' + product_name + '_message').text(message);
        if ( $('#' + product_name + '_message_container').is(':hidden') ) {
          $('#' + product_name + '_message_container').show('medium');
        }
        //Show the global error message at the top of the page.
        show_global_error_message("Sorry, I can't process this order.");
      }

      //Show a global error message. It applies to the entire
      //  page, not just one product.
      //Input
      //  message: The message to show.
      //Return: nothing.
      function show_global_error_message(message) {
        $("#global_error_message").text(message);
        if ($('#global_error_message_container' ).is(':hidden') ) {
          $('#global_error_message_container').show('medium');
        }
      }

      //Hide a product error message.
      //Input
      //  product_name: The product name the message is about.
      //Return: nothing.
      function hide_error_message(product_name) {
        $('#' + product_name + '_message_container').hide('medium');
      }

    </script>
  </head>
  <body>
    <h1>Order Form</h1>
    <p>What would you like?</p>
    <p id="global_error_message_container" class="message_container">
      <img src="error.png" alt="Error">
      <span id="global_error_message"/>
    </p>
    <form id="order_form" method="post" action="order.php">
      <p>
        <input type="text" name="frisbees" id="frisbees" size="3"
        <?php
        if ( isset($_POST['frisbees']) ) {
          print ' value="' . $_POST['frisbees'] . '"';
        }
        ?>
        >
        Frisbees ($8.95 each)<br>
        <span id="frisbees_message_container" class="message_container">
          <img src="error.png" alt="Error">
          <span id="frisbees_message"/>
        </span>
      </p>
      <p>
        <input type="text" name="giant_chew_ropes" id="giant_chew_ropes" size="3"
        <?php
        if ( isset($_POST['giant_chew_ropes']) ) {
          print ' value="' . $_POST['giant_chew_ropes'] . '"';
        }
        ?>
        >
        Giant chew ropes ($12.95 each)<br>
        <span id="giant_chew_ropes_message_container" class="message_container">
          <img src="error.png" alt="Error">
          <span id="giant_chew_ropes_message"/>
        </span>
      </p>
      <p>
        <input type="text" name="squeaky_balls" id="squeaky_balls" size="3"
        <?php
        if ( isset($_POST['squeaky_balls']) ) {
          print ' value="' . $_POST['squeaky_balls'] . '"';
        }
        ?>
        >
        Squeaky balls to chase ($1.95 each)<br>
        <span id="squeaky_balls_message_container" class="message_container">
          <img src="error.png" alt="Error">
          <span id="squeaky_balls_message"/>
        </span>
      </p>
      <p>
        <button type="submit">Order</button>
      </p>
    </form>
  </body>
</html>

Figure 5. Complete order.php

Code changes

We’ve seen most of this before. But there are some changes.

Blank is zero

When a field is empty, the program decides the user wants none of that product:

//Get the order amounts.
$frisbees = $_POST['frisbees'];
if ( $frisbees == '' ) {
  $frisbees = 0;
}

Figure 6. When a field is empty

Form submission

On the previous page, where we did client-side only, we didn’t have a <form> tag. We attached validation code to a button:

$(document).ready(function() {
  ...
  $("#order_button").click(function() {
    ...
  });
});
...
<button id="order_button" type="button">Order</button>

Figure 7. Validation without a form

This time, we do have a <form>. We don’t attach the validation code to the click() event of a <button>. We attach it to the submit() event of the <form>.

$(document).ready(function() {
  ...
  $("#order_form").submit(function() {
    ...
  });
});
...
<form id="order_form" method="post" action="order.php">
  ...
  <button type="submit">Order</button>
  ...
</form>

Figure 8. Triggering validation with a form

We need a way to stop data from being submitted if it is invalid. We do this by returning either true or false from the submit() function.

  • If the submit() function returns true, the browser goes ahead and submits the data to the page given in the action property (e.g., action="order.php").
  • If the submit() function returns false, the browser does not send the data. It does nothing.

We can use this as follows:

$("#order_form").submit(function() {
  ...
  var data_ok = true;
  ...
  if ( [there is an error] ) {
    ...
    data_ok = false;
  }
  ...
  return data_ok;
});

Figure 9. Stopping form submission

The variable data_ok is set to true initially. If there is an error, data_ok is set to false. Eventually, the browser gets to the line:

return data_ok;

If there have been no errors, data_ok will still be set to true, the value it started off with. Return it, and the browser will send the data. But if there has been an error, data_ok will be false. Return it, and the browser will not send the data to the server.

Error reporting

Let’s look at the code the reports server-side errors to the user.

By the way, the variable $data_ok is a server-side PHP variable. It has nothing to do with the JavaScript variable data_ok.

//Flag for data OK.
$data_ok = true;
//JavaScript to run after page load.
$js = '';

//Check frisbees.
$frisbees_message = check_number_ordered($frisbees);
if ( $frisbees_message != '' ) {
  $data_ok = false;
  $js .= 'show_product_error_message("'.$frisbees_message . '", "frisbees");';
}

Figure 10. Error reporting

Most of it should be familiar, but there is one new thing. Line 22:

$js = '';

This PHP variable holds JavaScript code that reports errors to the user. Let’s see how it works.

Suppose the user entered “bark!” in the frisbees field. Line 25:

$frisbees_message = check_number_ordered($frisbees);

would put “Please enter a number” in $frisbees_message. Remember, this is PHP code, running on the server.

We want to give this error message to the user. We want it to look the same as the error messages produced by the client-side code.

How? Well, we already have a JavaScript function that will report error messages. We saw it on the previous page. Here it is again:

function show_product_error_message(message, product_name) {
  //Show the error message in the product's message area.
  $('#' + product_name + '_message').text(message);
  if ( $('#' + product_name + '_message_container').is(':hidden') ) {
    $('#' + product_name + '_message_container').show('medium');
  }
  //Show the global error message at the top of the page.
  show_global_error_message("Sorry, I can't process this order.");
}

Figure 11. JavaScript function to show an error message

show_product_error_message() takes two arguments: the error message, and the product name.

So, we already have this function that will report errors. But it’s a JavaScript function. It runs in the browser. We want to call it from PHP code that runs on the server.

How?

Well, how about writing PHP code that writes JavaScript code that calls the function?

Like this (line 28 in Figure 10):

$js .= 'show_product_error_message("'.$frisbees_message . '", "frisbees");';

Suppose $frisbees_message has ‘Please enter a number’ in it. We get the string:

show_product_error_message("Please enter a number", "frisbees");

The string is appended to the PHP variable $js.

We can do something similar for the on-hand check:

if ( $frisbees > $frisbees_on_hand ) {
  $data_ok = false;
  $js .= "show_product_error_message('Sorry, we only have $frisbees_on_hand on hand.', 'frisbees');";
}

Figure 12. Checking inventory on hand

Notice that we have:

$js .=

We keep adding to $js as we find errors.

What do we do with $js? If we could insert it into the page along with the other JavaScript, it would get executed, and the error message(s) would appear. Here’s code that would do it.

<script type="text/javascript">
$(document).ready(function() {
  $("#global_error_message_container").hide();
  $("#frisbees").focus();
  <?php print $js; ?>

Figure 13. Inserting JavaScript error reporting code

Line 100 puts the contents of the PHP variable $js into the output stream. The variable contains some executable JavaScript code. The code is added into the $(document).ready() (starts in line 97), so it runs when the page loads.

To see how this works, try it, and enter large numbers in the form fields:

Too many again

Figure 14. Too many again

When you get a display like this, have a look at the source code of the page. You’ll see something like this:

<script type="text/javascript">
$(document).ready(function() {
  $("#global_error_message_container").hide();
  $("#frisbees").focus();
  show_product_error_message('Sorry, we only have 5 on hand.', 'frisbees');show_product_error_message('Sorry, we only have 4 on hand.', 'giant_chew_ropes');show_product_error_message('Sorry, we only have 8 on hand.', 'squeaky_balls');

Figure 15. Generated code

Line 5 was generated by PHP code. But the browser doesn’t know where it came from. It’s just there, and gets run along with everything else.

CC
CC

I’m confused. The PHP adds JavaScript? Won’t it be part of the page forever, even when the user didn’t make a mistake?

Kieran
Kieran

Remember that order.php is a PHP program, not a static HTML file. order.php can send different HTML and JavaScript to the browser, depending on the circumstances.

So when it inserts JavaScript into the HTML stream, it doesn’t permanently change order.php itself. Just what the user sees that particular time order.php is run.

But this can be hard to understand. There’s a lot going on. Let’s break it down.

How the user experiences errors

We want to give users a smooth experience with the form. That’s what matters in the end: can users easily order products? They don’t care about clients and servers and such. They just care what they see in the browser.

Let’s see how one user, Jeff, experienced the order form. Keep this is mind:

Structure

Figure 16. Structure

Jeff
Jeff
Firefox
Browser
PHP
Server
Looking at site's home page.

Clicks link to go to order form.
   
  Asks server for order.php  
    Starts order.php.

No form data passed (1 in Figure 16).

Generate page to show empty form (3 in Figure 16).

Send HTML and JavaScript to browser.
  Render page from server. Blank form.

Blank form
 
Types "lots" into frisbee field.

Clicks Order button.

Lots
   
  Runs JavaScript validation code.

"lots" is not a valid number.

Reports error.

Lots error

Note: Nothing sent to the server. The browser handles this error itself.
 
Changes "lots" to 400.

Clicks Order button.
   
  Runs JavaScript validation code.

400 is a valid number.

Asks server for order.php

Sends data: frisbees=400
 
    Starts order.php.

Note: This is the same page! order.php sent data to itself.

Form data passed:
  frisbees=400

Note: The first time the browser asked for order.php, it passed no form data. This time is different.

Run validation check.

Only have 5 frisbees.

Generate HTML and JavaScript for page.

Add into $(document).ready():

show_product_error_message(
'Sorry, we only have 5 on hand.', 'frisbees');


Send HTML and JavaScript to browser.
  Render page.

Show error message during page load.

400 error

Note: This error looks the same as the "Invalid number" error. At this point, Jeff has only ever seen order.php. But the server has run order.php twice. The first run created a blank form. The second run created a page with an error message and a form.
 
Erases 400 from frisbees field.

Puts 4 into frisbees field.

Clicks Order button.
   
  Runs JavaScript validation code.

4 is a valid number.

Asks server for order.php

Sends data: frisbees=4

Note: Calling the same page again. Still on order.php.
 
    Starts order.php.

Form data passed:
  frisbees=4

Run validation check.

Data valid.

Generate HTTP response with header:

Location:process.php?

  frisbees=4


Send HTTP response to browser.
  Gets HTTP redirect from server.

Asks server for process.php

Note: Finally! Going to a different page.

Sends data: frisbees=4
 
    Starts process.php.

Form data passed:
  frisbees=4

Run validation check.

Data valid.

Process order. (Send messages to accounting, shipping, etc.)

Generate HTML for order confirmation page.

Send page to browser.
  Render confirmation page.  
W00f!

Prints confirmation page.
   

Try the page, and run through the same steps Jeff did.

An important note:

Jeff’s experience is seamless.

The order page reloads after Jeff enters 400 frisbees and clicks the Order button. But he might not even notice the reload, if the server is fast enough. The browser shows the same color and fonts and headings and fields.

The key to this is that the same program – order.php – creates different HTML and JavaScript, depending on the situation.

Restoring values

One last piece of code to look at. This is from the HTML that makes the order form.

<input type="text" name="frisbees" id="frisbees" size="3"
<?php
if ( isset($_POST['frisbees']) ) {
  print ' value="' . $_POST['frisbees'] . '"';
}
?>
>

Figure 17. <input> field

Look at the source code in your browser again, you’ll see:

<input type="text" name="frisbees" id="frisbees" size="3" value="100">

(I left out some spaces.)

You can see that it puts the value the user typed into the field.

But the other thing is that the <input> tag has both name and id attributes. Why both? The name is needed to send the form data to the server. The id is used by the client-side JavaScript code.

This is by far the most complex page we’ve talked about. To get a better sense of how the whole thing fits together, download the files.

Summary

This lessons shows a complete form validation example.

  • It shows good error messages.
  • Some error checking is on the server side. Some is on the client side.
  • Bother server-side and client-side error messages are reported in the same way. That’s done by writing PHP code that calls JavaScript code.

What now?

Time for more exercises.

Exercises: Checking form data

Exercise: Ordering collars

Create a form like this:

Empty form

Figure 1. Empty form

The user enters a model number and the quantity. Model number must be either 23 or 39. Quantity must be a number greater than zero.

The data is sent to a PHP page for validation. All validation takes place in PHP!

If the data is OK, just show it:

Valid data

Figure 2. Valid data

But suppose there’s an error, like no model number:

No model

Figure 3. No model

Show an error message like this:

Error message

Figure 4. Error message

Here are the error messages your page should generate:

  • Please enter the model number.
  • Sorry, model must be 23 or 39.
  • Please enter the quantity.
  • Please enter a number for quantity.
  • Please enter a quantity more than zero.

You can try my solution.

Upload your solution to your server. Put your URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: New product form

Create a form that lets a user enter information about a new product. (Later, you’ll learn how to add this data to a database. This exercise is just about the form part.)

The form looks like this when opened:

Empty form

Figure 1. Empty form

If the user leaves all the fields blank and clicks the button, show:

Empty form with error messages

Figure 2. Empty form with error messages

Numbers must be valid:

Bad number

Figure 3. Bad number

All of these checks are done on the client side.

There is one check that is done on the server side only: the selling price cannot be less than the purchase cost. If it is, show:

Purchase cost less than selling price

Figure 4. Purchase cost less than selling price

If everything is OK, show a confirmation page:

Confirmation

Figure 5. Confirmation

You can try my solution and download the files. But do it yourself first!

Here is the error icon:

Error icon

The icon came from the famfamfam silk icon set. It’s one of the most complete icon sets you can find.

Hints:

  • There are text input fields. Remember to use the stripslashes() function that we talked about earlier. For example:

$product_name = stripslashes($_POST['product_name']);
print stripslashes($_POST['description']);

  • Notice that when the selling price is less than the purchase price, the error message shows in the global error message area.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Idea saver

Write a PHP application that saves ideas for projects to a file. You can try the application. (I disabled the idea saving bit.)

Here is the home page:

Home page

Figure 1. Home page

Note that the input focus is in the first field when the page loads.

You can download the background image. It’s from an OSWD template. You can also download the error icon.

The user completes all of the fields. If any fields are left blank, the user sees a page like this:

Missing data

Figure 2. Missing data

Checks for missing data are done on the client.

Only some users are allowed to post ideas about some projects.

  • Users beth.jackson and zon.plaske are allowed to post ideas about the Grendel project.
  • Users jed.mccurry and fhit.jhoges are allowed to post ideas about the Bathron project.

If the user is not allowed to post about the project, the following appears:

No permission

Figure 3. No permission

The message is “Sorry, that user does not have permission to post ideas about that project. Please check the project and user name.”

If the user is allowed…

Valid data

Figure 4. Valid data

... the post is saved to a file, and a confirmation shown:

Confirmation

Figure 5. Confirmation

The project/user permission check must be done on the server! In PHP. Not in JavaScript! You can use the pattern from the Complete validation page.

Click the “Show ideas” link, and you see a list of all the ideas saved:

Idea list

Figure 6. Idea list

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Basic database applications

A database is an collection of structured data. “Structured” means that the same data is stored for a bunch of different things. This timetable has some structured data:

Timetable

Figure 1. Timetable

There are a bunch of rail stops. Each one has a name (e.g., Hicksville) and a price (56 cents). The same type of information is stored for each stop: name and price.

Here’s another example of structured data. It’s part of actress Alyson Hannigan’s filmography.

Alyson Hannigan's filmography

Figure 2. Alyson Hannigan’s filmography

OK, so I have a thing for Willow.

There are a bunch of films. For each one, there’s the same data: year, film name, and role. The format of the data is predictable.

Structured data is often shown in tables, as you can see.

Here’s one more example.

Most popular dog breeds in the US

Figure 3. Most popular dog breeds in the US

Again, there’s a table, with rows and columns. Each row is about a dog breed. Each column has data about breeds. Every row has the same format.

So structured data has predictable information about things of the same type, like rail stops, films, or dog breeds. What’s unstructured data look like?

Here’s some unstructured data.

Ozymandias

Figure 4. Ozymandias

It’s the poem Ozymandias, by Shelley. There’s no predictable format.

PHP programs have an easier time processing structured than unstructured data. For example, a PHP program could take the dog breed data and compute the total number of registrations.

But there isn’t much a PHP program can do with a poem, except show it. It couldn’t tell you what the poem meant.

So a database is a collection of structured data. Some Web sites draw information from databases to create Web pages. For example, Amazon.com has a database, with data on the products they have. When your browser asks for a page, a program runs on their server. It gets product information from their database, and makes an HTML page out of it.

Amazon page

Figure 5. Amazon page

When you order something, your order goes into the database. Order data is structured. Each order has the same type of data: date, customer, products, etc. So one database can have structured data about different types of things: orders, customers, products, ...

A program tells the shipping people that there is a new order. They look in the database, and see your order. They put your stuff in a box, mail it to you, and update the database to show that the order was shipped.

The database ties different parts of the business together. Sales, shipping, receiving, billing, ... They all use data from the database.

DB ties business together

Figure 6. DB ties business together

This chapter shows you how database Web applications work. As usual, you’ll do a lot of hands-on stuff.

The chapter talks about databases that have just one table in them. I’ve found that people have trouble understanding databases with more than one table, because of table relationships. So I’ll delay talking about relationships until you know how single-table databases work.

You’ll need a Web hosting account with MySQL. If you followed my earlier recommendations, you have one.

Let’s start by looking at some sample database applications, so we know what we’re trying to acomplish.

Goal: A Web application

Where are we?

This chapter is about creating database Web applications. We need to know what a “database Web application” is.

This lesson’s goals

In this lesson, we:

  • Look at the architecture of two Web applications.
  • See what they have in common.
  • Talk about how you create Web applications.

Let’s look at some examples, where DB tech is key to a Web application.

DogToys, an ecommerce Web site

The goal

DogToys is an online store selling toys, like frisbees, chew ropes, and squeaky balls.

Customers see pages like this:

Product catalog

Figure 1. Product catalog

The catalog is the public face of the business. There’s a lot of activity behind the scenes. DogToys has to:

  • Select products people want to buy.
  • Decide on prices.
  • Write product descriptions.
  • Add new products.
  • Drop products that aren’t selling.
  • Update prices, dropping some and raising others.

DogToys has Web pages to help with these tasks. Customers can’t use these pages, just DogToys’ employees.

For example, here’s a screen that would let someone change a price:

Updating product data

Figure 2. Updating product data

Changing prices is quick and easy. No HTML. Change a number in a form, and click the Save button.

Here is what the customer sees after the change:

The new catalog

Figure 3. The new catalog

Architecture

How to make such an application? It’s easiest if there is one central data store, with all of the information about the products. A database.

The database has a products table, with every price in it, Here’s part of the product data:

Part of the product data in the DB

Figure 4. Part of the product data in the DB

When a customer visits the catalog Web page, a PHP program fetches the product from the database and shows it.

Customer viewing product data

Figure 5. Customer viewing product data

When a marketer changes a price, s/he fills in an HTML form. The form sends the data to a PHP program that writes the new price to the database.

Marketer changing product data

Figure 6. Marketer changing product data

DogToys has a bunch of PHP programs that all connect to the same database.

The system

Figure 7. The system

One PHP page shows the product catalog. This is all the customers are allowed to see.

Another PHP page updates product data. Another page adds a new product. Another removes a product. Employees use these pages to keep the catalog up-to-date.

The database is at the center of it all. Different PHP pages use the same database to show different people what they need.

Let’s look at another example of a Web application.

DogRock, a content management system

The goal

DogRock is a site about dog rock music. It has articles about new dog bands, albums, concerts, tours, and such.

Here’s a sample article:

An article

Figure 8. An article

People who write for DogRock aren’t Web experts. They don’t know what FTP means, or about nav bars and such. How to let writers add new stories without knowing much about Web tech?

That’s what a content management system (CMS) is for. CMS let nontech people manage Web site content.

When a user goes to the site, s/he sees an article list:

Article list

Figure 9. Article list

Suppose Turlough, one of DogRock’s authors, writes a new article. He creates it by filling in a form:

Writing an article

Figure 10. Writing an article

When Turlough has finished writing his story, it will show up on the article list:

New article list

Figure 11. New article list

Click on the link, and read the article:

Article

Figure 12. Article

Turlough created the new article by filling in a form on a Web page. He didn’t make an HTML file, or FTP anything.

This is a Big Thing that CMS do: non-tech people can create Web pages.

Architecture

Articles aren’t stored in HTML files. Instead, they’re stored in a database table.

Article table

Figure 13. Article table

A bunch of PHP pages let people read articles, write new ones, edit articles, and delete them. Which pages people are allowed to access depends on whether they are a reader or writer.

DogRock architecture

Figure 14. DogRock architecture

Things in common

Both of these systems – ecommerce and CMS – help different people do different tasks. But they have things in common.

Database

There’s a central database in each one.

CRUD

In both systems, people do four things to data:

  • Create (e.g., add a new article)
  • Read (e.g., read an article)
  • Update (e.g., change a product’s price)
  • Delete (e.g., remove a product)

DogToys and DogRock are CRUD systems.

User roles

People do different tasks with the same system, that is, they have different roles. For the ecommerce system, people with the “customer” role do some things (like look at the catalog). People with the “employee” role do different things, like change prices.

With the CMS, there’s the “writer” role and the “reader” role. Writers can create and edit stories. Readers can only read them.

Making a CRUD system

Here are the main steps in making a CRUD Web application:

  • Lists the tasks the system should support.

For example, for the DogRock CMS: “Create article, read article, update article, and delete article.”

  • Figure out what data is needed to support the tasks.

Each article has a title (“The Barkers to tour”), a publication date (June 15, 2011), and a body (“Those old time rockers, The Barkers, are at it again! The band last toured in 2007, when…”).

  • Create the database.
  • Write PHP to let people keep the data up-to-date.

add-article.php is a page that lets people add a new article, delete-article.php is a page that lets people erase an article, ...

  • Write PHP to let people see the data.

articles.php lists all the articles, show-article.php shows a particular article, ...

Moving forward

Learning how to do all of this at once is too hard. Let’s start with the things that are easy to learn (like making a table), and move towards the more complicated stuff (like updating data).

By the time we get to the complicated stuff, you’ll have so much background that it won’t be so difficult.

Summary

We looked at two examples of Web applications: ecommerce and CMS. For each one, there is:

  • A database
  • CRUD operations (create, read, update, and delete)
  • User roles

To create an application, you:

  • Lists the tasks the system should support.
  • Figure out what data is needed to support the tasks.
  • Create the database.
  • Write PHP to let people keep the data up-to-date.
  • Write PHP to let people see the data.

What now?

Let’s dig down a little, and see how a PHP page and a database management system (DBMS) work together in a Web application.

How PHP and DBMS work together

Where are we?

You’ve seen an overview of PHP database applications that run two small businesses. We’ll work slowly towards being able to write applications like that.

Let’s see how PHP programs and MySQL work together. Not in detail yet. That comes later.

This lesson’s goals

In this lesson, you will learn:

  • There is a database client and a database server (a DBMS).
  • The client sends SQL statements to the server. The server returns status codes (like OK) and, sometimes, some data.
  • You write PHP programs that create SQL statements, and send them to the server. Each one of these programs is a database client.

Database servers

Remember that a server is software (or an entire computer) that does something for other software (or other computers).

An email server helps email clients. They exchange messages using protocols. Recall that a protocol is a language, a set of standards for things like saying hello and saying goodbye. SMTP is an email protocol.

Email server and client

Figure 1. Email server and client

A Web server helps Web clients (like browsers). They exchange messages in the protocol HTTP.

Webserver and client

Figure 2. Web server and client

A database server works with database clients. The client sends requests in the language SQL. The server responds with data and status messages.

DB server and client

Figure 3. DB server and client

Figure 3 is a little different from the others. There’s a client and a server, and they communicate. But often the client and server software are on the same machine. They don’t use the Internet to communicate. Instead, they use something like a socket file to exchange messages. A socket file is a way of sending messages between software running independently on the same computer.

Another difference is the type of client. We will be writing our own clients, in PHP. In the DogRock CMS, each PHP program like add-article.php and delete-article.php is a client. It will generate SQL, and send it to the server.

DB server software is usually called a database management system (DBMS). An individual data set is a database.

Interaction pattern

The pattern for interaction is:

  • Client sends an SQL statement.
  • Server returns a status code, and maybe some data.

Suppose you want your PHP page to add data to a database.

Adding data

Figure 4. Adding new data

Your page sends an SQL statement that includes the data to add. The statement would start with the word INSERT. The DBMS executes the SQL, and returns a status code, like “OK.”

If you want your PHP page to delete some data, your page would send a DELETE statement.

Deleting data

Figure 5. Deleting data

To fetch some data from the database, send an SQL statement. This one starts with the word SELECT.

Fetching data

Figure 6. Fetching data

The DBMS returns a status code, and some data. Your PHP can then do something with the data, like show it to the user.

MySQL

There are many different DBMS. The one we’ll talk about is MySQL. Why this one? Because:

  • It’s free.
  • It’s available on every Unix shared hosting account you would want to use.
  • It’s used by many open source (free) Web applications.
  • There’s lots of documentation, and many people know how to use it.

MySQL is included with the XAMPP package we talked about earlier. So if you installed XAMPP, you already have MySQL. W00f!

SQL

SQL stands for structured query language. It’s been part of the geek toolkit for about 30 years. Yes, you read that right. SQL has survived for a long time. And it’s not going away anytime soon.

We’ll talk more about SQL throughout this chapter. But let’s start with some examples, so you get the flavor of it.

Remember, don’t get hung up on the details. I just want you to understand the idea of “send SQL, get status and data.”

Suppose we have some dog data. For each dog, we know its name, breed, and weight. Like this:

Dog data

Figure 7. Dog data

This data is sitting on the DB server.

We could send this SQL query to the server:

select name, weight from dogs;

This means “fetch the name and weight of every dog.” Here’s what the DBMS would return:

Two columns

Figure 8. Two columns

Renata
Renata

Earlier you wrote about SELECT, and now about select. Which is right?

Kieran
Kieran

Both are. MySQL doesn’t care whether keywords like select and insert are in upper- or lowercase.

It also doesn’t care whether SQL statements have semicolons(;) at the end. Strictly, they should be there, but you can leave them off if you like.

Here’s another query:

select name from dogs where weight > 50;

This says “Give me the names of the dogs whose weight is more than 50.”

The DBMS would return:

Heavy dogs

Figure 9. Heavy dogs

This shows that a DBMS can filter data, sending back only what the client asks for. In our case, the client will be a PHP program.

Another example:

select name, weight from dogs order by weight;

This gives:

Sorted dogs

Figure 10. Sorted dogs

The DBMS sorts the data before returning it.

Another example:

insert into dogs (name, breed, weight) values ('Brian', 'Lab', 51);

This adds a new row to the dogs table:

New dog

Figure 11. New dog

This shows that SQL statements can change a database, as well as fetch data from it.

One more example:

delete from dogs where weight < 20;

This would remove all of the light dogs. You would get:

No light dogs

Figure 12. No light dogs

PHP creates SQL statements

PHP pages send SQL statements to DBMS. So, to mess with data, you write PHP that writes SQL statements. Then you send the SQL to the DBMS.

For example:

$query = "insert into dogs (name, breed, weight) 
    values ('Brian', 'Lab', 51)";
$db->query($query);

Figure 13. Adding a dog

The first line puts the SQL statement into a variable. The second line sends it to the DBMS.

Here’s another one:

$query = "delete from dogs where weight < 20";
$db->query($query);

Figure 14. Removing light dogs

The first line creates the SQL, and the second one executes it.

What happens when you get a bunch of data back from a database? Remember this query:

select name from dogs where weight > 50;

This gives:

Heavy dogs

Figure 9 (again). Heavy dogs

You write PHP to handle each row. The general pattern is:

  • Make an SQL query.
  • Send it to the DBMS.
  • For each row:
    • Get the row data.
    • Do something with it.

Here’s an example. Don’t worry about all of the details; just look for the general pattern. The comments explain what each line does.

//Run the SQL query.
$query = 'select name from dogs where weight > 50';
$record_set = $db->query($query);
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for a row.
  $name = $row['name'];
  //Output
  print "<p>Dog name: $name</p>";
}

Figure 13. Showing heavy dogs

Lines 2 and 3 run the query. The DBMS sends back some data, in a table (see Figure 9). The table has one column, and a bunch of rows. Each row is also called a record. The bunch of rows the DBMS returns is called a record set.

Lines 5 to 10 form a loop. The loop runs once for each row in the record set. (Don’t worry about how it works; just know that each record is passed through lines 6 to 9.)

Line 7 gets some data from a record, and puts it into a variable. Line 9 shows the data.

CC
CC

I’m getting worried here. It’s looking awfully complex.

Kieran
Kieran

I don’t blame you. It does get messy. It’s important to take baby steps. Just one little thing at a time.

That’s what we’ll do. But it won’t work if you skip the exercises. The more difficult the material, the more important the hands-on stuff is.

Summary

  • There is a database client and a database server (a DBMS).
  • The client sends SQL statements to the server. The server returns status codes (like OK) and, sometimes, some data.
  • You write PHP programs that create SQL statements, and send them to the server. Each one of these programs is a database client.

What now?

Let’s look at a tool that can make your database work easier.

Tools for developers

Where are we?

You’ve seen that clients send SQL statements to DBMS. There are tools that write SQL statements for you, saving you some work. Let’s talk about phpMyAdmin, the most widely used tool for managing MySQL databases.

This lesson’s goals

You’ll learn:

  • To do database work, you need to send SQL statements to a DBMS. But some SQL statements are long and easy to mess up.
  • phpMyAdmin is a PHP Web application that writes SQL for you. phpMyAdmin is part of XAMPP.
  • You’ll see how to start MySQL and phpMyAdmin on your computer. You’ll see how to use phpMyAdmin on your hosting account.
  • You can create databases on your local computer, and use phpMyAdmin to export your work to your hosting account.

phpMyAdmin

Almost everything you do with MySQL is done by sending SQL commands to the server:

Everything is SQL

Figure 1. Everything is SQL

When you make Web applications, there’s some work you need to do to get things ready for your PHP pages. You create databases, make tables, add fields (columns), and other things.

This is done with SQL. But doing it all manually can be a pain. You have to type out long commands like this:

CREATE TABLE products (
   product_id int(11) NOT NULL AUTO_INCREMENT,
   'name' char(50) NOT NULL,
   description text NOT NULL,
   image_file_name char(50) NOT NULL,
   price decimal(10,2) NOT NULL,
   PRIMARY KEY (product_id)
);

And you have to get everything just right. You might make a mistake, like this:

CREATE TABLE products (
   product_id int(11) NOT NULL AUTO_INCREMENT,
   'name' char(50) NOT NULL,
   description text NOT NULL,
   image_file_name char(50) NOT NULL,
   price decimal(10,2) NOT NULL,
   PRIMARY KEY (prodct_id)
);

It wouldn’t work, but the problem is hard to spot. Grrrr!

There are tools that make it easier.

The one that most people use is phpMyAdmin. It’s actually just a bunch of PHP pages.

Most Web hosting companies install phpMyAdmin for you. It’s also part of the XAMPP package. If you installed XAMPP, you already have phpMyAdmin installed.

So you will have two copies of phpMyAdmin:

  • One on your local computer.
  • One on your hosting account.

You also have two different MySQL instances:

  • One running on your local machine.
  • One running on your hosting account.

Starting MySQL and phpMyAdmin on your local machine

Open the XAMPP control panel. Start both Apache and MySQL:

XAMPP control panel

Figure 2. XAMPP control panel

phpMyAdmin is a bunch of Web pages. It needs Apache (or another Web server) to run.

Start your browser, and go to http://localhost/. You’ll see this on the left:

XAMPP home page

Figure 3. XAMPP home page

Click the phpMyAdmin link to start it.

Starting phpMyAdmin on your Hostgator account

Log in to your Hostgator control panel. Scroll down to the Databases section:

Starting phpMyAdmin on Hostgator

Figure 4. Starting phpMyAdmin on Hostgator

If you aren’t using Hostgator, you’ll start phpMyAdmin in a different way. Check you hosting company’s help pages.

CC
CC

How do I start MySQL on my hosting account?

Kieran
Kieran

You don’t. MySQL is always running, just like Apache is always running.

Exercise: Starting phpMyAdmin

Start MySQL on your computer. Then open a browser, and run phpMyAmdin.

Run phpMyAdmin on your hosting account.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Different versions

The phpMyAdmin installation on your local computer might be a different version from the one on your hosting account. As I write this, I’m running version 2.10.3 on my local machine, and version 2.11.9.5 on my hosting account.

Even the versions of MySQL are different. I’m running MySQL version 5.0.45 on my local machine, and version 5.1.30 on my hosting account.

That’s OK. We’re only covering DB basics. At the level we’re working, having different versions won’t matter. The things we’re looking at have been more-or-less the same for twenty years.

And they’re still current. The core of DB tech has been unusually stable.

From local to remote

In earlier chapters, I recommended that you install XAMPP on your own computer, and write PHP programs there. When everything is working, then you upload the finished programs to your hosting account.

Adding databases complicates things a little, but not much. You can create databases on your local machine, and then move them to your hosting account when you’re ready.

phpMyAdmin can both import and export databases, tables, and other objects. So you can just copy most of your work from your local computer to your hosting account.

I’ll go over an example when we get to creating a table.

Other tools

There are many other tools that can help you work with databases. Earlier, I recommended Netbeans for writing Web pages. Netbeans has an integrated DB tool that works nicely with MySQL.

However, to keep things simple, we’ll only use phpMyAdmin. It’s already installed on both your development (local) and production (hosting account) servers. The same tool works no matter where you are.

Going deeper

You can read a review of Netbean’s database explorer.

Summary

  • To do database work, you need to send SQL statements to a DBMS. But some SQL statements are long and easy to mess up.
  • phpMyAdmin is a PHP Web application that writes SQL for you. phpMyAdmin is part of XAMPP.
  • We saw how to start MySQL and phpMyAdmin on your computer. We saw how to use phpMyAdmin on your hosting account.
  • You can create databases on your local computer, and use phpMyAdmin to export your work to your hosting account.

What now?

You’re starting to see how PHP programs can work with databases. Let’s look at DogToys and DogRock again. We’ll go through all of their screens, and see how they fit together.

Sample: DogToys

Where are we?

You’ve seen how PHP pages can send SQL statements to MySQL. We’re going to see how you use this ability to write Web applications.

But first, let’s look at two simple applications. Understanding what they do will help you understand why database stuff is done the way it is.

This lesson’s goals

Recall that DogToys has an online product catalog, with things like squeaky balls, and chew ropes. In this lesson, we’ll look at what the site does. Later, you’ll see how it can be built.

By the end of this lesson, you should:

  • Know what the business goals of the site are.
  • See how the site’s design achieves those goals.

Web site goals

The DogToys site has three main goals:

  • Customers can see product data.
  • Employees can change product data, quickly and easily.
  • Webers can update the Web site, quickly and easily.

Let’s talk about each one.

Customers can see product data

Customers want to be able to see pictures, descriptions, and prices of products. The product list should be easy to get to, from anywhere in the site.

Customers should be able to sort product data the way they want. We’ll let them sort the data by price (low to high, and high to low), and by product name.

Employees can change product data

We want DogToys employees to be able to change product data. The employees who change product data will be from the marketing department. They will not have strong technical skills. They don’t know any HTML.

We’ll create an administration section of the Web site. Employees will be able to fill in forms to change product data.

Webers can update the Web site

We will want to change the site. Maybe change the color scheme, add buttons to the nav bar, change the page footer, etc. Webers should be able to make these changes quickly and easily.

We’ll make a template-based site.

Try it

You can try the site. The admin parts of the site don’t actually update the database.

You can download the code.

For the customer

Time for some screens. Here’s the home page:

Home page

Figure 1. Home page

Notice the nav bar on the left. This will be the same on every page. Customers can click on the Products button to see the products catalog. This meets the goal of having the products list available from every page on the site. It’s always just one click away.

Here’s the product catalog:

Product catalog

Figure 2. Product catalog

Each product has a name, picture, description and price.

The user can sort the data. Clicking on the Name link sorts the data by name. Clicking on the up and down arrows next to the price sorts by price.

You can try it.

Administration

The administration section of the site lets employees change product data. The administration section is just a bunch of Web pages.

There is no clickable link from the main part of the DogToys site to the administration section. To get to it, you need to know the URL. Add /admin to the home page’s URL to get to the main administration menu.

CC
CC

That isn’t very secure.

Kieran
Kieran

You’re right, it isn’t. Later in the book, you’ll learn how to add user names and passwords to sites.

Main administration screen

Here’s the main administration screen:

Main administration screen

Figure 3. Main administration screen

There’s a link users can click to add a new product.

All the products are listed. It looks like the product catalog, but each product has an Edit and Delete link.

Adding product data

Here’s the form for adding product data:

Adding product data

Figure 4. Adding product data

There are four fields:

  • Product name
  • Description
  • Image file name
  • Price

Most are familiar, but what about the image file name? What’s that about?

We want to show a picture of each product. We’ll take a photo of each one, with a digital camera. We’ll store the image files on the Web site. Let’s put all the photos in a directory called product-images.

How to show the photo on a Web page? With the <img> tag, like this:

<img src="product-images/ Image file name " alt=" Product name ">

Suppose we take a photo of the ball. We name the file with the photo ball.jpg, and put the file into the product-images directory. To show it on the product catalog page:

<img src="product-images/ball.jpg" alt="Squeaky ball">

So the user would type “ball.jpg” into the image file name field in Figure 4.

Image file name is just a regular text field. It’s used to make the <img> tag. The tag itself is just text. But when the browser renders the tag, it will download the image file.

Changing product data

The user clicks a product’s Edit link to edit a product record:

Edit link

Figure 5. Edit link

This goes to a page like this:

Edit form

Figure 6. Edit form

It looks like the new product form in Figure 4, but the fields already have data in them.

Deleting product data

When on the main administration page, the user can click the Delete link to remove product data:

Delete link

Figure 7. Delete link

This shows a confirmation page, to make sure the user really wants to delete the product record:

Confirm delete

Figure 8. Confirm delete

The user has to click the Confirm button to actually delete the record.

Exercise: DogToys tasks

Using a site is a good way to get to know it. But you should use it in the same way that other people would use it.

Pretend you’re a DogToys customer, who wants to buy the most expensive toy. Start at the site’s home page. Write down the steps you would take to complete the task.

Now pretend you are a DogToys employee. DogToys is having a sale on squeaky balls. You want to reduce the price of squeaky balls by 50 cents. Start at the site’s home page. Write down the steps you would take to complete the task.

Put your answers below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

We’ve talked about two of the site’s goals so far:

  • Customers can see product data.
  • DogToys employees can change product data, quickly and easily.

Now for the last one.

Changing the Web site

We want to make it easy for Webers to change the site. We’ll use the PHP template approach we talked about before.

So we’ll be using PHP for two things:

  • Manage product data.
  • Assemble pages from template components.

Summary

We looked at the DogToys Web site. The site lets:

  • Customer look at product data.
  • Employees easily change product data.
  • Webers easily alter the Web site.

What now?

Let’s tour the DogRock site.

Sample: DogRock

Where are we?

Let’s look at another application. Understanding it will help you understand why database stuff is done the way it is.

This lesson’s goals

Recall that DogRock’s site shows articles about dog rock music. It’s a content management system (CMS).

In this lesson, we’ll look at what the site does. Later, you’ll see how it can be built.

By the end of this lesson, you should:

  • Know the business goals of the site.
  • See how the site achieves those goals.

Web site goals

The DogRock site has three main goals:

  • Users can see a list of articles, and read each one.
  • DogRock’s writers can add and change articles, quickly and easily.
  • DogRock’s Webers can update the Web site, quickly and easily.

You can try the site. The admin pages don’t actually change the database.

You can download a zip file of all of the sites files.

Let’s talk about each goal.

Users can a list of articles, and read each one

Users want to get to new articles quickly. They also want to be able to access a list of older articles.

Each article has a title, a body, an author, and a publication date. Users want to be able to see a list of articles sorted by publication date, so they can see recent stuff. They also want to be able to sort by title and author.

Writers can add and change articles

DogRock’s writers are not computer experts. They can handle some basic HTML tags, like <p>, but that’s about it. They don’t know how to create entire Web pages.

We’ll create an administration section of the Web site. Writers will use forms to add and change articles. We’ll let them delete articles as well.

Webers can update the Web site

We will want to change the site. Maybe change the color scheme, add buttons to the nav bar, change the page footer, etc. Webers should be able to make these changes quickly and easily.

We’ll make a template-based site.

For the users

Time for some screens. Here’s the home page:

Home page

Figure 1. Home page

The latest three articles are listed on the home page, from most recent to least recent. You can try it.

Notice the nav bar on the left. This will be the same on every page. Users can click on the Articles button to see a complete article list.

Here’s the article list:

Article list

Figure 2. Article list

Clicking on the column names (title, date, and author) sorts the list. Clicking on the title of an article shows the article.

You can try it.

Administration

The administration section of the site lets writers add, edit, and remove articles. The administration section is just a bunch of Web pages.

There is no clickable link from the main part of the DogRock site to the administration section. To get to it, you need to know the URL. Add /admin to the home page’s URL to get to the main administration menu.

Main administration screen

Here’s the main administration screen:

Main administration screen

Figure 3. Main administration screen

There’s a link users can click to add a new article.

All the articles are listed. It looks like the article list, but each article has an Edit and Delete link.

Adding an article

Here’s the form for adding an article:

Adding an article

Figure 4. Adding an article

There are three fields:

  • Title
  • Body
  • Author name

Some PHP sets the publication date when the article is saved.

Changing an article

The user clicks an article’s Edit link to edit an article record:

Edit link

Figure 5. Edit link

This goes to a page like the add article form, but with the fields filled in.

Deleting an article

When on the main administration page, writers can click the Delete link to remove an article:

Delete link

Figure 6. Delete link

This shows a confirmation page, to make sure the user really wants to delete the article record:

Confirm delete

Figure 7. Confirm delete

The user has to click the Confirm button to actually delete the record.

Exercise: DogRock tasks

Using a site is a good way to get to know it. But you should use it in the same way that other people would use it.

Pretend you’re a DogRock reader, who wants to read the latest article. Start at the site’s home page. Write down the steps you would take to complete the task.

Now pretend you’re a reader who wants to see what Bounder has written. Start at the site’s home page. Write down the steps you would take to complete the task.

Now pretend you are a DogRock writer. You want to fix a spelling error in the article “A new howl on the prowl.” Start at the site’s home page. Write down the steps you would take to complete the task.

Put your answers below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Changing the Web site

We want to make it easy for Webers to change the site. We’ll use the PHP template approach we talked about before.

So we’ll be using PHP for two things:

  • Manage article data.
  • Assemble pages from template components.

Summary

The DogRock site lets:

  • Users see a list of articles, and read each one.
  • Writers add and change articles, quickly and easily.
  • Webers update the Web site, quickly and easily.

What now?

Each application – DogToys and DogRock – uses a database. Let’s see how you create a database, and then add a table to it.

Creating a database

Where are we?

You’ve seen how PHP programs can send SQL statements to MySQL. You’ve seen two sample applications. Time to get your hands dirty.

This lesson’s goals

By the end of this lesson, you should:

  • Be able to create a MySQL database on your local machine.
  • Be able to create a MySQL user on your local machine.
  • Be able to create a MySQL database on your hosting account.
  • Be able to create a MySQL user on your hosting account.
  • Be able to write PHP test programs to connect to the databases.

Creating a local database with phpMyAdmin

Start phpMyAdmin on your computer (see this explanation).

You’ll see the phpMyAdmin home page. If you get lost, click the home button to get back to it:

Go to the phpMyAdmin home page

Figure 1. Go to the phpMyAdmin home page

Type in the name of the new database on the phpMyAdmin home page, and click the Create button:

Creating a database

Figure 2. Creating a database

phpMyAdmin will make the SQL statement that creates a database, and send it to MySQL. You’ll see something like this:

Feedback from creating a database

Figure 3. Feedback from creating a database

That’s it! You’ve created a database.

There are lots of options you can choose. As usual, CoreDogs only talks about the really important stuff.

Exercise: Creating databases

Create two databases on your local machine:

  • dogtoys
  • dogrock

You’ll use them to install your own copies of the DogToys and DogRock applications.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Creating a local MySQL user

MySQL uses its own security system, with user names and passwords. When you create a database, the next thing is:

  • Create a new MySQL user.
  • Give the new user access to the database.

You don’t have to create a new user for each database, but it’s common practice.

Remember this:

MySQL user accounts have nothing to do with other user accounts!

MySQL accounts are used for MySQL access, and nothing else. Not FTP, or email, or Web access. Just MySQL.

Creating a local MySQL user

How you do it is a little different on different versions of phpMyAdmin. But the concepts are the same.

Go to the phpMyAdmin home page:

Go to the phpMyAdmin home page

Figure 1 (again). Go to the phpMyAdmin home page

Depending on your version of phpMyAdmin, either click the Privileges link, or the Privileges tab:

Privileges link

Or

Privileges tab

Figure 4. Privileges link or tab

Click the add user link:

Add user link

Figure 5. Add user link

Fill in the user’s name, host, and password and click the Go button:

Add user

Add user

Figure 6. Add user

Don’t set any global privileges for the user.

I often make the user name the same as the database name. You can type in your own password, or have MySQL generate one for you. If you do that, make sure you copy and paste the password to a file on your computer. You don’t want to lose it!

What’s that “host” thing? This is which computer(s) the user is allowed to connect from. The right value is almost always localhost, because your Web server and your DB server are running on the same machine. Type in localhost unless your hosting company tells you otherwise.

I’ve only ever run into one hosting company that didn’t use localhost. That was Dreamhost.

Giving a user access to a database

Once you have created the user, you can give the user access to a database. Scroll down to the “Database-specific privileges” section, and fill it in:

Add database privileges

Figure 7. Add database privileges

Enter the database you want to give the user access to.

<Begin alternate route>

You can also get to Figure 7 from the home page. Go to the home page:

Go to the phpMyAdmin home page

Figure 1 (again). Go to the phpMyAdmin home page

Then go to the privileges screen:

Privileges link

Or

Privileges tab

Figure 4 (again). Privileges link or tab

Click the edit user icon:

Edit user privileges

Figure 8. Edit user privileges

</End alternate route>

Now that you’ve give the user access to the database, you tell MySQL what that user is allowed to do:

Select privileges

Go button

Figure 9. Select privileges

Check the privileges in the figure.

You’re done.

Exercise: Creating database users

Create two database users on your local machine, one for each of the databases you created earlier. You can name them anything you like. I usually give them the same names as the databases:

  • dogtoys
  • dogrock

You’ll use them for your own copies of the DogToys and DogRock applications.

Choose your own passwords, and write them down!

Give the users access to the server at localhost.

Give the users access to their respective databases. Give them the following privileges:

  • SELECT
  • INSERT
  • UPDATE
  • DELETE
  • CREATE
  • ALTER
  • INDEX
  • DROP

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Local testing

But did it work? When you create a new database and user, you should run some PHP to make sure it worked.

Here’s a test page.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
  </head>
  <body>
    <h1>PHP-MySQL connection test</h1>
    <?php
    //DB connection data.
    //Probably leave $host alone.
    $host = 'localhost';
    //Set $db to the name of your database.
    $db = 'dogrock';
    //Set user_name to the name of the MySQL user you made.
    $user_name = 'dogrock';
    //Set $password to the user's password.
    $password = '[Secret password here]';
    ?>
    <p>Trying to connect to MySQL.</p>
    <ul>
      <li>Host name: <?php print $host; ?></li>
      <li>Database name: <?php print $db; ?></li>
      <li>User name: <?php print $user_name; ?></li>
      <li>User password: (Secret)</li>
   </ul>
    <?php
    //Connect to the MySQL server.
    $db = new mysqli($host, $user_name, $password, $db);
    //Did it work?
    if ( mysqli_connect_error() ) {
      print '<p>Error! Could not connect to the database. ';
      print 'Error message: '.mysqli_connect_error().'</p>';
    }
    else {
      print '<p>W00f! It worked!</p>';
    }
    ?>
  </body>
</html>

Figure 10. Test a connection

Line 29 makes the connection. It needs four pieces of data:

  • Host name, e.g., localhost
  • User name, e.g., dogrock
  • User password
  • Database name, e.g., dogrock

You type in those values in lines 11 to 18.

Line 31 tests whether the connection worked or not. If not, it shows an error message. If there is no error, line 36 will tell you about it.

Grab this code, and put it somewhere on your local computer.

Here’s what happens when everything works:

Connection OK

Figure 11. Connection OK

Here’s what happens if MySQL is not running:

MySQL not running

Figure 12. MySQL not running

The error message is “Can’t connect to MySQL server.”

Here’s what happens if the password is wrong:

Wrong password

Figure 13. Wrong password

If the database name is wrong? Here it is:

Wrong database name

Figure 14. Wrong database name

Exercise: Testing your local database connections

Write PHP programs to test your two local databases:

  • dogtoys
  • dogrock

Copy the test code, and adjust the connection parameters.

If there’s a problem:

  • Use phpMyAdmin to confirm that the databases exist.
  • Use phpMyAdmin to make sure that the users exist, and they have privileges to access localhost and the databases.
  • Check the connection parameters in your PHP code.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Now you know how to:

  • Create a database on your computer.
  • Create a user with access to the database.
  • Test the connection.

What about your hosting account?

Creating a remote database with phpMyAdmin

When you create a database on your hosting account, you do things a little differently. The exact details depend on your hosting company. I’ll run through the procedures for Hostgator.

Log in to your control panel. Find the database icons:

Control panel database icons

Figure 14. Control panel database icons

Click the “MySQL databases” icon:

Control panel database icons

Figure 15. Control panel database icons

Type in the name of the new database and click the button:

Create database

Figure 16. Create database

You’ll get some feedback, like this:

Create database feedback

Figure 17. Create database feedback

The next thing is important!

The real name of the database might not be the name you typed!

Hostgator prepends (adds in front) your user name to the database name, and chops off extra characters, if there are any. So it your Hostgator user name is woof121 and you type dogrock for your database name, the real name of your database might be woof121_dogrock.

You would use woof121_dogrock as the database name in your PHP programs.

Renata
Renata

Why does Hostgator do this?

Kieran
Kieran

Remember that many sites run on the same server. That’s what “shared hosting” means. There’s one copy of MySQL that everyone shares.

Several people might create a database named books. How is the server to tell them apart?

Renata
Renata

Oh, I see. Hostgator changes the names, so they are all different.

Kieran
Kieran

Right! So if the user lab200 typed in books as the name of a database, the actual name of the database would be lab200_books. If the user pug77 typed in books as the name of a database, the actual name of the database would be pug77_books.

Copy and paste the real name of the database into your code.

Creating a remote MySQL user

Go back to the MySQL database page. Remember, to get there from the control panel:

Control panel database icons

Figure 15 (again). Control panel database icons

To create a user, scroll down to the Add User form, and fill it in:

Create a user

Figure 18. Create a user

You’ll get feedback like this:

Create a user - feedback

Figure 19. Create a user – feedback

But this is misleading. The control panel changes the user name the same way it changes the database name. So if lab200 creates the user dogrock, the user is really lab200_dogrock.

Hostgator’s control panel won’t change the password. If you type a password of secret_ThinG_12, then that will be the password you use in your PHP.

Now you need to give the user access to the database.

On the MySQL Databases page, you’ll see something like this:

Giving a user access to a database

Figure 20. Giving a user access to a database

Select the user and the database, and click the Add button.

Then you select which privileges the user has:

Setting privileges

Figure 21. Setting privileges

Use the ones shown in the figure.

You’ll get feedback like this:

Setting privileges - feedback

Figure 22. Setting privileges – feedback

Hooray! You’ve:

  • Created a database on your hosting account.
  • Created a MySQL user on your hosting account.
  • Given the user access to the database.

Remember: use the real names of the database and the user. You can see them on the control panel’s MySQL Databases page.

Remote testing

How to test that everything worked? Same as before. Put a file with this code on your hosting account:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
  </head>
  <body>
    <h1>PHP-MySQL connection test</h1>
    <?php
    //DB connection data.
    //Probably leave $host alone.
    $host = 'localhost';
    //Set $db to the name of your database.
    $db = 'dogrock';
    //Set user_name to the name of the MySQL user you made.
    $user_name = 'dogrock';
    //Set $password to the user's password.
    $password = '[Secret password here]';
    ?>
    <p>Trying to connect to MySQL.</p>
    <ul>
      <li>Host name: <?php print $host; ?></li>
      <li>Database name: <?php print $db; ?></li>
      <li>User name: <?php print $user_name; ?></li>
      <li>User password: (Secret)</li>
   </ul>
    <?php
    //Connect to the MySQL server.
    $db = new mysqli($host, $user_name, $password, $db);
    //Did it work?
    if ( mysqli_connect_error() ) {
      print '<p>Error! Could not connect to the database. ';
      print 'Error message: '.mysqli_connect_error().'</p>';
    }
    else {
      print '<p>W00f! It worked!</p>';
    }
    ?>
  </body>
</html>

Figure 10 (again). Test a connection

Replace the database name, user name, and password. Use the real names, like lab200_dogrock. Load the page in your browser, and see what you get.

Exercise: Create and test remote databases

Create two databases on your hosting account:

  • dogtoys
  • dogrock

Your hosting account’s control panel might adjust the names of the databases. Make sure you know the real names.

Create users for each database. Give them access to localhost. Write down the users’ passwords!

A few hosting companies don’t use localhost.

Give the users the following privileges on their respective databases:

  • SELECT
  • INSERT
  • UPDATE
  • DELETE
  • INDEX
  • CREATE
  • ALTER
  • DROP

Copy the test code, and adjust the connection parameters. Upload to your server, and run your test programs.

Put the URLs of your test pages below.

If there’s a problem:

  • Confirm that the databases exist. Make sure you know what their real names are.
  • Confirm that the database users exist. Make sure you know what their real names are.
  • Confirm that the users have access to locahost.
  • Check that the users can access their respective databases. Check their privileges.
  • Check the connection parameters in your PHP code.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

In this lesson, you learned how to:

  • Create MySQL databases and users on your local machine.
  • Create MySQL databases and users on your hosting account.
  • Write PHP test programs to connect to the databases.

What now?

Now you have some databases. Time to add a table to each one.

Creating a table

Where are we?

You know how to create a database. Now learn how to add a table to a database.

This lesson’s goals

Learn:

  • All data is in tables. Each row is about a single thing, like a dog. Each column is an attribute, like a name or a weight. Each row has the same columns.
  • Each field has a data type. MySQL has lots of data types. We talked about INT, DECIMAL, CHAR, TEXT, and DATE.
  • Tables have primary keys. A primary key is a field whose values are different for every row.
  • Usually you create an INT field to be a primary key. Make it unsigned and auto_increment. MySQL will fill in a unique value.
  • Learn how to create a table with phpMyAdmin, and add data to it.
  • Learn how to export a table from one database (e.g., one on your local machine), and import it into another (e.g., one on your hosting account).

All data is in tables

All MySQL data is in tables. Here’s an example:

dogs table

Figure 1. dogs table

The data is in rows and columns.

Each row is data about a single dog. So all the data on the first row is about Francis. A row is also called a record.

Each column is a characteristic of a dog, like the dog’s name, breed, or weight. Columns are also called fields. Every row has the same fields.

Here’s the articles table from the DogRock content management system (CMS):

Articles table

Figure 2. articles table

Each row is data about one article. Each column in an attribute of articles, like the title.

Here’s the products table from the DogToys product catalog:

products table

Figure 3. products table

Each row is data about one product. Each column in an attribute of products, like the price.

Exercise: Fields for jokes

Suppose you want a table to store your favorite jokes. Each row of the table would have a joke.

What fields would you put in the table?

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Data types

Each column contains a specific type of data. Here are the data types for the dogs fields.

dogs data types

Figure 4. dogs data types

When you create a table, you tell MySQL the data type of each column. Don’t worry about how you do that just yet; we’ll talk about it soon. For now, let’s focus on the data types.

There are many different data types. The core types are in three categories:

  • Characters.
  • Numbers.
  • Date/time.

Character types

Characters are text, as in “I like the number 96.” Character fields are also called string fields.

You can tell MySQL how long character fields should be. For example…

CHAR(10)

... tells MySQL that a field can have up to 10 characters. Try to put more in the field, and the extra characters will be cut off.

Another character data type is:

TEXT

A TEXT field can have up to about 65,000 characters.

Number types

We’ll only talk about two number types:

  • Integers (whole numbers)
  • Decimals (numbers with a fixed number of decimal places)

Integer fields can contain whole numbers only, like 7. Try to put 7.32 into an integer, and the .32 will be cut off.

Make integer fields with the INT data type.

Decimals are declared like this:

DECIMAL(10,2)

This is a number that is 10 digits long, with 2 decimal places.

Date/time

MySQL has several date/time types. You can store times down to a fraction of a second, if you want.

We’ll just use the simplest date/time type: DATE. It stores just a date, and not a time.

But how to format a date? In the US, it’s month/day/year, so June 3, 2013 would be 6/3/13. In Australia, you would use day/month/year, as in 3/6/13.

Just as there’s a standard for HTTP, and another one for HTML, and another one for CSS, there’s an international standard for dates. It’s YYYY-MM-DD. So June 3, 2013 would be 2013-06-03.

Most Webers use the standard format when storing dates in a database. That’s what we’ll do. But when we show the dates to people, we’ll use the US format. You can adjust the format, if you like.

Other types

There are many other data types. Other types of strings, other types of numbers, and things that aren’t either. For example, coordinates of a place on the earth, used in mapping programs.

We’ll stick with CHAR, TEXT, INT, DECIMAL, and DATE for now. I’ll explain others if we need them.

Primary key

Here’s the products table again:

products table

Figure 3 (again). products table

Most tables have a column that is the table’s primary key. This uniquely identifies each row in the table.

For products, only one product will have a product_id of 1. Only one product will have a product_id of 2. There will never be any duplicates.

Could the name field be a primary key? No. There might be two products from different manufacturers that have the same name. Maybe two manufacturers make a product they call “Giant chew rope.” They will have different product_ids, so the database can tell them apart.

Here’s the articles table again:

Articles table

Figure 2 (again). articles table

Two articles could have the same name. For example, Wendy could write an article called “Old Time Howl.” In three years, Bounder could write an article called “Old Time Howl.”

The field article_id takes care of it. Each row has a different value for article_id. It’s the table’s primary key.

Here’s the dogs table:

dogs table

Figure 1 (again). dogs table

What is the primary key? Is it name? That’s the best field we have, but it isn’t very good. Two dogs could have the same name. There might be two Ralphs, for example.

This table isn’t designed well. It would be better if it had a primary key:

dogs table with primary key

Figure 5. dogs table with primary key

Integer primary key

It’s common practice to add an integer field (whole number, remember) to a table, and make it the primary key. That’s what I did with the dogs table.

The first row has a value of 1 for this field. The next row has 2, and so on. Actually, it doesn’t matter what the values are, as long as each one is different. I could make the dog_id of the first row 432, the second row 89, the third row 299, etc. But making them 1, 2, 3, etc., is usual.

When we add a new record to the table, we want dog_id to have a new unique value. We can tell MySQL to do this for us. When we create the table, we can mark an integer primary key field as auto_increment.

When our PHP program adds a new record to dogs, we send the values for each field to MySQL. Like this:

insert into dogs (name, breed, weight) values ('Jamie', 'Scottie', 9);

Notice that we didn’t give a value for dog_id. Since dog_id is an auto_increment field, MySQL will set the value for us. It will take the next value in sequence. So if the last record we added has a value of 3,409, then MySQL will fill in the value 3,410.

Exercise: Data types for jokes fields

You listed the fields for a jokes table. What is the data type of each one? Choose from INT, DECIMAL, CHAR, TEXT, and DATE.

Remember to include a primary key.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Creating a table with phpMyAdmin

Let’s see how you actually add a table to the a database.

I added a humans table to the dog database. It has fields for:

  • Primary key.
  • Name, e.g., Sarah.
  • Weight in pounds.

First, I opened the database in phpMyAdmin. Do this by selecting the database in the drop-down:

Open a database

Figure 5. Open a database

Then I typed the name of the new table and the number of fields into the Create table form:

Create a new table

Figure 6. Create a new table

Then I clicked the Go button.

Here are the settings for the primary key, human_id:

The human_id field

Figure 7. The human_id field

The data type is INT. I didn’t need to give a length; INT has a standard length, and MySQL knows what it is.

Primary keys are not negative; they could be, but it would be strange. So I made the field UNSIGNED. It’s also set to AUTO_INCREMENT. You should make your primary keys INT, UNSIGNED, and AUTO_INCREMENT.

I marked as human_id the table’s primary key, using the radio button next to the picture of a table with a key on it. Don’t forget to mark your primary keys!

Here are the settings for name:

The name field

Figure 8. The name field

It’s set to CHAR and given a length of 20. You always need to give a length to CHAR fields.

Here are the settings for weight:

The weight field

Figure 9. The weight field

It’s set to INT.

I clicked the Save button to make the table. phpMyAdmin created an SQL statement, and told MySQL to run it. Here’s the feedback I got.

Create table feedback

Figure 10. Create table feedback

Adding data with phpMyAdmin

Now I have some tables. How about adding data?

Let’s say I want to add data to the dogs table. First, I open the table.

Opening the dogs table

Figure 11. Opening the dogs table

The I click the Insert tab.

Insert tab

Figure 12. Insert tab

I see a form that lets me add data.

Add record form

Figure 13. Add record form

I fill in the form, but not the auto_increment field!

Adding data

Figure 14. Adding data

dog_id, the primary key, is an auto_increment field. MySQL will fill in that value for me.

I click the Save button, and get this feedback:

Feedback on adding data

Figure 15. Feedback on adding data

You can see the SQL INSERT statement that phpMyAdmin created. You can also see the message “Data truncated for column ‘breed’ at…” Why? Because I declared breed as CHAR(10). The value I typed in – Whale hound – is 11 characters long. So the last character was lost.

Here’s what the table looks like with the new record:

New data

Figure 16. New data

You can see the truncated breed.

You can also see that the dog_id is 6. Remember that I didn’t type in that value. Because it’s an auto_increment field, MySQL filled in the value itself.

Exporting to remote

I created the dogs table on my local PC, and added some data. How do I get it on my Hostgator server? I could retype everything, but there’s an easier way:

  • On localhost: Export the table to a file.
  • On hosting server: Import the file to make a table.

When you export a table in phpMyAdmin (on your local machine), phpMyAdmin recreates the SQL statements that made the table and added the data. Then you can tell phpMyAdmin (the one on your hosting account) to execute those statements.

Exporting

Open the table you want to export:

Open table

Figure 17. Open table

Click the Export tab:

Export tab

Figure 18. Export tab

Make sure SQL is the export format:

Export format

Figure 19. Export format

Check “Save as file”:

Save as file

Figure 20. Save as file

Click the Go button. Your browser will save the file.

Open the file in Notepad++, Netbeans, or some other editor. You’ll see the SQL that phpMyAdmin exported.

Importing

Start phpMyAdmin on your hosting account. If you forget how, here’s a reminder.

Open the database you want to put the table into.

Click the import tab:

Import tab

Figure 21. Import tab

Click the Browse button:

Browse button

Figure 22. Browse button

Select the file to import. Click the Go button. phpMyAdmin will grab the file and run the SQL statements inside it.

Exercise: Installing DogToys and DogRock on your computer

In this exercise, you will get DogToys and DogRock running on your computer.

If you haven’t already, download the zip files for DogToys and DogRock. Extract them into separate directories on your computer. Put them under the document root of your local Web server (probably c:\xampp\htdocs\).

If you haven’t already, create two databases on your computer: dogtoys and dogrock. Create a user for each one, with access to the databases.

Now it’s time to create the tables. Both DogToys and DogRock have an SQL file you can import into phpMyAdmin. Each file will create a table and add some data. For DogToys, the file is dogtoys.sql. For DogRock, it’s dogrock.sql.

Import the files into their respective databases on your local computer.

The last step is to tell DogToys and DogRock what connection data (database, host, and user) to use. Both applications have a file called /library/db-connect.php. Open the one for DogToys. You’ll see something like this:

<?php
//DB connection data.
$host = 'localhost';
$db = 'dogtoys';
$user_name = 'dogtoys';
$password = 'dogtoys';
?>

Figure 1. DogToys database connection parameters

Change these values to match your database. For example, if you gave your dogtoys user a password of playallday, use that.

Do the same for DogRock.

Start Apache and MySQL on your computer. Try the applications.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Export local jokes to remote

You created a jokes table on your local computer. Export it. This will create an SQL file on your computer. Store the file wherever you like.

Now import the file into the jokes database on your hosting account.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • All data is in tables. Each row is about a single thing, like a dog. Each column is an attribute, like a name or a weight. Each row has the same columns.
  • Each field has a data type. MySQL has lots of data types. We talked about INT, DECIMAL, CHAR, TEXT, and DATE.
  • Tables have primary keys. A primary key is a field whose values are different for every row.
  • Usually you create an INT field to be a primary key. Make it unsigned and auto_increment. MySQL will fill in a unique value.
  • You learned how to create a table with phpMyAdmin, and add data to it.
  • You learned how to export a table from one database (e.g., one on your local machine), and import it into another (e.g., one on your hosting account).

What now?

You’ll need a way to apply your database skills. In the next lesson, you’ll set up the Jokes application. You’ll add to it as we work through the rest of the chapter.

Starting the jokes project

Where are we?

You’ll need a way to apply your database skills. In this lesson, you’ll set up the Jokes application. As we work through this chapter, you’ll add pages to Jokes, until it’s complete.

This lesson’s goals

The Jokes application is much like DogToys and DogRock. It’s a template application, so it uses PHP for more than database work. You can try it.

In this lesson, you’ll:

  • Create a database on your computer.
  • Create a database user.
  • Test the connection.
  • Create a table.
  • Download the starting files to your machine.

Create the jokes database

Create a database called jokes on your machine. A brief recap of the steps:

  • Start Apache and MySQL.
  • Open phpMyAdmin.
  • Create the database.

Create a database user

Create a MySQL user for the database. You’ll need this to connect you PHP code to the database.

Test

Run the test program we used earlier. Here is the code again:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
  </head>
  <body>
    <h1>PHP-MySQL connection test</h1>
    <?php
    //DB connection data.
    //Probably leave $host alone.
    $host = 'localhost';
    //Set $db to the name of your database.
    $db = 'jokes';
    //Set user_name to the name of the MySQL user you made.
    $user_name = '[User name here]';
    //Set $password to the user's password.
    $password = '[Secret password here]';
    ?>
    <p>Trying to connect to MySQL.</p>
    <ul>
      <li>Host name: <?php print $host; ?></li>
      <li>Database name: <?php print $db; ?></li>
      <li>User name: <?php print $user_name; ?></li>
      <li>User password: (Secret)</li>
   </ul>
    <?php
    //Connect to the MySQL server.
    $db = new mysqli($host, $user_name, $password, $db);
    //Did it work?
    if ( mysqli_connect_error() ) {
      print '<p>Error! Could not connect to the database. ';
      print 'Error message: '.mysqli_connect_error().'</p>';
    }
    else {
      print '<p>W00f! It worked!</p>';
    }
    ?>
  </body>
</html>

Figure 1. Test a database connection

Change the code on lines 12 to 18 to match your database.

Create the jokes table

Create a jokes table in the jokes database. Here are the fields and types I used in mine:

  • joke_idINT, primary key, auto_increment
  • titleCHAR(30). A few words reminding me what the joke is about.
  • joke_textTEXT. The text of the joke itself.
  • funninessINT. How funny the joke is, from 1 (not funny) to 5 (hilarious).
  • when_addedDATE. When the joke was added to the database.

You can modify this list, if you want.

Add two or three jokes to the table.

Extract the starting files

Rather than asking you to write the entire application from scratch, I’ll give you all of the files with the database stuff removed. The PHP for templating is included.

Download the zip file. Extract it to a directory under your Web root, like c:\xampp\htdocs\jokes.

Try it in your browser.

Look through the files. Remind yourself how templating works.

Summary

You set up the Jokes application. It’s a starting point. You’ll add database code to it as we work though the chapter. You:

  • Created a database.
  • Created a database user.
  • Tested the connection.
  • Added a table and some data.
  • Extracted the starting files.

What now?

Let’s see how you let users add data to a database.

Adding data

Where are we?

You know how to create databases, add tables, and test connections. Now let’s get our hands (brains?) dirty. Let’s see how you help users add data.

This lesson’s goals

In this lesson, you will learn:

  • There are two pages for adding a record: one page with a form the user fills in, and another page that adds the user’s data to the database.
  • The SQL INSERT statement does the work.
  • Use stripslashes() to remove backslashes that PHP adds to form data.
  • Use $db->escape_string() to foil SQL injection attacks.

Adding a new toy

Let’s see how data gets added to the DogToys database.

The big picture

What’s the workflow when a user enters a new record?

First, the user clicks the Add product link on the administration menu.

Main administration screen

Figure 1. Main DogToys administration screen

The user sees a form:

Adding product data

Figure 2. Adding product data

The user types in the data, and clicks the Save button. This sends the data to a PHP page that saves the data. That page then goes back to the administration menu.

So the workflow is:

Adding product data

Figure 3. Adding product workflow

  • Browser: Show the administration menu.
  • User: Click the Add product link.
  • Browser: Show a form.
  • User: Fill in the form and click the Save button.
  • Server: Run a PHP program that saves the data. Then tell the browser to…
  • Browser: Show the administration menu.

The form

Here’s the code for the form. I omitted some validation stuff; I’ll add it back in the next lesson.

<form id="new_product_form" method="post" action="save-new-product.php">
  <p>
    Name<br>
    <input type="text" name="name" id="name" size="30">
  </p>
  <p>
    Description<br>
    <textarea name="description" id="description" rows="5" cols="30"></textarea>
  </p>
  <p>
    Image file name<br>
    <input type="text" name="image_file_name" id="image_file_name" size="30">
  </p>
  <p>
    Price<br>
    <input type="text" name="price" id="price" size="10">
  </p>
  <p>
    <button type="submit">Save</button>
  </p>
</form>

Figure 4. HTML for the form

First there’s the <form> tag:

<form id="new_product_form" method="post" action="save-new-product.php">

action tells the browser where to go to save-new-product.php when the user submits the form.

The rest of the code should be familiar. The <input type="text"> tags create one-line input fields. <textarea> creates a multi-line input field.

PHP to insert a record

OK, let’s look at save-new-product.php, the PHP that saves the data. The pattern for the page is:

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

Here is the entire code. We’ll run through it a step at a time.

<?php
//Save a new product.
//Input (all POST):
//  name: Name of the product.
//  description: Description of the product.
//  image_file_name: Name of the file containing an image of the product.
//  price: Selling price of the product.

$path_to_root = '..';
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);

//Get the form fields.
$name = stripslashes($_POST['name']);
$description = stripslashes($_POST['description']);
$image_file_name = stripslashes($_POST['image_file_name']);
$price = stripslashes($_POST['price']);

//Make the fields safe.
$name = $db->escape_string($name);
$description = $db->escape_string($description);
$image_file_name = $db->escape_string($image_file_name);
$price = $db->escape_string($price);

//Create and run the SQL.
$query = "insert into products
  (name, description, image_file_name, price)
  values ('$name', '$description', '$image_file_name', $price)";
$db->query($query);
//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 5. save-new-product.php

Let’s break it down into steps.

Lines 2 to 7 are documentation. They explain what the page does, and what input it expects. It’s good practice to add comments like this.

Line 9 sets the variable $path_to_root to the path from save-new-product.php (the file we’re talking about) to the root of the site. This is part of the templating system. Recall that one of the business goals of DogToys was to make the site easy to change. The templating system lets us do that.

Database connection

The first step in the pattern is:

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

Lines 11 and 12 connect to the database.

You could make a database connection with a line like this:

$db = new mysqli('localhost', 'dogtoys', 'password', 'dogtoys');

We’re going to be making connections to the database in many different PHP pages. The page that adds a record to the database needs to connect to the database. So does the page that saves edited data. And the page that deletes a record. And the product catalog page itself.

All of these pages would have a line like:

$db = new mysqli('localhost', 'dogtoys', 'password', 'dogtoys');

Now, what if we need to change the password? We’d need to find every one of these lines, in every page, and change it.

Ack!

There’s an easier way: put the connection information in a separate file, then include that file when needed. Here are the two lines from save-new-product.php:

require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);

Part of Figure 5 (again). save-new-product.php

Line 11 loads db-connect.php. Here’s what’s in it:

<?php
//DB connection data.
$host = 'localhost';
$db = 'dogtoys';
$user_name = 'dogtoys';
$password = 'password';
?>

Figure 6. db-connect.php

Line 12 uses the variables set in db-connect.php to make the connection.

Every page that needs to connect to the database uses db-connect.php.

If I want to change the password? I change one line in db-connect.php, and it’s changed for every page on the site.

Hooray! That’s another productivity win from reuse.

Grabbing the form data to insert

So we have a connection to the database. What’s next? Let’s look at the pattern.

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

Here’s some more code from save-new-product.php.

//Get the form fields.
$name = stripslashes($_POST['name']);
$description = stripslashes($_POST['description']);
$image_file_name = stripslashes($_POST['image_file_name']);
$price = stripslashes($_POST['price']);

Part of Figure 5 (again). save-new-product.php

This gets the data the user typed into the form fields, and puts it into variables.

As we talked about earlier, PHP sometimes adds backslashes (\) to data typed into form fields. It’s trying to be “helpful.” The stripslashes() function gets rid of them. It’s good to send every value from the user through stripslashes().

Foiling the Evil Doer

What’s next?

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

The next few lines are:

//Make the fields safe.
$name = $db->escape_string($name);
$description = $db->escape_string($description);
$image_file_name = $db->escape_string($image_file_name);
$price = $db->escape_string($price);

Part of Figure 5 (again). save-new-product.php

We’re going to create an SQL command from the data the user types in. It’s possible for an Evil Doer – one who knows SQL – to make trouble. Unless we do something about it.

Suppose someone typed this into the form:

Evil Doer at work

Figure 7. Evil Doer at work

The DROP statement is an SQL command that erases a table. By inserting quotes, semicolons, and SQL, a clever Evil Doer can make our PHP program do bad things.

This is called an SQL injection attack. You can see the consequences at XKCD.

escape_string() will foil the Evil Doer. It will mess up the quotes, semicolons, and other special characters needed for an SQL injection attack.

Hooray!

So run all form data through escape_string() before you do anything with it.

Creating and running an INSERT statement

What’s next?

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

Here are the next few lines:

//Create and run the SQL.
$query = "insert into products
  (name, description, image_file_name, price)
  values ('$name', '$description', '$image_file_name', $price)";
$db->query($query);

Part of Figure 5 (again). save-new-product.php

This makes an SQL statement, and puts it into the variable $query. I split the statement across several lines to make it easier to read. PHP lets you do this when you use double quotes (”) around the string. SQL doesn’t care that statements are split across lines.

Here’s another example of line splitting in PHP:

$temp = "
   <blockquote>
     <p>I love dogs!</p>
   </blockquote>";

The HTML inside the quotes is easy to follow.

Note all the single quotes (’) on line 29. It’s important to get them right. The field name is a text field, so you need to put quotes around the values you put in it.

The field price is a number. So no quotes for its value.

Line 30 sends the SQL to MySQL for execution.

Back to the administration menu

What’s next?

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

The end of save-new-product.php is:

header('location:index.php');

This tells the browser to jump back to index.php. There’s no directory, just a file name. save-new-product.php is in the /admin directory, so the browser will jump to /admin/index.php.

So that’s it.

  • The user fills in a form and clicks its submit button.
  • The data is sent to a PHP page:

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

W00f!

That was DogToys. Let’s have a look at DogRock.

Adding a new article

Writers add articles to DogRock. Let’s look at the overall workflow.

The big picture

What’s the workflow when a user enters a new record?

First, the user clicks the “Add article” link on the administration menu.

Main administration screen

Figure 8. Main DogRock administration screen

The user sees a form. S/he types in the data, and clicks the Save button.

Adding an article

Figure 9. Adding an article

This sends the data to a PHP page, which saves the data. That page then goes back to the administration menu.

So the workflow is:

Adding an article

Figure 10. Workflow for adding an article

  • Browser: Show the administration menu.
  • User: Click the Add article link.
  • Browser: Show a form.
  • User: Fill in the form and click the Save button.
  • Server: Run a PHP program that saves the data. Then tell the browser to…
  • Browser: Show the administration menu.

The form

Here’s the code for the form. Again, I stripped out some validation stuff.

<form id="new_article_form" method="post" action="save-new-article.php">
  <p>
    Title<br>
    <input type="text" name="title" id="title" size="30">
  </p>
  <p>
    Author<br>
    <input type="text" name="author" id="author" size="30">
  </p>
  <p>
    Body<br>
    <textarea name="body" id="body" rows="10" cols="30"></textarea>
  </p>
  <p>
    <button type="submit">Save</button>
  </p>
</form>

Figure 11. HTML for the form

First there’s the <form> tag:

<form id="new_article_form" method="post" action="save-new-article.php">

action tells the browser to send the data to the page save-new-article.php.

The rest of the code should be familiar. The <input type="text"> tags create one-line input fields. <textarea> creates a multi-line input field.

PHP to insert a record

Let’s look at save-new-article.php, the PHP that saves the data. The pattern is:

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Get the current date.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

The pattern is the same as before, except for the fourth line.

Here’s the code. I’ll go over it step by step in a moment.

<?php
//Save a new article.
//Input (all POST):
//  title: Title of the article.
//  body: Body of the article.
//  author: Author of the article.

$path_to_root = '..';
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);

//Get the form fields.
$title = stripslashes($_POST['title']);
$body = stripslashes($_POST['body']);
$author = stripslashes($_POST['author']);

//Make the fields safe.
$title = $db->escape_string($title);
$body = $db->escape_string($body);
$author = $db->escape_string($author);

//Prepare the publish date.
$when_published = date('Y-m-d');

//Create and run the SQL.
$query = "insert into articles
  (title, author, body, when_published)
  values ('$title', '$author', '$body', '$when_published')";
$db->query($query);

//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 12. save-new-article.php

Let’s break it down.

Lines 2 to 6 are documentation. They explain what the page does, and what input it expects.

Database connection

The pattern is:

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Get the current date.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

Lines 10 and 11 connect to the database. As before, the connection parameters (host, user name, etc.) are in a separate file.

Grabbing the form data to insert

What’s next?

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Get the current date.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

Here’s some more code from save-new-article.php.

//Get the form fields.
$title = stripslashes($_POST['title']);
$body = stripslashes($_POST['body']);
$author = stripslashes($_POST['author']);

Part of Figure 12 (again). save-new-article.php

This gets the data the user typed into the form fields, and puts it into variables. The stripslashes() function gets rid of the backslashes that PHP adds to form field data.

Foiling the Evil Doer

What’s next?

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Get the current date.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

Here’s the code for that step:

//Make the fields safe.
$title = $db->escape_string($title);
$body = $db->escape_string($body);
$author = $db->escape_string($author);

Part of Figure 12 (again). save-new-article.php

As before, this messes up an SQL injection attack.

Setting the date

What’s next?

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Get the current date.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

One of the things we store in the record for each article is the publication date. Rather than making the user type it, we can ask PHP what the current date is, and use that.

Here’s the line that does it:

$when_published = date('Y-m-d');

The date() function gets today’s date. The Y-m-d stuff gives a format: four digits for year, then a dash, then two digits for month, then a dash, then two digits for day. This is the international format that MySQL usually expects dates to be in.

What happens to the formatted date? It goes into the variable $when_published. We can use it just like the variables that contain the form data ($title, $body, and $author).

Creating and running an INSERT statement

What’s next?

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Get the current date.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

Here’s the code:

//Create and run the SQL.
$query = "insert into articles
  (title, author, body, when_published)
  values ('$title', '$author', '$body', '$when_published')";
$db->query($query);

Part of Figure 12 (again). save-new-article.php

This makes an SQL statement, and puts it into the variable $query. Again, the single quotes need to be right on line 29.

Line 30 sends the SQL to MySQL for execution.

Back to the administration menu

What’s next?

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Get the current date.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

The end of save-new-article.php is:

header('location:index.php');

This tells the browser to jump back to index.php. save-new-article.php is in the /admin directory, so the browswer will jump to /admin/index.php.

So that’s it.

  • The user fills in a form and clicks its submit button.
  • The data is sent to a PHP page:

  • Connect to the database.
  • Get the values the user typed into the form.
  • Make the values safe.
  • Get the current date.
  • Create and run an SQL INSERT statement.
  • Jump back to the admin menu.

W00f!

Exercise: Adding jokes

Change the Jokes application so that users can add jokes.

Remember that there are two pages:

  • The form the user fills in (admin/add-joke.php).
  • The PHP program that saves the joke to the database (admin/save-new-joke.php).

The first one you already have. It was in the zip file you downloaded. Your job is to write save-new-joke.php.

You can base it on save-new-product.php from DogToys and save-new-article.php from DogRock.

Don’t forget to adjust library/db-connect.php to use the right connection parameters.

Once you’ve finished, you can compare your solution with mine. But don’t look at it now!

If you want to share your solution with other people, you’ll need to:

  • Export your database to your hosting server.
  • Upload your Jokes application.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • There are two pages for adding a record: one page with a form the user fills in, and another page that adds the user’s data to the database.
  • The SQL INSERT statement does the work.
  • Use stripslashes() to remove backslashes that PHP adds to form data.
  • Use $db->escape_string() to foil SQL injection attacks.

What now?

You know how to add a record. But what happens when something goes wrong? Let’s see.

Handling errors

Where are we?

Yow know how to help people add new data to a database. But what happens when things go wrong?

This lesson’s goals

We’ve talked about error handling before. Both on the client side and the server side.

Using databases adds more things that can go wrong. In this lesson, we’ll see how you can handle database errors.

We’ll talk about two new types of errors:

  • Database connection errors
  • SQL errors

You can check for both.

We’ll also talk about simple server-side validation. Even if you can check all data on the client, you should check it on the server as well. To foil Evil Doers.

New types of errors

There are two new types of errors:

  • Connection errors
  • SQL errors

A connection error is when a PHP program can’t connect to the database. An SQL error is when MySQL says something is wrong with an SQL statement. Like SELECT being typed as SELCT.

Let’s look at each type of error.

Connection errors

We had this earlier:

require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);

Figure 1. Connecting to a database

If all is well, $db will be a valid connection object, something your PHP can use to send SQL to the database. But what if you have the wrong password, or user name? Or if the database server is down? Here’s how you check:

$db = new mysqli(...);
//Did it work?
if ( mysqli_connect_error() ) {
  print '<p>Error! Could not connect to the database. ';
  print 'Error message: '.mysqli_connect_error().'</p>';
  exit();
}
Do something.

Figure 2. Checking for a connection error

mysqli_connect_error() will be FALSE if there was no connection error. Otherwise, it will contain an error message. So if there was no connection error, the if will skip over lines 4 to 7, and continue with normal processing on line 8.

exit() causes PHP to stop immediately and end the page.

So this is how you handle a connection error. But there are also…

SQL errors

It’s easy for errors to find their way into SQL. For example, suppose I tried to use this to create a new record in the DogRock database:

$db = new mysqli(...);
...
$query = "insert into articles
  (title, author, body, when_published)
  values ($title, $author, $body, $when_published)";
$db->query($query);

Figure 3. SQL error

Can you see what’s wrong?

Quotes are missing. Suppose $title had the value Why I like fish. This:

... values($title,

would become:

... values(Why I like fish,

What we want is:

... values('Why I like fish',

The quotes keep everything together, so the title is treated as one value. When the quotes are missing, MySQL doesn’t know what to do.

Here’s how to check that an SQL query is correct:

$db = new mysqli(...);
...
$query = "insert into articles
  (title, author, body, when_published)
  values ($title, $author, $body, $when_published)";
$db->query($query);
if ( $db->error != '' ) {
  print '<p>SQL error! Message: ' . $db->error . '</p>';
  print "<p>Query:</p>
    <blockquote>
    $query
    </blockquote>";
  exit();
}

Figure 4. SQL error checking

Line 7 is:

if ( $db->error != '' ) {

If there is no error, then $db->error will be empty. If there is an error, then $db->error will not be empty.

If there’s an error, line 8 shows the error message. Lines 9 to 12 show the query that caused the error.

Line 13 stops the program immediately.

Summary so far – There are two new types of errors:

  • Database connection errors
  • SQL errors

You’ve seen how to test for them.

Renata
Renata

So, if you don’t get any errors from the $db->error check, that means that everything is OK?

Kieran
Kieran

No!

It means that the format of the SQL statement was correct. But you might still have coding errors.

Suppose this was in your PHP:

$query = "insert into products
  (name, description, image_file_name, price)
  values ('$name', '$image_file_name', '$description', $price)";
$db->query($query);

Does that look OK?

Renata
Renata

Let me check… Hmm… Wait, the image file name and description are backwards. You have:

(name, description, image_file_name, price)

in one place, and:

('$name', '$image_file_name', '$description', $price)

in another.

Kieran
Kieran

Right! It’s a bug.

But the format of the SQL statement is just fine. MySQL would detect no errors, and would do what you told it to.

CC
CC

So what’s the point, if it won’t tell you about all of the errors?

Kieran
Kieran

$db->error will tell you about some of the errors. And that’s better than nothing. But you still need to look in the database, and make sure that your data was added correctly.

Renata
Renata

And we could use phpMyAdmin to check the data, right?

Kieran
Kieran

Yes. You can use phpMyAdmin to check whether your PHP program did what you thought it should.

DogToys: Saving a new product record

Let’s see a new version of save-new-product.php. Recall that save-new-product.php is sent the form data the user types, and saves it to the database.

Here is the code with error checking:

<?php
//Save a new product.
//Input (all POST):
//  name: Name of the product.
//  description: Description of the product.
//  image_file_name: Name of the file containing an image of the product.
//  price: Selling price of the product.

$path_to_root = '..';
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Did it work?
if ( mysqli_connect_error() ) {
  print '<p>Error! Could not connect to the database. ';
  print 'Error message: '.mysqli_connect_error().'</p>';
  exit();
}

//Get the form fields.
$name = stripslashes($_POST['name']);
$description = stripslashes($_POST['description']);
$image_file_name = stripslashes($_POST['image_file_name']);
$price = stripslashes($_POST['price']);

//Make the fields safe.
$name = $db->escape_string($name);
$description = $db->escape_string($description);
$image_file_name = $db->escape_string($image_file_name);
$price = $db->escape_string($price);

//Create and run the SQL.
$query = "insert into products
  (name, description, image_file_name, price)
  values ('$name', '$description', '$image_file_name', $price)";
$db->query($query);
if ( $db->error != '' ) {
  print '<p>SQL error! Message: ' . $db->error . '</p>';
  print "<p>Query:</p>
    <blockquote>
    $query
  </blockquote>";
  exit();
}

//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 5. save-new-product.php

The code starting at line 14 checks whether the connection to MySQL was made successfully.

The code starting at line 37 checks whether the SQL statement ran without error.

DogRock: Saving a new article

Let’s do the same with DogRock.

<?php
//Save a new article.
//Input (all POST):
//  title: Title of the article.
//  body: Body of the article.
//  author: Author of the article.

$path_to_root = '..';
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
if ( mysqli_connect_error() ) {
  print '<p>Error! Could not connect to the database. ';
  print 'Error message: '.mysqli_connect_error().'</p>';
  exit();
}

//Get the form fields.
$title = stripslashes($_POST['title']);
$body = stripslashes($_POST['body']);
$author = stripslashes($_POST['author']);

//Make the fields safe.
$title = $db->escape_string($title);
$body = $db->escape_string($body);
$author = $db->escape_string($author);

//Prepare the publish date.
$when_published = date('Y-m-d');

//Create and run the SQL.
$query = "insert into articles
  (title, author, body, when_published)
  values ('$title', '$author', '$body', '$when_published')";
$db->query($query);
if ( $db->error != '' ) {
  print '<p>SQL error! Message: ' . $db->error . '</p>';
  print "<p>Query:</p>
    <blockquote>
    $query
  </blockquote>";
  exit();
}

//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 6. save-new-article.php

It has shiny new error checking code.

CC
CC

I can see how this error checking is needed.

But aren’t most errors made by people? Maybe typing something wrong? How are they checked?

Kieran
Kieran

Ooo, good question! You’re right, people make more mistakes than computers. Let’s see how to check for those sorts of errors

Validating user data

We looked earlier at how to do validation. Remember that we did some client-side checking, and some server side checking.

We ended up with an architecture like this:

Validation architecture

Figure 7. Validation architecture

order.php would do client-side checking with JavaScript. It would also do server-side checking with PHP. order.php would send form data to itself to do any server-side checking. Then, if all was OK, it would send the data to process.php.

We hadn’t written process.php at that point. But now we can write it, storing information to a database.

Let’s see how that will work for our two sample applications

Validating in DogToys

Here’s the input form for adding a new product:

Add product form

Figure 8. Add product form – add-product.php

What errors can the user make? Not many.

  • Leaving any field blank.
  • Entering a non-number in price, or a negative number.

We could add other checks, like making sure that the image name is a valid file name. But let’s leave it for now.

Both of these checks can be done on the client side. And I added code to do them to add-product.php. Like this:

...
//Check the name field.
var name = $("#name").val();
if ( name == '' ) {
  data_ok = false;
  show_field_error_message('Sorry, there must be a name.', 'name');
}
else {
  hide_error_message('name');
}
...
<p>
  Name<br>
  <input type="text" name="name" id="name" size="30"><br>
  <span id="name_message_container" class="message_container">
    <img src="<?php print $path_to_root; ?>/library/error.png" alt="Error">
    <span id="name_message"/>
  </span>
</p>
...

Figure 9. Client-side error checking

I used the same approach we used earlier to show error messages. The result is like this:

Client-side error

Figure 10. Client-side error

So all of our validation can be done on the client, in JavaScript.

But we want to add server-side validation anyway, in PHP.

Why?

Security. A smart hacker could grab the code from add-product.php (the page that shows the form). S/he could delete the JavaScript code that did the client-side error checks, and run the modified page. It would accept bad data (like a negative price), and send it to save-new-product.php for storage in the database. If save-new-product.php didn’t check the data, it would let the evil data into the database.

So we’ll add some simple validation to save-new-product.php. Nothing fancy, but enough to make sure that evil data doesn’t get into the database.

Here’s yet another version of save-new-product.php.

<?php
//Save a new product.
//Input (all POST):
//  name: Name of the product.
//  description: Description of the product.
//  image_file_name: Name of the file containing an image of the product.
//  price: Selling price of the product.

$path_to_root = '..';
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Did it work?
if ( mysqli_connect_error() ) {
  print '<p>Error! Could not connect to the database. ';
  print 'Error message: '.mysqli_connect_error().'</p>';
  exit();
}

//Get the form fields.
$name = stripslashes($_POST['name']);
$description = stripslashes($_POST['description']);
$image_file_name = stripslashes($_POST['image_file_name']);
$price = stripslashes($_POST['price']);

//Validate
if ( $name == '' ) {
  print '<p>Error! Name is missing.</p>';
  exit();
}
if ( $description == '' ) {
  print '<p>Error! Description is missing.</p>';
  exit();
}
if ( $image_file_name == '' ) {
  print '<p>Error! Image file name is missing.</p>';
  exit();
}
if ( $price == '' ) {
  print '<p>Error! Price is missing.</p>';
  exit();
}
if ( is_nan($price) ) {
  print "<p>Error! Price is not a number: $price.</p>";
  exit();
}
if ( $price <= 0 ) {
  print "<p>Error! Price is too low: $price.</p>";
  exit();
}

//Make the fields safe.
$name = $db->escape_string($name);
$description = $db->escape_string($description);
$image_file_name = $db->escape_string($image_file_name);
$price = $db->escape_string($price);

//Create and run the SQL.
$query = "insert into products
  (name, description, image_file_name, price)
  values ('$name', '$description', '$image_file_name', $price)";
$db->query($query);
if ( $db->error != '' ) {
  print '<p>SQL error! Message: ' . $db->error . '</p>';
  print "<p>Query:</p>
    <blockquote>
    $query
  </blockquote>";
  exit();
}

//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 11. Another version of save-new-product.php

The new stuff starts at line 26. If there’s an error, a message is shown, and the program exits at once. This gives ugly error messages, but that’s acceptable. These errors should never happen, unless either:

  • The DogToys application is broken.
  • An Evil Doer is attacking.

Line 43 shows the “is not a number” function in PHP:

is_nan()

The syntax is a little different from JavaScript. In JS, it’s:

isNaN() This is JavaScript, not PHP.

But they do the same thing.

You could also use:

if ( ! is_numeric($price) ) {

is_numeric() is TRUE if you give it a numeric value. ! means “not.” So this version of the if says: “if it is not true that $price is numeric.”

Use either is_nan() or ! is_numeric(). Use the one that makes more sense to you.

Line 44 shows a good practice: it outputs the value that is incorrect. This helps in debugging.

Validating in DogRock

Here’s some new code for save-new-article.php:

//Validate
if ( $title == '' ) {
  print '<p>Error! Title is missing.</p>';
  exit();
}
if ( $body == '' ) {
  print '<p>Error! Body is missing.</p>';
  exit();
}
if ( $author == '' ) {
  print '<p>Error! Author is missing.</p>';
  exit();
}

Figure 12. Code for save-new-article.php

It just makes sure that each field has a value.

Exercise: Check for errors in Jokes

Add error checking to save-new-joke.php. Check for:

  • Connection errors.
  • SQL errors.
  • Validation errors. Missing data, or an invalid funniness value.

How to check whether your error checking worked? You need to break stuff. Here are some suggestions.

  • Fill in the add-joke.php form, and, before you click the Save button, use the XAMPP control panel to stop MySQL. Then click the Save button. This will simulate a DBMS crash.
  • Fill in the add-joke.php form, and, before you click the Save button, change admin/db-connect.php. Use the wrong password. Then click the Save button.
  • Sabotage the JavaScript error checking in save-joke.php. Now try to save bad data, like a joke with a funniness rating of -8. This simulates a hacker attack.

One way to sabotage the client-side error checking is to add a new line:

$("#new_joke_form").submit(function() {
   return true;

The return statement will make sure that the form data is always accepted, even if some fields are invalid.

Once you have it working, you can check my solution. But don’t look at it now!

If you want to share your solution with other people, you will need to upload it to your hosting server.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

We talked about error checking and validation on this page. We saw two new types of errors:

  • Database connection errors
  • SQL errors

You can check for them both.

We also talked about simple server-side validation. Even if you can check all data on the client, you should check it on the server as well. To foil Evil Doers.

What now?

You know how to add data to the database. But how do you show the data that was added? That’s coming up next.

Showing data

Where are we?

You know how to add data to a table. Now let’s see how you can show it.

This lesson’s goals

In this lesson, you learn that:

  • The SQL SELECT statement returns a record set. A record set has one or more rows of data.
  • You can loop across a record set, extracting each row. From each row, you extract the fields in the row, and use them to create some HTML.

Showing the DogToys product catalog

The goal

We want to make something like this:

Product catalog

Figure 1. Product catalog

The HTML uses the <table> tag. There’s a row for each product.

<Start table detour>

Let’s have a quick review of <table>s. Suppose we want to make a table like this:

Rendered table

Figure 2. Rendered table

Here’s the HTML code:

<table>
  <thead>
    <tr>
      <th>Food</th>
      <th>Rating</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Bacon</td>
      <td>Yum!</td>
    </tr>
    <tr>
      <td>Brussel sprouts</td>
      <td>Yuck!</td>
    </tr>
  </tbody>
</table>

Figure 3. The <table> tag

The whole thing is wrapped in <table>.

The <tr> tag makes a row. The <td> makes a cell in a row. The “d” in <td> stands for “data.”

<th> makes a cell as well, but with some special formating that’s good for column headers (hence the “h” in <th>).

The <thead> and <tbody> tags separate the table into two regions, header and body. The tags help with styling. For example, you can easily give different background colors to the header and body of the table.

To get the spacing in Figure 2, use the following CSS:

table {
  border-spacing: 10px;
}
td {
  padding: 10px;
}

Figure 4. CSS for table spacing

This puts 10 pixels between each cell (line 2) and 10 pixels between the edges of each cell and its content (line 5).

That’s all you need to know about tables for now. You can read the table chapter if you want to know more.

</ End table detour>

So our PHP will output HTML code with the table tag. Here’s the rendered product page:

Product catalog

Figure 1 (again). Product catalog

Here’s the HTML that will make it.

<tr> 
  <td> 
    <img class='product_image' src='./product-images/ball.jpg'> 
  </td> 
  <td class='product_name_description_container'> 
    <p class='product_name'>Squeaky ball</p> 
    <p class='product_description'>Bouncing, squeaking fun!</p> 
  </td> 
  <td class='product_price'>1.99</td> 
</tr> 
<tr> 
  <td> 
    <img class='product_image' src='./product-images/frisbee.jpg'> 
  </td> 
  <td class='product_name_description_container'> 
    <p class='product_name'>Frisbee</p> 
    <p class='product_description'>A whirling disk of pure goodness! From <a href="http://www.wham-o.com/">Wham-O</a>.</p> 
  </td> 
  <td class='product_price'>10.95</td> 
</tr> 
<tr> 
  <td> 
    <img class='product_image' src='./product-images/rope.jpg'> 
  </td> 
  <td class='product_name_description_container'> 
    <p class='product_name'>Giant chew rope</p> 
    <p class='product_description'>Nom nom nom, and nom again. Chew this, not human shoes. You know how touchy they are.</p> 
  </td> 
  <td class='product_price'>12.95</td> 
</tr>

Figure 5. HTML created by PHP

The pattern

Here’s the pattern for PHP that will take data from a MySQL table, and make HTML to show it:

  • Create a SELECT statement. SELECT is the SQL command to grab data.
  • Execute the SQL, and get back a record set. A record set is the result of a SELECT. Could be one record, or five, or 1,289.
  • For each record in the record set:
    • Get the fields from the records.
    • Show the fields.

The code

Here’s the code that will generate the HTML to show the product list. To make the code easier to follow, I’ve omitted some stuff having to do with sorting. I’ll add it back in the next lesson.

I’ve also omitted error checking, e.g., checking that the database connection was successful. That would clutter things, making it harder for you to focus on the core ideas of this lesson.

Here’s the code. We’ll go through it piece by piece.

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Create the query.
$query = "
  select product_id, name, description, image_file_name, price
    from products";
//Run the query.
$record_set = $db->query($query);
//Start the output table.
?>
<table class="product_table">
  <thead>
    <tr>
      <th>&nbsp;</th>
      <th>Name</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
<?php
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for a product.
  $product_id = $row['product_id'];
  $name = $row['name'];
  $description = $row['description'];
  $image_file_name = $row['image_file_name'];
  $price = $row['price'];
  //Output
  print "
    <tr>
      <td>
        <img class='product_image' src='$path_to_root/product-images/$image_file_name'>
      </td>
      <td class='product_name_description_container'>
        <p class='product_name'>$name</p>
        <p class='product_description'>$description</p>
      </td>
      <td class='product_price'>$price</td>
    </tr>";
} //End while.
?>
  </tbody>
</table>

Figure 6. PHP to show the product catalog

Lines 3 and 4 open the database. As usual.

Lines 6 to 8 create the SQL query that fetches the data. I broke it across lines to make it easier for me to read. The statement has the form:

SELECT fields FROM table;

When you send this to MySQL:

select name, description, image_file_name, price from products;

You are telling MySQL:

Open up the products table, and, for each row, fetch name, description, image_file_name, and price.

Here’s what you get:

From MySQL

Figure 7. A record set from MySQL

Renata
Renata

Hmm, that looks like part of a phpMyAdmin screen.

Kieran
Kieran

It is. I copied the SQL query from my code, clicked the SQL tab in phpMyAdmin, and pasted in the query. Clicked the Go button, and MySQL ran it.

That’s a good way to test your own queries.

Once the query has been created, it gets run in line 10:

$record_set = $db->query($query);

query() returns a record set, that gets put into the variable $record_set. The record set is this:

From MySQL

Figure 7 (again). A record set from MySQL

It’s the results of running the query.

We’re used to a variable containing something simple, like a number or some text:

$x = 42;
$y = 'The answer!';

But $record_set is different. It contains a complex object.

We don’t care too much what an object really is. Just think of it as a blob of data. There can be all kinds of data in an object.

You need special functions to get at the data inside an object. We’ll see some in a moment.

OK, so we’ve:

  • Created an SQL query.
  • Sent it to MySQL.
  • Gotten an object back. The object is a record set, with rows and columns.

Now we need to show the records in a <table>. There’s some code in Figure 6 to output the <table> tag and the header:

<table class="product_table">
  <thead>
    <tr>
      <th>&nbsp;</th>
      <th>Name</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>

Part of Figure 6. PHP to show the product catalog

Line 16 is a little strange.

<th>&nbsp;</th>

The first column in the output table has the item’s picture. It doesn’t need a heading. So line 16 creates an empty heading cell.

Here’s the next part of the code, where the meat is:

<?php
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for a product.
  $name = $row['name'];
  $description = $row['description'];
  $image_file_name = $row['image_file_name'];
  $price = $row['price'];
  //Output
  print "
    <tr>
      <td>
        <img class='product_image' src='$path_to_root/product-images/$image_file_name'>
      </td>
      <td class='product_name_description_container'>
        <p class='product_name'>$name</p>
        <p class='product_description'>$description</p>
      </td>
      <td class='product_price'>$price</td>
    </tr>";
} //End while.
?>

Part of Figure 6. PHP to show the product catalog

while is a new PHP statement for us. It’s a loop. It executes the same PHP statements a bunch of times.

Its format is:

while( test ) {
  Do this
}

It says:

Keep doing “Do this” while the test is true.

Here’s an example:

<?php
$x = 1;
while ( $x <= 5 ) {
  print "$x<br>";
  $x += 1;
}
?>

Figure 8. while loop example

This says:

While $x is less than or equal to 5:
     Output $x and a line break tag.
     Add 1 to $x.

The PHP engine will keep doing lines 4 and 5 while the condition is true.

Line 5 is a common PHP shortcut. += means “add to.” So:

$x += 1;

means “Add 1 to what is already in $x.”

Here’s the HTML the code generates:

1<br>2<br>3<br>4<br>5<br>

Here’s how it renders:

Rendered output

Figure 9. Rendered output

Let’s have another look at the loop in our database program:

while( $row = $record_set->fetch_assoc() ) {
     Stuff to do
}

The PHP engine will keep looping around, doing “Stuff to do” while $row = $record_set->fetch_assoc() is true. When $row = $record_set->fetch_assoc() stops being true (when it is false), the PHP engine stops the loop. It will skip to the code after the loop.

Let’s look at:

$record_set->fetch_assoc()

Recall that $record_set is a record set object. It contains this data:

From MySQL

Figure 7 (again). A record set from MySQL

fetch_assoc() is a function that returns one row.

When a function belongs to an object, it’s really called a “method.” But let’s keep calling it a function for simplicity.

A record set object has other data besides some rows. It also has a record pointer:

Record pointer

Figure 10. Record pointer

The record pointer starts off pointing to the first record.

When you use the fetch_assoc() function, you are saying two things:

  • Give me the current record (the one the pointer points to).
  • Move the pointer down one record.

What does “Give me the current record” mean? Here’s the code again:

$row = $record_set->fetch_assoc()

fetch_assoc() copies $record_set’s current record into $row. fetch_assoc() doesn’t remove the data from $record_set. It’s still there. fetch_assoc() copies the row, and sends it to $row.

Then fetch_assoc() moves the pointer down. So after fetch_assoc() has run once, we would have:

<code>fetch_assoc()</code> has run once

Figure 11. fetch_assoc() has run once

fetch_assoc() has returned the record it was pointing to, and moved the pointer down one record.

Run fetch_assoc() a second time, and you get:

<code>fetch_assoc()</code> has run twice

Figure 12. fetch_assoc() has run twice

Run fetch_assoc() a third time, and you get:

<code>fetch_assoc()</code> has run three times

Figure 13. fetch_assoc() has run three times

The record pointer has moved beyond the end of the table.

Run fetch_assoc() a fourth time, and you get:

<code>fetch_assoc()</code> has run four times

Figure 14. fetch_assoc() has run four times

The record pointer is pointing to an empty space, so $row = $record_set->fetch_assoc() puts NULL into $row. NULL acts like FALSE in PHP’s if(), while(), and other statements.

Here’s the loop again:

while( $row = $record_set->fetch_assoc() ) {
     Stuff to do
}

This will do “Stuff to do” again and again. The first time, $row will have the first record. The second time, $row will have the second record. And so on.

The last time, $row will have the last record. When the while() tries to run again, fetch_assoc() will return NULL, which acts like FALSE. So the loop will stop. Execution will pick up after the brace (}) that ends the loop.

What we have now is a way to grab each record in the record set. Each one gets put into $row. But what do we do with $row?

Remember our goal. We want to create a <table> that contains rows like this:

<tr> 
  <td> 
    <img class='product_image' src='./product-images/ball.jpg'> 
  </td> 
  <td class='product_name_description_container'> 
    <p class='product_name'>Squeaky ball</p> 
    <p class='product_description'>Bouncing, squeaking fun!</p> 
  </td> 
  <td class='product_price'>1.99</td> 
</tr> 
<tr> 
  <td> 
    <img class='product_image' src='./product-images/frisbee.jpg'> 
  </td> 
  <td class='product_name_description_container'> 
    <p class='product_name'>Frisbee</p> 
    <p class='product_description'>A whirling disk of pure goodness! From <a href="http://www.wham-o.com/">Wham-O</a>.</p> 
  </td> 
  <td class='product_price'>10.95</td> 
</tr> 
<tr> 
  <td> 
    <img class='product_image' src='./product-images/rope.jpg'> 
  </td> 
  <td class='product_name_description_container'> 
    <p class='product_name'>Giant chew rope</p> 
    <p class='product_description'>Nom nom nom, and nom again. Chew this, not human shoes. You know how touchy they are.</p> 
  </td> 
  <td class='product_price'>12.95</td> 
</tr>

Figure 5 (again). HTML created by PHP

Lines 1 to 10 are for the first product. They were created from the first row in the record set:

From MySQL

Figure 7 (again). A record set from MySQL

The second row in the table was created from the second row in the record set.

Here’s the entire loop again:

while( $row = $record_set->fetch_assoc() ) {
  //Get fields for a product.
  $name = $row['name'];
  $description = $row['description'];
  $image_file_name = $row['image_file_name'];
  $price = $row['price'];
  //Output
  print "
    <tr>
      <td>
        <img class='product_image' src='$path_to_root/product-images/$image_file_name'>
      </td>
      <td class='product_name_description_container'>
        <p class='product_name'>$name</p>
        <p class='product_description'>$description</p>
      </td>
      <td class='product_price'>$price</td>
    </tr>";
} //End while.

Part of Figure 6 (again). PHP to show the product catalog

This from line 24:

$row = $record_set->fetch_assoc()

puts a row from the record set into $row.

Remember that $record_set doesn’t have a single value, like 7 or bark. It’s an object, with a bunch of data.

$row doesn’t contain a single value, either. But it isn’t a record set object. It’s an array. We don’t care right now about arrays. Just know that an array contains a bunch of values that you can access.

Remember the SQL query used to create the record set:

select name, description, image_file_name, price from products

So record (row) in the record set has the fields name, description, image_file_name, and price. When:

$row = $record_set->fetch_assoc()

puts the current record in $row, it puts four values into $row.

Here are the next few lines of code:


//Get fields for a product.
$name = $row[‘name’];
$description = $row[‘description’];
$image_file_name = $row[‘image_file_name’];
$price = $row[‘price’];

You can access array elements like this:

$array_name['element name']

So…

$name = $row['name'];

... gets the name element from $row and puts it into the variable $name. $name is not a complex object. It’s just a variable with a simple string in it.

We end up with four variables containing the name, description, image file name, and price for the current record.

Then we can output the variables:

//Output
print "
  <tr>
    <td>
      <img class='product_image' src='$path_to_root/product-images/$image_file_name'>
    </td>
    <td class='product_name_description_container'>
      <p class='product_name'>$name</p>
      <p class='product_description'>$description</p>
    </td>
    <td class='product_price'>$price</td>
  </tr>";

Part of Figure 6 (again). PHP to show the product catalog

This PHP statement outputs the fields, inserting them into some HTML, to create a table row.

Here’s all the code again:

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Create the query.
$query = "
  select product_id, name, description, image_file_name, price
    from products";
//Run the query.
$record_set = $db->query($query);
//Start the output table.
?>
<table class="product_table">
  <thead>
    <tr>
      <th>&nbsp;</th>
      <th>Name</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
<?php
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for a product.
  $product_id = $row['product_id'];
  $name = $row['name'];
  $description = $row['description'];
  $image_file_name = $row['image_file_name'];
  $price = $row['price'];
  //Output
  print "
    <tr>
      <td>
        <img class='product_image' src='$path_to_root/product-images/$image_file_name'>
      </td>
      <td class='product_name_description_container'>
        <p class='product_name'>$name</p>
        <p class='product_description'>$description</p>
      </td>
      <td class='product_price'>$price</td>
    </tr>";
} //End while.
?>
  </tbody>
</table>

Figure 6 (again). PHP to show the product catalog

You should be able to follow it now.

Lines 3 and 4 connect to the database.

Lines 6 to 10 run a SELECT query, fetching a record set from MySQL.

Lines 13 to 21 output the table header.

Lines 24 to 43 loop across the record set, showing a table row for each product.

Lines 45 and 46 close the table’s HTML.

W00f!

That’s a lot of new stuff we’ve covered. If you need a brain break, take one.

Let’s take a look at the second sample application, the DogRock CMS.

Showing the DogRock article list

We want to make something like this:

Article list

Figure 15. Article list

Here’s part of the HTML that shows the table:

<tr> 
  <td>A new howl on the prowl</td> 
  <td>February 5, 2010</td> 
  <td>Lumis</td> 
</tr> 
<tr> 
  <td>Somebody let them out!</td> 
  <td>February 3, 2010</td> 
  <td>Bounder</td> 
</tr> 

Figure 16. HTML for the article list

I’ve omitted some stuff for simplicity. It’ll come back later.

Here’s the PHP that creates the HTML that makes the page.

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Create the query.
$query = "
    select title, author, when_published
    from articles";
//Run the query.
$record_set = $db->query($query);
//Start the output table.
?>
<table cellpadding="10" cellspacing="10" border="0">
  <thead>
    <tr>
      <th>Title</th>
      <th>Date</th>
      <th>Author</th>
    </tr>
  </thead>
  <tbody>
<?php
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for an article.
  $title = $row['title'];
  $when_published = $row['when_published'];
  $author = $row['author'];
  //Format the date.
  $when_published = date('F j, Y', strtotime($when_published));
  //Output
  print "
    <tr>
      <td>$title</td>
      <td>$when_published</td>
      <td>$author</td>
    </tr>";
} //end while
?>
    </tbody>
  </table>

Figure 17. Article list code

Lines 3 and 4 connect to the database.

Lines 6 to 8 create an SQL query:

select title, author, when_published from articles

This says:

From the articles table, fetch the fields title, author, and when_published for every record.

Why “every record?” Because there’s no WHERE clause. Like this:

select title, author, when_published from articles where author='Bounder'

This would fetch data only for the rows that have Bounder in the author field.

But we don’t have a WHERE clause, so all rows are returned.

Lines 13 to 21 output the table header and such.

The fun starts here:

//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for an article.
  $title = $row['title'];
  $when_published = $row['when_published'];
  $author = $row['author'];
  //Format the date.
  $when_published = date('F j, Y', strtotime($when_published));
  //Output
  print "
    <tr>
      <td>$title</td>
      <td>$when_published</td>
      <td>$author</td>
    </tr>";
} //end while

Part of Figure 15. Article list code

Line 24 fetches a row from the record set. Lines 26 to 28 fetch fields from the row into variables.

Line 30 formats the date in $when_published. The format in the database is YYYY-MM-DD, like 1960-01-03 (my birthday – yes, I’m old). This is the standard international format, but most people aren’t used to it.

This code…

date('F j, Y', strtotime($when_published))

... uses the date() function to format the date in the common “Month Day, Year” format, such as “January 3, 1960.” date() expects the date you give it to be in a certain format – a Unix timestamp. strtotime() converts a YYYY-MM-DD date (from the database) into a Unix timestamp (that the date() function needs).

The output from the date() function is put back into $when_published:

$when_published = date('F j, Y', strtotime($when_published));

The last few lines show the record:

//Output
print "
  <tr>
    <td>$title</td>
    <td>$when_published</td>
    <td>$author</td>
  </tr>";

So that’s it! We now have a list of articles. W00f!

There’s something missing here, though. Here’s the output we want:

Article list

Figure 15 (again). Article list

We want the name of the article to be a clickable link. We’ll look at that later.

Exercise: Showing jokes

Write jokes.php, a page that will show the jokes in your database. You already have a version of the page; it was in the download. You need to add the database logic.

Include error checking code. Check that:

  • The database connection worked.
  • The SQL query was valid.

Don’t worry about sorting the records. We’ll talk about that later.

Don’t include the links to the individual jokes. That comes later, too.

Once you have it running, you can check my solution. But don’t look at it now!

Jokes also puts a list of jokes on the home page, like DogRock does. Do that too. Don’t worry about sorting, links, or limiting the number of records. That comes later.

You can check my solution once you have it running.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • The SQL SELECT statement returns a record set. A record set has one or more rows of data.
  • You can loop across a record set, extracting each row. From each row, you extract the fields in the row, and use them to create some HTML.

What now?

Let’s see how you can let users sort the data.

Showing sorted data

Where are we?

You know how to show users the data in a database. What if they want to sort the data? Let’s see how you do that.

This lesson’s goals

In this lesson, you will learn:

  • The ORDER BY clause of the SELECT statement tells MySQL to sort records in the record set.
  • Let users choose the sort order by adding a GET parameter to links. Add code that will append different fields to ORDER BY, depending on the parameter.
  • The LIMIT clause of the SELECT statement sets the maximum number of records MySQL will add to a record set.

The ORDER keyword

Recall that the SQL SELECT command returns a bunch of rows:

select name, description, image_file_name, price from products;

You can tell MySQL how to sort the returned rows. Add ORDER BY and then the field to sort by.

For example, this…

select name, description, image_file_name, price from products order by name;

... returns:

Sorted by name

Figure 1. Sorted by name

This…

select name, description, image_file_name, price from products order by image_file_name;

... returns:

Sorted by image_file_name

Figure 2. Sorted by image_file_name

This…

select name, description, image_file_name, price from products order by price;

... returns:

Sorted by price

Figure 3. Sorted by price

By default, the records are sorted in ascending order, from smaller to larger. But you can change this. For example, if you want to sort by price from high to low:

select name, description, image_file_name, price from products order by price desc;

I added desc for “descending” after the sort field name. This returns:

Sorted by price

Figure 4. Sorted by price, descending

Sorting is not difficult. But it’s trickier to…

Let the user choose the sort order

We want to let the user choose how to sort the data. Here’s the interface for the product list:

Sort interface for products

Figure 5. Sort interface for products

The user can click on the product name to sort by name, or click on an arrow to sort by price, from low to high, or high to low.

You can try it.

How does this work? The URLs give a clue. Here’s how the URL changes when the user clicks on the “Price” link.

User sorting by price

Figure 6. User sorting by price

The URL of the page is normally products.php. This gives a default sort order, by name. Then the user clicks the sort arrow. The HTML for the arrow is:

<a href="products.php?order=price_asc"> 
  <img src="./library/arrow_down.png"
    alt="Sort from low to high"> 
</a>

Figure 7. HTML for a sort arrow in products.php

Look at the href in line 1 of Figure 7:

products.php?order=price_asc

So the page points to itself, but with a parameter order.

When this renders (Figure 6), the user sees the data sorted ascending by price.

For this to work, we need:

  • A way for the page to get a sort order from the URL.
  • A way to apply the sort order.
  • Links that send the sort order to the page, as in Figure 7.

Let’s look at the code for DogToys.

Sorting the DogToys product list

Here’s products.php:

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Create the query.
$query = "
  select product_id, name, description, image_file_name, price
  from products
  order by ";
//Append the sort order.
$order = $_GET['order'];
if ( $order == 'price_asc' ) {
  $query .= 'price asc';
}
else if ( $order == 'price_desc' ) {
  $query .= 'price desc';
}
else {
  //Default sort order.
  $query .= 'name';
}

//Run the query.
$record_set = $db->query($query);
//Start the output table.
?>
<table cellpadding="5" cellspacing="0" border="0">
  <thead>
    <tr>
      <th> </th>
      <th><a href="products.php?order=name">Name</a></th>
      <th>Price<br>
        <a href="products.php?order=price_asc">
          <img src="<?php print $path_to_root; ?>/library/arrow_down.png"
               alt="Sort from low to high">
        </a>
        <a href="products.php?order=price_desc">
          <img src="<?php print $path_to_root; ?>/library/arrow_up.png"
               alt="Sort from high to low">
        </a>
      </th>
    </tr>
  </thead>
  <tbody>
<?php
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
...

Figure 8. products.php

I’ve omitted some code for simplicity.

We need to add the ORDER BY to the SQL. That’s what lines 9 to 21 do.

Line 9 adds “order by” to the SQL statement. But the statement is not complete. Order by what? That’s what the next few lines figure out.

Line 11 …

$order = $_GET['order'];

... fetches the value of the GET parameter order, if there is one. Recall that GET passes data to a Web page through the URL. The value of order is put in the PHP variable $order.

Here are the next few lines (lines 12 to 14):

if ( $order == 'price_asc' ) {
   $query .= 'price asc';
}

Recall that .= means “append” or “concatenate.” In normal words, “add to the end of.” So id the variable $order contained price_asc, then price asc would get appended to the SQL statement. It would end like this:

... order by price asc

Now the order by clause is complete. When the SQL statement is run, the records will be sorted by price in ascending order.

Lines 15 to 17 append price desc to the SQL statement, if $order contains price_desc:

if ( $order == 'price_desc' ) {
   $query .= 'price desc';
}

If $order contains anything else, or nothing at all, then line 20 sets the default sort order to name:

$query .= 'name';

The SQL query now has a complete ORDER BY clause, so MySQL will sort the data before returning it.

The last thing we need is the interface, that is, a way for the user to select the sort order. That’s what lines 31, 33, and 37 do:

<a href="products.php?order=name">

<a href="products.php?order=price_asc">

<a href="products.php?order=price_desc">

Clicking these links will cause products.php to reload itself, but with a different sort order.

Sorting the DogRock articles list

Here’s the code for articles.php:

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Create the query.
$query = "select article_id, title, author, when_published
    from articles
    order by ";
//Append the sort order.
$order = $_GET['order'];
if ( $order == 'date' ) {
  $query .= 'when_published desc';
}
else if ( $order == 'author' ) {
  $query .= 'author';
}
else {
  //Default sort order.
  $query .= 'title';
}
//Run the query.
$record_set = $db->query($query);
//Start the output table.
?>
<table cellpadding="10" cellspacing="10" border="0">
  <thead>
    <tr>
      <th><a href="articles.php?order=title">Title</a></th>
      <th><a href="articles.php?order=date">Date</a></th>
      <th><a href="articles.php?order=author">Author</a></th>
    </tr>
  </thead>
  <tbody>
<?php
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
...

Figure 9. articles.php

It works the same way as products.php. Clicking a link (lines 28, 29, and 30) causes articles.php to reload itself, passing a sort order.

Line 10 gets the order from the URL.

Lines 11 to 20 complete the SQL statement, depending on which sort order the user selected.

The DogRock home page

The DogRock site also shows articles on its home page. It shows the three most recent:

Most recent articles

Figure 10. Most recent articles

They are sorted descending by date, so the most recent article is first.

Here’s the code for the page, with some things omitted.

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Fetch the three most recently published articles.
$query = "select article_id, title, when_published
    from articles
    order by when_published desc
    limit 3";
$record_set = $db->query($query);
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for an article.
  $article_id = $row['article_id'];
  $title = $row['title'];
  $when_published = $row['when_published'];
  //Format the date.
  $when_published = date('F j, Y', strtotime($when_published));
  print "<p>$title ($when_published)</p>";
} //End while.
?>

Figure 11. index.php for DogRock

Line 8 adds the sort order:

order by when_published desc

The desc means the the highest (most recent) articles are first.

Line 9 shows something new:

limit 3

MySQL will limit the record set it returns to the first three records.

The rest is as before.

Exercise: Showing sorted jokes

Change jokes.php so that users can sort by title, funniness, and date. Don’t worry about linking a joke’s title to its content; that comes later.

You can check my solution. But do it yourself first!

Now change the home page so that the user sees the three funniest jokes.

You can check my solution. But, as before, do it yourself first!

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • The ORDER BY clause of the SELECT statement tells MySQL to sort records in the record set.
  • Let users choose the sort order by adding a GET parameter to links. Add code that will append different fields to ORDER BY, depending on the parameter.
  • The LIMIT clause of the SELECT statement sets the maximum number of records MySQL will add to a record set.

What now?

Let’s see how you let users “drill down,” that is, go from summary data (e.g., just the title of an article) to details (e.g., all of the data about the article).

Drilling down

Where are we?

You know how to create a listing of data, like the DogRocks’ article list. But each listing only shows the title of the article, not the article itself.

This lesson’s goals

In this lesson, you will learn that:

  • Drilling down means going from summary to detail.
  • On a summary page, use PHP to create links that include a primary key value.
  • On a detail page, get the primary key value from the link, and use it to fetch detail data from the database.

Getting the details

“Drilling down” means to get more detailed information, to go from a summary to detail.

Here’s the article list from DogRock:

Article list

Figure 1. Article list, from articles.php

The reader gets a little information on what each article is about: the title. If the reader wants more, s/he can click on the title of the article. For example, clicking on the first one shows:

An article

Figure 2. An article

How does this work? Here’s some of the HTML that creates Figure 1, the article list:

<tr> 
  <td><a href='show-article.php?id=2'>A new howl on the prowl</a></td> 
  <td>February 5, 2010</td> 
  <td>Lumis</td> 
</tr> 
<tr> 
  <td><a href='show-article.php?id=1'>Somebody let them out!</a></td> 
  <td>February 3, 2010</td> 
  <td>Bounder</td> 
</tr> 

Figure 3. HTML generated by articles.php

Lines 1 to 5 show a table row for an article. You can see the link to the article on line 2:

<a href='show-article.php?id=2'>

show-article.php will show an article, if you send it the article’s id. If you want a different article, send it a different id.

Here’s a picture of what’s happening:

Link to article

Figure 4. Link to article

You can see the HTML that makes the link. The user clicks on the link. The browser jumps to show-article.php, passing id of the article to show.

What are the ids? They’re from the database:

articles table

Figure 5. articles table

The article ids are the primary key values.

So we need to do two things to get this working:

  • Write PHP for articles.php, to create links to the articles, links like <a href='show-article.php?id=2'>.
  • Write show-article.php, so it will take an id, and show the article with that id.

Let’s look at each piece.

The article list

Here’s code from articles.php. I’ve omitted error control and sorting.

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Create the query.
$query = "select article_id, title, author, when_published
    from articles";
//Run the query.
$record_set = $db->query($query);
//Start the output table.
...
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for an article.
  $article_id = $row['article_id'];
  $title = $row['title'];
  $when_published = $row['when_published'];
  $author = $row['author'];
  //Format the date.
  $when_published = date('F j, Y', strtotime($when_published));
  //Output
  print "
    <tr>
      <td><a href='show-article.php?id=$article_id'>$title</a></td>
      <td>$when_published</td>
      <td>$author</td>
    </tr>";
} //end while
?>

Figure 6. articles.php

Lines 3 and 4 connect to the MySQL database.

Lines 6 to 9 create and run the MySQL query. Here it is:

select article_id, title, author, when_published from articles

The query retrieves article_id, because we’ll need it to make the link to show-article.php. show-article.php needs to know which article to show.

Here’s the data the query might fetch:

Article data

Figure 7. Article data

The while() loop (lines 13 t0 28) works as before. It grabs the fields from each row, including each record’s article_id and title. They’re put in the variables $article_id and $title (lines 15 and 16).

The program outputs the link to each article with line 24:

<a href='show-article.php?id=$article_id'>$title</a>

If $article_id is 2 and $title is A new howl on the prowl (from the first record in Figure 7), then we get:

<td><a href='show-article.php?id=2'>A new howl on the prowl</a></td>

W00f! We have the article list.

Showing an article

How do we show the article? Here’s show-article.php, with error checking omitted.

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Fetch article data.
$article_id = $_GET['id'];
$query = "select title, body, when_published, author
    from articles
    where article_id = " . $article_id;
$record_set = $db->query($query);
$row = $record_set->fetch_assoc();
//Get fields.
$title = $row['title'];
$body = $row['body'];
$when_published = $row['when_published'];
$author = $row['author'];
//Format the date.
$when_published = date('F j, Y', strtotime($when_published));
//For the body, change new lines into <br> tags.
$body = str_replace("\n", "<br>", $body);
//Output.
print "
  <h2>$title</h2>
  <p>By $author</p>
  <p>$when_published</p>
  <p>$body</p>
";
?>

Figure 8. show-article.php

Line 6 gets the id of the article from the URL.

Lines 7 to 9 use the id to create an SQL statement to fetch the article with that id. It will fetch just one article. Remember that article_id is the primary key of the article table, so each row in the table will have a different value for article_id.

Line 10 runs the query, getting a record set back from MySQL. It puts the record set into the variable $record_set.

Line 11 takes the row from the record set:

$row = $record_set->fetch_assoc();

There is only one row, so no loop is needed.

Lines 13 to 16 extract the fields from the $row. Line 18 formats the date.

Renata
Renata

I don’t see the article id there. Why don’t you have something like this?

$article_id = $row['article_id'];

Kieran
Kieran

article_id was passed into the page. The page got it on line 6:

$article_id = $_GET['id'];

So there was no need to fetch it again from the database.

Lines 20 is something new. The body field of the articles table contains the main text of the article. When a writer types it in, s/he might do something like this:

Typing in an article

Figure 9. Typing in an article

The writer added a blank line between the two paragraphs. It gets stored this way my MySQL:

Article body shown by phpMyAdmin

Figure 10. Article body shown by phpMyAdmin

But, as you know, browsers don’t render whitespace in HTML. And an empty line is whitespace.

Take this:

<blockquote>
  <p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae quam quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec commodo vehicula neque, vitae mollis ligula bibendum sit amet.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae quam quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec commodo vehicula neque, vitae mollis ligula bibendum sit amet.
  </p>
</blockquote>

Figure 11. Empty line in body

It will render as:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae quam quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec commodo vehicula neque, vitae mollis ligula bibendum sit amet. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae quam quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec commodo vehicula neque, vitae mollis ligula bibendum sit amet.

Figure 12. Empty line in body, rendered

The two paragraphs are smooshed together. There isn’t any whitespace between them, because browsers don’t render whitespace in HTML.

How can we make sure that the empty lines in the body are rendered by the browser?

Here’s one way, from line 20 of the code:

$body = str_replace("\n", "<br>", $body);

\n stands for the “new line” character. It’s the character that your keyboard makes when you press the Enter key. The str_replace() function goes through $body, and replaces all of the \ns with <br>s. As you, the <br> renders as a line break.

By the way, it’s important to use double quotes – "\n" – rather than single quotes – '\n'. PHP will only interpret the \n as the new line character when it’s in double quotes.

Here’s what $body looks like before the str_replace():

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae quam quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec commodo vehicula neque, vitae mollis ligula bibendum sit amet.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae quam quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec commodo vehicula neque, vitae mollis ligula bibendum sit amet.

It contains two \ns, one at the end of line 1, and other at the end of line 2. They’re might have been one at the end of line 3, but the writer didn’t type it.

Here’s what $body looks like after the substitution:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae quam quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec commodo vehicula neque, vitae mollis ligula bibendum sit amet.
<br> 
<br>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae quam quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec commodo vehicula neque, vitae mollis ligula bibendum sit amet.

The two <br>s make the browser show the empty line. W00f!

Here’s the rest of the code from show-article.php:

//Output.
print "
  <h2>$title</h2>
  <p>By $author</p>
  <p>$when_published</p>
  <p>$body</p>
";

Figure 13. Part of show-article.php

It outputs the article data.

Exercise: Drilling down to jokes

Change jokes.php so that each joke title is a link to the page show-joke.php. This page shows all of the details of a joke. You can see the page in action on my sample site.

Do the same for the home page.

You can see my code for:

But don’t look at them now! Do it yourself first.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

You learned:

  • Drilling down means going from summary to detail.
  • On a summary page, use PHP to create links that include a primary key value.
  • On a detail page, get the primary key value from the link, and use it to fetch detail data from the database.

The administration menu

Where are we?

You know how to add records, and show them. What about maintaining the data? How do we let people edit and delete records?

That’s what we look at next. But we start with the administration menu. It lets users select the records they want to edit or delete.

This lesson’s goals

Learn that:

  • Our applications’ sites have two parts: one for regular users, and one for administration.
  • The admin menu lists items (products or articles), with Edit and Delete links for each.

The administration section

Both DogToys and DogRock have two parts to their sites:

  • The public part, with product and article lists.
  • The adminstration part.

The pages for the administration part of the site are all in the admin directory. Here is the layout of the DogToys site:

DogToys directory layout

Figure 1. DogToys directory layout

The administration menu (admin/index.php) gives users access to the all of the administrative functions. This is what it looks like:

DogToys administration menu

Figure 2. DogToys administration menu

At the top is a link to add a new record. At the bottom is a list of existing records, with Edit and Delete links. This is much like the list of products on the product catalog, but with some extra stuff.

Let’s see how this works.

DogToys administration menu

Here’s the code. I’ve included all the sorting and error checking.

<p>What do you want to do?</p>
<blockquote>
  <p><a href="add-product.php">Add a new product</a></p>
</blockquote>
<h2>Current products</h2>
<?php
//List the current products, along with edit/delete action for each one.
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
if ( mysqli_connect_error() ) {
  print '<p>Error! Could not connect to the database. ';
  print 'Error message: '.mysqli_connect_error().'</p>';
  exit();
}
//Fetch product data.
$query = "select product_id, name, description,
      image_file_name, price
    from products
    order by ";
//Get the product list order, if given.
$order = $_GET['order'];
if ( $order == 'price_asc' ) {
  $query .= 'price asc';
}
else if ( $order == 'price_desc' ) {
  $query .= 'price desc';
}
else {
  //Default sort order.
  $query .= 'name';
}
$record_set = $db->query($query);
if ( $db->error != '' ) {
  print '<p>SQL error! Message: ' . $db->error . '</p>';
  print "<p>Query:</p>
    <blockquote>
    $query
  </blockquote>";
  exit();
}
//Start the product table.
?>
<table cellpadding="5" cellspacing="0" border="0">
  <thead>
    <tr>
      <th> </th>
      <th><a href="index.php?order=name">Name</a></th>
      <th>Description</th>
      <th>Price<br>
        <a href="index.php?order=price_asc">
          <img src="<?php print $path_to_root; ?>/library/arrow_down.png"
               alt="Sort from low to high">
        </a>
        <a href="index.php?order=price_desc">
          <img src="<?php print $path_to_root; ?>/library/arrow_up.png"
               alt="Sort from high to low">
        </a>
      </th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
<?php
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for an product.
  $product_id = $row['product_id'];
  $name = $row['name'];
  $description = $row['description'];
  $image_file_name = $row['image_file_name'];
  $price = $row['price'];
  //Output
  print "
    <tr>
      <td>
        <img src='$path_to_root/product-images/$image_file_name'>
      </td>
      <td>$name</td>
      <td>$description</td>
      <td>$price</td>
      <td>
        <a href='edit-product.php?id=$product_id'>Edit</a><br>
        <a href='confirm-delete-product.php?id=$product_id'>Delete</a>
      </td>
    </tr>";
} //End while.
?>
  </tbody>
</table>

Figure 3. DogToys administration menu code

Line 3 makes a link to the add-product.php page.

Lines 9 and 10 connect to the database. Lines 11 to 15 check that the connection was successful.

Lines 17 to 32 create the SQL query. The statement has an ORDER BY clause attached.

Line 33 runs the query. Lines 34 to 41 check for error reports from the database.

Lines 66 to 87 output a table row for each product. Here are the lines that make the Edit and Delete links:

<a href='edit-product.php?id=$product_id'>Edit</a><br>
<a href='confirm-delete-product.php?id=$product_id'>Delete</a>

They use the “drill-down” method we used in the previous lesson. The product id is attached to the URL for the edit and delete pages.

We’ll look at how editing and deletion is done in future lessons. For now, just notice how the links are created to edit-product.php and confirm-delete-product.php.

DogRock administration menu

The DogRock administration menu is much the same:

DogRock administration menu

Figure 4. DogRock administration menu

Here are excerpts from the code that makes the menu:

<p>What do you want to do?</p>
<blockquote>
  <p><a href="add-article.php">Add a new article</a></p>
</blockquote>
<h2>Current articles</h2>
<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
...
//Fetch article data.
$query = "select article_id, title, author, when_published
    from articles
    order by ";
...
$record_set = $db->query($query);
...
//Loop across records.
while( $row = $record_set->fetch_assoc() ) {
  //Get fields for an article.
  $article_id = $row['article_id'];
  $title = $row['title'];
  $when_published = $row['when_published'];
  $author = $row['author'];
  ...
  //Output
  print "
    <tr>
      <td><a href='../show-article.php?id=$article_id'>$title</a></td>
      <td>$when_published</td>
      <td>$author</td>
      <td>
        <a href='edit-article.php?id=$article_id'>Edit</a><br>
        <a href='confirm-delete-article.php?id=$article_id'>Delete</a>
      </td>
    </tr>";
}
?>

Figure 5. DogRock administration menu code

It’s much the same as the other administration menu. Lines 33 and 34 create the Edit and Delete links.

Exercise: Jokes administration menu

Add an administration menu to your Jokes application. It should have a link to the add form, plus edit and delete links for each joke.

The links should refer to edit-joke.php and confirm-delete-joke.php. They don’t exist yet; you’ll add them later.

You can check my solution. But don’t look at it now! Do it yourself, first.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • Our applications’ sites have two parts: one for regular users, and one for administration.
  • The admin menu lists items (products or articles), with Edit and Delete links for each.

What now?

Let’s see how record deletion works.

Deleting records

Where are we?

You know how to create an admin menu, with a delete link for each record. But what happens when the user clicks on one? Let’s see.

This lesson’s goals

In this lesson, learn:

  • On the admin menu, there’s a delete link for each record. It includes the id of the record.
  • Clicking the delete link shows a confirmation page. This page shows all of the data in the record to be deleted. It has a form with a hidden field containing the record id, and a confirmation button.
  • If the user confirms the delete, another PHP program creates and runs an SQL DELETE statement.

The DELETE statement

The SQL DELETE statement deletes records from a table. Here’s a typical example:

delete from dogs where weight < 25;

This will delete all records from the dogs table that have a weight less than 25.

Typically, you use the primary key value to delete a single record. Like this:

delete from articles where article_id = 17;

Delete a DogToys product

The administration menu looks like this:

DogToys administration menu

Figure 1. DogToys administration menu

There’s a Delete link for each record. Here’s the PHP that makes it:

while( $row = $record_set->fetch_assoc() ) {
  //Get fields for an product.
  $product_id = $row['product_id'];
  ...
  print "
  ...
        <a href='confirm-delete-product.php?id=$product_id'>Delete</a>
?>
  </tbody>
</table>

Figure 2. DogToys administration menu code

The code generates HTML like this:

<a href='confirm-delete-product.php?id=2'>Delete</a>

Figure 3. HTML generated by PHP

When the user clicks on a Delete link, we could delete the record immediately. But that’s not a good idea. It would be easy to accidentally click the Delete link, maybe when aiming for the Edit link.

So let’s ask the user to confirm the deletion. That’s why the link in Figure 3 calls the page confirm-delete-product.php. It renders like this:

Confirm deletion

Figure 4. Confirm deletion

The page shows the record that the user has chosen to delete. The user has to click the button before the record will be deleted.

Here’s code for this page.

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Fetch product data.
$product_id = $_GET['id'];
$query = "select name, image_file_name, price
    from products
    where product_id = " . $product_id;
$record_set = $db->query($query);
$row = $record_set->fetch_assoc();
//Get fields.
$name = $row['name'];
$image_file_name = $row['image_file_name'];
$price = $row['price'];
//Output confirmation.
print "
  <p>You have chosen to delete this product:</p>
  <blockquote>
    <p><img src='$path_to_root/product-images/$image_file_name'> $name</p>
    <p>$$price</p>
  </blockquote>
";
?>
<form method="post" action="delete-product.php">
  <p>Are you sure you want to do this?</p>
  <p>The action cannot be undone.</p>
  <p>
    <input type="hidden" name="id" value="<?php print $product_id; ?>">
    <button type="submit">Confirm</button>
  </p>
</form>
<p><a href="index.php">< Back</a></p>

Figure 5. confirm-delete-product.php

Lines 3 and 4 connect to the database.

Line 6 gets the id of the record from the URL.

Lines 7 to 9 creates an SQL SELECT statement with the id. Line 10 runs the query.

Line 11 gets a row from the record set (there will be only one) and puts it into the variable $row. Lines 13 to 15 get the fields from $row, putting them into variables.

The next few lines show the field values:

print "
  <p>You have chosen to delete this product:</p>
  <blockquote>
    <p><img src='$path_to_root/product-images/$image_file_name'> $name</p>
    <p>$$price</p>
  </blockquote>
";

Part of Figure 5. confirm-delete-product.php

Line 21 looks a little odd:

<p>$$price</p>

Why two dollar signs? The second dollar sign is part of the variable name. The first one is shown on the page. So if $price is 9.95, then the following shows:

<p>$9.95</p>

Let’s look at the screen shot again:

Confirm deletion

Figure 4 (again). Confirm deletion

We’ve done everything except for the button. Here’s the code:

<form method="post" action="delete-product.php">
  <p>Are you sure you want to do this?</p>
  <p>The action cannot be undone.</p>
  <p>
    <input type="hidden" name="id" value="<?php print $product_id; ?>">
    <button type="submit">Confirm</button>
  </p>
</form>

Another part of Figure 5. confirm-delete-product.php

The program that will delete the product is delete-product.php. It’s the action property of the form:

<form method="post" action="delete-product.php">

We only need to send one piece of data to the page: the id of the product to delete. How to add it to the form? With a hidden field. It’s in line 29:

<input type="hidden" name="id" value="<?php print $product_id; ?>">

If the product id is, say, 17, the HTML will be:

<input type="hidden" name="id" value="17">

A hidden field is like a regular <input> field, but it isn’t shown on the page. Its value, however, is passed to the destination page, like any other field on a form.

What happens when the user clicks the Confirm button? The id is sent to delete-product.php, which actually does the deletion.

Here’s the code for delete-product.php. I left out the error checking code.

<?php
//Delete a product.
//Input:
//  id: id number of the product. POST.
$path_to_root = '..';
$product_id = $_POST['id'];
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Delete the product.
$query = "delete from products where product_id = $product_id";
$db->query($query);
//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 6. delete-product.php

Line 6 gets the id passed to the page.

Lines 8 and 9 connect to the database.

Line 11 creates the SQL statement that will delete the product:

$query = "delete from products where product_id = $product_id";

Line 12 runs the query.

Finally…

header('location:index.php');

...back to the administration menu.

Delete a DogRock article

The administration menu for DogRock looks like this:

DogRock administration menu

Figure 7. DogRock administration menu

There’s a Delete link for each record. Here’s the PHP that makes it:

while( $row = $record_set->fetch_assoc() ) {
  //Get fields for an article.
  $article_id = $row['article_id'];
  ...
  print "
  ...
        <a href='confirm-delete-article.php?id=$article_id'>Delete</a>
?>
  </tbody>
</table>

Figure 8. DogRock administration menu code

Clicking on the Delete link loads a confirmation page:

Confirm deletion

Figure 9. Confirm deletion

Here’s the code for this page.

<?php
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Fetch product data.
$product_id = $_GET['id'];
$query = "select name, image_file_name, price
    from products
    where product_id = " . $product_id;
$record_set = $db->query($query);
$row = $record_set->fetch_assoc();
//Get fields.
$name = $row['name'];
$image_file_name = $row['image_file_name'];
$price = $row['price'];
//Output confirmation.
print "
  <p>You have chosen to delete this product:</p>
  <blockquote>
    <p><img src='$path_to_root/product-images/$image_file_name'> $name</p>
    <p>$$price</p>
  </blockquote>
";
?>
<form method="post" action="delete-product.php">
  <p>Are you sure you want to do this?</p>
  <p>The action cannot be undone.</p>
  <p>
    <input type="hidden" name="id" value="<?php print $product_id; ?>">
    <button type="submit">Confirm</button>
  </p>
</form>
<p><a href="index.php">< Back</a></p>

Figure 10. confirm-delete-article.php

As before, it shows the record to be deleted, then a <form> with:

  • A hidden field with the id of the record.
  • A confirmation button.

Here’s the code for delete-article.php.

<?php
//Delete an article.
//Input:
//  id: id number of the article. POST.
$path_to_root = '..';
$article_id = $_POST['id'];
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Delete the article.
$query = "delete from articles where article_id = $article_id";
$db->query($query);
//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 6. delete-article.php

This is almost identical to delete-product.php.

Exercise: Deleting jokes

Add confirm-delete-joke.php and delete-joke.php to your Jokes application. They should act like their DogToys and DogRock counterparts.

You can see my solutions for the confirmation and deletion pages. But write them yourself first!

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • On the admin menu, there’s a delete link for each record. It includes the id of the record.
  • Clicking the delete link shows a confirmation page. This page shows all of the data in the record to be deleted. It has a form with a hidden field containing the record id, and a confirmation button.
  • If the user confirms the delete, another PHP program creates and runs an SQL DELETE statement.

What now?

We’re almost done. Time to let users edit existing records.

Editing records

Where are we?

You’ve created an administration menu, that lets users delete and edit records. You know how to program the delete part. Let’s talk about the edit part.

This lesson’s goals

In this lesson, learn:

  • The add and edit forms are similar, the edit form has data already in it, and passes around an id number.
  • The SQL UPDATE statement changes data in an existing record. Usually, it has a single primary key value in its WHERE clause.

Editing is like adding

Here’s a screenshot of someone editing a product record:

Editing a product record

Figure 1. Editing a product record

The user changes the values and clicks the button.

Here’s the form for adding a product:

Adding a product

Figure 2. Adding a product

They’re almost the same. They have the same form fields, and the same client-side validation.

The main differences between add and edit are:

  • Records being edited already exist in the database. Records being added do not. Therefore…
  • Records being edited already have a primary key value. For products, that’s a product_id. New records don’t have a product id. MySQL will choose an id value when it adds a record to the database. (Recall that we made product_id an auto_increment field.)
  • The edit form has the current data in the fields. All of the fields on the add form are blank.
  • To edit a record, you use the SQL statement UPDATE. To add a new record, we use the INSERT statement.

The UPDATE statement

Let’s look at the dogs table again. Each row has data about one dog. The table has the fields:

  • dog_id: the dog’s id number (integer, primary key).
  • name: name of the dog (character).
  • breed: breed of the dog (character).
  • weight: weight of the dog in pounds (integer).

Here’s some sample data:

Dog data

Figure 3. Dog data

Suppose Brian puts on two pounds. Here’s an SQL statement to show the change:

update dogs
   set weight = 53
   where dog_id = 5;

The statement tells MySQL three things:

  • What table to change: dogs.
  • Which record to change: the one with a dog_id of 5.
  • What the new field values are: change weight to 53.

Most update statements just change one record, using a primary key value.

You can change text values, like this:

update dogs
   set name = 'Fido'
   where dog_id = 5;

Don’t forget the quotes around the text.

You can change more than one field at a time. For example:

update dogs
   set name = 'Fido',
     weight = 56
   where dog_id = 5;

Renata
Renata

When we changed the weight to 53. Could we do this?

update dogs
   set weight = 53
   where name = 'Brian';

Kieran
Kieran

Yes, we could. You can use text fields in the where clause. It would work in this case, but…

Could there be more than one dog named Brian?

Renata
Renata

Hmm, I suppose there could be.

Kieran
Kieran

Right! And your SQL statement would change the weight of all those dogs to 53.

So, usually, when you use the update statement, you use the primary key. This identifies a single record. Here it is again:

update dogs
   set weight = 53
   where dog_id = 5;

There is only one dog with an id of 5. Even if we had eight dogs called Brian, only one of them would have an id of 5.

Let’s see how we can use the update statement.

DogToys: Editing a product

Suppose a user wants to edit product data on the DogToys site. Let’s look at the workflow.

The workflow

The user starts at the admin menu, and selects a product to edit:

Admin menu

Figure 4. Admin menu

Here’s a sample Edit link:

<a href='edit-product.php?id=2'>Edit</a>

Clicking the link jumps to the page edit-product.php, passing an id of 2. This is the product_id of the product the user wants to edit.

edit-product.php shows a form like this:

Editing a product record

Figure 1 (again). Editing a product record

The user changes the data and clicks the Save button. If all the validation checks are passed, the new data is saved into the database, and the user is taken back to the main menu.

Here’s a picture of the workflow:

Workflow for editing a product record

Figure 5. Workflow for editing a product record

The edit form

Let’s look at the code for edit-product.php, the edit form. Here’s what the code has to produce.

Editing a product record

Figure 1 (again). Editing a product record

Each of the form fields shows the current data.

Here’s how edit-product.php works:

  • Get the id of the product.
  • Look up the data for that product.
  • Show the form, with product data in each field.

Here’s the page. Some of the code has been removed for simplicity.

<?php
...
//Connect to the database.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Fetch product data.
$product_id = $_GET['id'];
$query = "select name, description,
      image_file_name, price
    from products
    where product_id = " . $product_id;
$record_set = $db->query($query);
$row = $record_set->fetch_assoc();
//Extract fields.
$name = $row['name'];
$description = $row['description'];
$image_file_name = $row['image_file_name'];
$price = $row['price'];
?>
...
<form id="edit_product_form" method="post" action="save-edited-product.php">
  <p>
    Name<br>
    <input type="text" name="name" id="name" size="30"
           value="<?php print $name; ?>">
  </p>
  <p>
    Description<br>
    <textarea name="description" id="description" rows="5" cols="30"><?php print $description; ?></textarea>
  </p>
  <p>
    Image file name<br>
    <input type="text" name="image_file_name" id="image_file_name" size="30"
           value="<?php print $image_file_name; ?>">
  </p>
  <p>
    Price<br>
    <input type="text" name="price" id="price" size="10"
           value="<?php print $price; ?>">
  </p>
  <p>
    <input type="hidden" name="product_id" value="<?php print $product_id; ?>">
    <button type="submit">Save</button>
  </p>
</form>

Figure 6. edit-product.php

Line 4 to 5 connect to the database.

Line 7 gets the product id from the URL. Recall that the URLs are like this:

edit-product.php?id=2

Lines 8 to 11 create an SQL statement that looks up the data for the product. For example, if id was 2, the query would be:

select name, description, image_file_name, price
   from products
   where product_id = 2

Line 12 runs the query. The query only returns one row, because the where clause tests the primary key.

Line 13 fetches the row. Lines 15 to 18 get the individual fields, and put them into variables. For example:

$name = $row['name'];

Here’s how that data is used:

<input type="text" name="name" id="name" size="30"
   value="<?php print $name; ?>">

If $name contained Frisbee, this line would become:

<input type="text" name="name" id="name" size="30"
   value="Frisbee">

When the browser renders the field, it will put the value Frisbee into it:

Name field rendered

Figure 7. Name field rendered

This page has to send the new data to save-product.php. It needs to include the product_id. But the id is not actually shown in the form. The user can’t change it, so there’s no point in showing it.

So how do you put some data into a form so that it can be sent, but not have it visible to the user?

The solution: use a hidden field. Like this:

<input type="hidden" name="product_id" value="<?php print $product_id; ?>">

The product_id will travel along with the rest of the form fields. But the user won’t see it.

W00f!

Saving the data

Here’s the workflow again:

Workflow for editing a product record

Figure 5 (again). Workflow for editing a product record

When the user clicks the Save button, the browser sends the data to save-edited-product.php. This is the action property of the form in line 21 in Figure 6:

<form id="edit_product_form" method="post" action="save-edited-product.php">

Here’s what save-edited-product.php has to do:

  • Get the new data for the product.
  • Make an update statement.
  • Run it.
  • Jump back to the admin menu.

Here’s the code. Error checking and some other code has been omitted.

<?php
$path_to_root = '..';
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Get the form fields.
$product_id = stripslashes($_POST['product_id']);
$name = stripslashes($_POST['name']);
$description = stripslashes($_POST['description']);
$image_file_name = stripslashes($_POST['image_file_name']);
$price = stripslashes($_POST['price']);
//Make the fields safe.
$product_id = $db->escape_string($product_id);
$name = $db->escape_string($name);
$description = $db->escape_string($description);
$image_file_name = $db->escape_string($image_file_name);
$price = $db->escape_string($price);
//Create and run the SQL.
$query = "update products
  set name = '$name',
  description = '$description',
  image_file_name = '$image_file_name',
  price = $price
  where product_id = $product_id";
$db->query($query);
//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 8. save-edited-product.php

Lines 4 and 5 connect to the database.

Lines 7 to 11 get the form data, including the product_id passed as a hidden field. Note that stripslashes() was used to undo PHP’s “helpful” insertion of backslashes.

Lines 13 to 17 make the data safe from Evil Doers. It defuses SQL injection attacks.

Lines 19 to 24 creates the SQL query. It includes quotes (’) for text fields, and a where clause to select the right product.

Line 25 runs the query.

Line 27 jumps back to the admin menu.

That’s it for DogToys. Let’s move on.

DogRock: Editing an article

Workflow

The workflow for editing an article is the same as the workflow for editing a product:

  • User clicks the Edit link in the admin menu.
  • Browser shows an edit form.
  • User changes the data and clicks the Save button.
  • A PHP page saves the data.

The edit form

Here’s what the form looks like:

Editing an article

Figure 9. Editing an article

Here’s the code, with some stuff omitted:

<?php
//Connect to the database.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Fetch article data.
$article_id = $_GET['id'];
$query = "select title, body, when_published, author
    from articles
    where article_id = " . $article_id;
$record_set = $db->query($query);
$row = $record_set->fetch_assoc();
//Extract fields.
$title = $row['title'];
$body = $row['body'];
$when_published = $row['when_published'];
$author = $row['author'];
//Format the date.
$when_published = date('F j, Y', strtotime($when_published));
?>
<form id="new_article_form" method="post" action="save-edited-article.php">
  <p>
    Title<br>
    <input type="text" name="title" id="title" size="40"
           value="<?php print $title; ?>">
  </p>
  <p>
    Author<br>
    <input type="text" name="author" id="author" size="40"
           value="<?php print $author; ?>">
  </p>
  <p>
    When published<br>
    <input type="text" name="when_published" id="when_published" size="40"
           value="<?php print $when_published; ?>">
  </p>
  <p>
    Body<br>
    <textarea name="body" id="body" rows="8" cols="40"><?php print $body; ?></textarea>
  </p>
  <p>
    <input type="hidden" name="article_id" value="<?php print $article_id; ?>">
    <button type="submit">Save</button>
  </p>
</form>

Figure 10. edit-article.php

Lines 3 and 4 connect to the database.

Lines 6 gets the id of the article wants to edit. Lines 7 to 9 create the SQL query, which includes the article’s primary key. Line 10 runs the query, and line 11 gets the row fetched by MySQL.

Lines 13 to 16 extract the individual fields from the row. Line 18 formats the date to a familiar format.

The form is then shown. Each field’s value property puts the current value into the field. Line 41 adds the article-id as a hidden field, so the id will get passed to the page that saves the data.

Saving the data

Here’s save-edited-article.php, the program that saves the new article data.

<?php
$path_to_root = '..';
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($host, $user_name, $password, $db);
//Get the form fields.
$article_id = stripslashes($_POST['article_id']);
$title = stripslashes($_POST['title']);
$body = stripslashes($_POST['body']);
$author = stripslashes($_POST['author']);
$when_published = stripslashes($_POST['when_published']);
//Make the fields safe.
$article_id = $db->escape_string($article_id);
$title = $db->escape_string($title);
$body = $db->escape_string($body);
$author = $db->escape_string($author);
$when_published = $db->escape_string($when_published);
//Format the date.
$when_published = date('Y-m-d', strtotime($when_published));
//Create and run the SQL.
$query = "update articles
  set title = '$title',
  author = '$author',
  body = '$body',
  when_published = '$when_published'
  where article_id = $article_id";
$db->query($query);
//Back to admin menu.
header('location:index.php');
exit();
?>

Figure 11. save-edited-article.php

Lines 4 and 5 connect to the database. Lines 7 to 11 get the data passed into the form, and strips the excess backslashes with stripslashes().

Lines 13 to 17 sanitize the data. Line 19 converts the publication date into the format MySQL prefers.

Lines 21 to 26 create the SQL update statement. Line 27 runs the query.

Line 29 jumps back to the admin menu.

W00f!

Patterns

Here’s the pattern for the edit page itself.

[node:pattern/updating-database-record noterms]

Exercise: Editing jokes

Give users the ability to edit existing jokes. Model your code on DogToys and DogRock.

You can see my code for edit-joke.php and save-edited-joke.php. But do it yourself first!

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • The add and edit forms are similar, the edit form has data already in it, and passes around an id number.
  • The SQL UPDATE statement changes data in an existing record. Usually, it has a single primary key value in its WHERE clause.

What now?

W00f!

Time for some more exercises.

Exercises: Basic database applications

Exercise: Create a dog movie database

On your local machine, create a database called dogmovies. It will have data about movies that have dogs in them.

Make a user who can access the database.

Add a table to the database. Call it movies. Add fields for:

  • Movie id (the primary key)
  • Movie name
  • Year made
  • Rating (e.g., PG-13)

Add some movie data from this page.

Write a PHP test page to connect to the database.

Now duplicate the database and the test page on your hosting account.

Put the URL of the test page below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Create a dog book database

On your local machine, create a database called dogbooks. It will have data about books about dogs.

Make a user who can access the database.

Add a table to the database. Call it books. Add fields for:

  • Book id (the primary key)
  • Book name
  • Author name
  • Year published
  • Description

The last field is a paragraph or two about the book.

Add some book data from Dogwise, or another site. There’s lots of good stuff there.

Write a PHP test page to connect to the database.

Now duplicate the database and the test page on your hosting account.

Put the URL of the test page below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Create a dog Web site database

On your local machine, create a database called dogsites. It will have data on Web sites about dogs.

Make a user who can access the database.

Add a table to the database. Call it sites. Add fields for:

  • Site id (the primary key)
  • Site name
  • Home page URL
  • Comments

Add some data about dog sites.

Write a PHP test page to connect to the database.

Now duplicate the database and the test page on your hosting account.

Put the URL of the test page below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Dog movies home page and movie list

Create a version of the DogMovies site with a home page and a movies list. The home page should look something like this, but with your own graphics and colors:

Home page

Figure 1. Home page

Here’s the movie list page.

Movies

Figure 2. Movies

Clicking on the column headings sorts the data.

Add at least five movies to your database.

You can try my solution.

Upload your application to your hosting account. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Dog books home page and book list

Create a version of the DogBooks site with a home page, a book list, and a page describing each book. The home page should look something like this, but with your own graphics and colors:

Home page

Figure 1. Home page

Here’s the book list page:

Book list

Figure 2. Book list

Clicking on a column header sorts the pages.

Here’s part of a book description page:

Book description

Figure 3. Book description

You can try my solution.

Upload your application to your hosting account. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Dog sites home page and site list

Create a version of the DogSites site with a home page, and a site list. The home page should look something like this, but with your own graphics and colors:

Home page

Figure 1. Home page

Here’s the site list page:

Site list

Figure 2. Site list

You can try my solution.

Upload your application to your hosting account. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Administering the dog movie database

Add an administration section to the DogMovies Web site.

You can see my solution.

Enter the URL of your solution below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Administering the dog book database

Add an administration section to the DogBooks Web site.

You can see my solution.

Enter the URL of your solution below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Administering the dog site database

Add an administration section to the DogSites Web site.

You can see my solution.

Enter the URL of your solution below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Restricting access

Recall that DogToys has two parts to it:

DogToys

Figure 1. DogToys

One part of DogToys is for customers. That’s the blue area. It has pages for tasks that customers need to do. Like look at a list of products.

The other part of DogToys is for employees. That’s the green area. It has pages for tasks that employees need to do. Like add products and change prices.

But we don’t want just anyone messing with prices. This chapter shows you how to let different people access different pages, depending on what they are allowed to do.

You can try the secure version of the DogToys application. You have to look at the admin menu to see the changes (user names and passwords are hereboth are case-sensitive). As before, this version of DogToys won’t update product data.

You can download the files for the application. If you want to run the application on your own computer, you’ll need to change the database connection information.

The goal

Where are we?

We want to restrict who can see what pages on a site. Let’s make sure we understand what that means.

This lesson’s goals

Learn:

  • There two parts to restricting access: authentication and permissions.
  • Authentication is about knowing who the user is.
  • Permissions is about knowing what the user is allowed to do.
  • Create a database table with information about users, including their user names, passwords, and permissions.

Authentication and permissions

Here’s the situation:

Louise wants to change a price

Figure 1. Louise wants to change a price

Louise wants to change a product’s price. edit-product.php is the page that lets her do that.

We’ll add some code to DogToys, including edit-product.php. The new stuff needs to handle two things:

  • Authentication. Knowing which person is making the request. Is it Louise? Larry? Luna? Lenore?
  • Permissions. Is Louise allowed to change product data?

Authentication

We’ll give every person in the company a user name and a password. They’ll have to log in before they can use any of the administrative functions of DogToys (like edit prices, add products, and delete products).

The log in page will look like this:

Log in page

Figure 2. Log in page

The person types in his/her user name and password:

Louise logs in

Figure 3. Louise logs in

All of the admin pages are in a separate part of the site. They’re in the admin/ directory. There’s an admin menu, that shows all of the tasks authorized users are allowed to do.

Here is part of the admin menu, shown after the user logs in:

Admin menu

Figure 4. Admin menu

It shows the name of the logged in user. It has a link to log out. Click it, and the browser will jump back to the log in page.

Every admin page, like edit-product.php, will have some new PHP at the very beginning. It will make sure that someone is logged in:

if ( nobody is logged in )
  Jump to the log in page.
...

Figure 5. Log in check

If nobody is logged in, the browser will be sent to the log in page.

Permissions

So now we know who is logged in. But what is that user allowed to do?

Let’s add a table to the DogToys database.

users table

Figure 6. users table

We’ll actually use different field names later.

For each user, there’s a set of permissions. There’s a y if the user is allowed to do a task, like edit. If the user isn’t allowed, there’s an n.

Each page will check the permissions before running.

Let’s add to the security code in edit-product.php:

if ( nobody is logged in )
  Jump to log in page.
if ( edit permission is 'n' )
  Show "Permission denied" message.
  Stop.
... 

Figure 7. Log in and permissions check

What we need to do

We need to figure out how to:

  • Store data on user names, passwords, and permissions in the database.
  • Check this information on log in.
  • Check the user’s permissions when s/he visits a page.
  • Let the user log out.

You already know all the PHP you need, except for one thing: remembering who a user is once s/he has logged in. We’ll look at that in the next lesson.

Summary

  • There two parts to restricting access: authentication and permissions.
  • Authentication is about knowing who the user is.
  • Permissions is about knowing what the user is allowed to do.
  • Create a database table with information about users, including their user names, passwords, and permissions.

What now?

Let’s talk about PHP sessions. DogToys will use sessions to remember who has logged in.

PHP sessions

Where are we?

This chapter is about restricting access to pages on a Web site. We just talked about the goal: a workflow with log in, actions, and log out.

This page talks about PHP sessions. We’ll use use to create the workflow we want.

This lesson’s goals

Learn that:

  • Session memory is separate from any one page.
  • Pages can store data in session memory, and retrieve it.
  • Sessions can be destroyed by PHP pages. Sessions expire after a time of inactivity.

The goal

Louise works for DogToys, keeping product information up-to-date. Here’s one of Louise’s work sessions.

Louise's work session

Figure 1. Louise’s work session

Louise logs in. Then she does her work: adding products, editing products, whatever she needs to do. At the end of the session, she logs out.

Once she has logged in, all of the pages – edit-product.php, add-product.php, and so on – know who she is, and what permissions she has. She doesn’t have to log in again for each change.

This is the workflow we want. But…

The problem

Each PHP page is a program running on the server. When the program starts running, it asks the server’s operating system for some memory, where it can store variables.

Page memory

Figure 2. Page memory

Each time the code makes a new variable, a place is created for the variable in the page’s memory.

Here’s an important thing: when the page is finished, the page’s memory is erased. The server’s operating system grabs the memory back. So there’s nothing left of the variables. They’re all gone.

Every page has its own memory space:

Each page has some memory

Figure 3. Each page has some memory

All of the page memories are independent. Nothing is shared.

Remember we want to have a log in page, log-in.php. How does this page let the other pages know that Louise is allowed to use the system? log-in.php has to leave something behind to say, “Louise is OK.” But how?

That’s where PHP sessions come in. Sessions do a number of things, but the most important is that they create some memory on the server that exists separately from any one page:

Session memory

Figure 4. Session memory

log-in.php can put variables into the session memory. When the server erases log-in.php’s memory, the session memory will still be there.

Other pages can access the session memory. add-product.php can look in it, and see what log-in.php left behind.

This is how we can make authentication work. When a user logs in, we can put some data into the session memory. Other pages can check the session memory, to see what the user is allowed to do.

Each person who connects to DogToys gets their own session memory:

Separate sessions

Figure 5. Separate sessions

Actually, the server doesn’t create a separate session for each person, but for each browser that connects. Each browser will be running on a machine with a different IP address:

Sessions for IP addresses

Figure 6. Sessions for IP addresses

There’s a little more to it, but it’s close enough.

But we’ll continue to talk about user sessions, not IP sessions. It’s simpler.

Suppose each user in Figure 5 – Louise, Jim, Clara, and Joe – connects to log-in.php. When log-in.php runs, it uses separate session data for each user:

Separate sessions

Figure 7. log-in.php uses separate sessions

This means that each person’s log in data is kept separate.

Different users will run the same page, like log-in.php. But log-in.php will see different data for each user, because it is pulling data from each user’s own session memory.

Here’s an example. It isn’t accurate about how session data is stored; we’ll see that in a minute.

log-in.php uses different values for each user

Figure 8. log-in.php uses different values for each user

log-in.php outputs $p. What will it show? Because the data comes from session memory, each user would see something different.

  • Louise sees 17.
  • Jim sees 23.
  • Clara sees 29.
  • Joe sees 31.

So the same code – print $p; – could show something different when it is run by different users, if it is using data from session memory.

Let’s look at the PHP statements that mess with sessions. We’ll only look at the core statements.

PHP’s session statements

Starting a session

You need to start the session mechanism before you can use it.

session_start();

This statement has to appear before the page outputs anything, or sessions won’t work.

Storing session data

Once a session has been started, you can store data into it using the $_SESSION array. For example:

$_SESSION['user name'] = 'jimmy';

The string between the quotes names the session data. Other examples:

$_SESSION['favorite number'] = 96;
$_SESSION['favorite color'] = 'green';
$_SESSION['database server'] = 'localhost';

Retrieving session data

You can retrieve session data as well. Again, you have to start the session first.

$user name = $_SESSION['user name'];
$num = $_SESSION['favorite number'];
$best_color = $_SESSION['favorite color'];
$db_server_host = $_SESSION['database server'];

Destroying a session

Destroy a session like this:

session_start();
$_SESSION = array();
session_destroy();

The second line erases the session data. The third one wipes out the session itself.

An example

Let’s look at a sample application that uses session. The user enters his/her name:

User enters name

Figure 9. User enters name

The name is stored in the session.

The user then sees a menu, like this:

Menu

Figure 10. Menu

The user’s name is inserted into captions of photos. Here’s what clicking on the Renata link shows:

Renata

Figure 11. Renata

You can try the application, and download its files.

Here are the pages in the application:

Pages in the application

Figure 12. Pages in the application

Here are the pieces:

  • ask-user-name.html shows the form that gets the user’s name.
  • remember-name.php stores the user’s name in the session.
  • menu.php shows the application’s menu.
  • cc.php, kieran.php, and renata.php show photos with the user’s name in the captions.
  • new-name.php erases the user’s name from the session, and jumps back to the user name form.

Let’s look at the code.

Getting the user’s name

ask-user-name.html shows the form:

User enters name

Figure 9 (again). User enters name

The form passes the data to remember-name.php, which stores the data into the session.

Here’s the code for remember-name.php:

<?php
//Get the name the user typed.
$user_name = $_POST['user_name'];
//Validate.
if ( $user_name == '' ) {
  header('location:ask-user-name.html');
  exit();
}
//Start session.
session_start();
//Store data in session.
$_SESSION['user name'] = $user_name;
//Go to the main menu.
header('location:menu.php');
?>

Figure 13. remember-name.php

remember-name.php doesn’t output anything for the user to see. It puts data into the session, for other pages to use.

Line 3 gets the value the user typed into the form. Lines 5 to 8 check whether the value is empty. If it is, the browser is told to jump back to ask-user-name.html.

Now to store the name into the session. Line 10 starts the session:

session_start();

PHP pages need to do this before they can use sessions.

Line 12 stores the user name into the session:

$_SESSION['user name'] = $user_name;

Line 14 jumps to the menu:

header('location:menu.php');

The menu

Here’s the code for menu.php.

<?php
//Do we know the user's name?
session_start();
$user_name = $_SESSION['user name'];
if ( $user_name == '' ) {
  header('location:ask-user-name.html');
  exit();
}
?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Menu | Sessions Sample App</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <h1>Menu</h1>
    <p>Name: <?php print $user_name; ?></p>
    <ul>
      <li><a href="cc.php">CC photo</a></li>
      <li><a href="kieran.php">Kieran photo</a></li>
      <li><a href="renata.php">Renata photo</a></li>
      <li><a href="new-name.php">Use a different name</a></li>
    </ul>
  </body>
</html>

Figure 14. menu.php

Line 3 starts the session.

Line 4 gets some data from the session:

$user_name = $_SESSION['user name'];

Lines 5 to 8 checks that there is a name. If not, the browser is sent to ask-user-name.html.

Renata
Renata

How could $_SESSION['user name'] be empty? It was just set in remember-name.php, wasn’t it?

Kieran
Kieran

You’re right, remember-name.php sets
$_SESSION['user name'], and then it jumps to menu.php.

But that’s only one way the user can get to menu.php. There are other ways. For example, the user can type the page’s URL directly into the browser:

User types menu.php into the browser

Figure 15. User types menu.php into the browser

This skips remember-name.php, so the session data isn’t set.

It’s best not to assume that the session data is there.

The menu shows the user name:

Menu

Figure 10 (again). Menu

Here’s line 17 from menu.php:

<p>Name: <?php print $user_name; ?></p>

Showing a photo

Here’s renata.php, that shows the user’s name in a photo caption:

<?php
//Do we know the user's name?
session_start();
$user_name = $_SESSION['user name'];
if ( $user_name == '' ) {
  header('location:ask-user-name.html');
  exit();
}
?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Renata Photo | Sessions Sample App</title>
  </head>
  <body>
    <div id="container">
      <p><img src="renata.jpg" alt="CC"></p>
      <h1 class="loves">Renata loves <?php print $user_name; ?></h1>
    </div>
    <p><a href="menu.php">< Menu</a></p>
  </body>
</html>

Figure 16. renata.php

Line 3 starts the session. Line 4 gets the user’s name from the session:

$user_name = $_SESSION['user name'];

Lines 5 to 8 check whether there is a user name. If not, jump to ask-user-name.html.

Line 18 puts the user name into the photo caption:

<h1 class="loves">Renata loves <?php print $user_name; ?></h1>

Changing the user name

What if the user wants to change the name that shows in the captions? There’s a link for that in the main menu.

Menu

Figure 10 (again). Menu

The last link in the menu sends the browser to new-name.php. Here is the code:

<?php
session_start();
//Erase the session variables.
$_SESSION = array();
//Kill the session.
session_destroy();
header('location:ask-user-name.html');
?>

Figure 17. new-name.php

new-name.php erases the session data (line 4) and kills the session itself (line 6). Then it sends to browser back to the user name form.

Again, new-name.php doesn’t output anything itself. It does something to the session, and jumps to another page.

Session lifetime

You might be asking yourself:

Self, do sessions last forever?

Sessions are destroyed when:

  • A PHP program runs session_destroy();
  • The browser is closed.
  • The session expires.

Sessions expire if the browser does not access a page in the application within a certain amount of time. You can set the amount of time sessions last. The default is 20 minutes.

Exercise: Event list

The Dog Center is a meeting center that holds lectures, demonstrations, workshops, and other events. About dogs, of course.

The Center’s management want to put information kiosks at the doors. They’ll be PCs with touch screens, running a Web browser. People can use the kiosks to learn about the events at the Center that day.

Here’s what the kiosk’s screen will look like for a typical day:

Event list

Figure 1. Event list

Clicking on a link shows an event:

Event

Figure 2. Event

There’s a joke at the bottom of every page. When a kiosk is started for the day, it asks the user for a joke:

Asking for a joke

Figure 3. Asking for a joke

The joke is stored in the session. It’s then shown on every page.

Management wants to be able to change the joke if need be. Add a link to the menu to do that. But the link should be hidden on the lower right of the page. The mouse cursor should change when passing over the link, but the link itself should not show:

Secret link

Figure 4. Secret link

When the link is clicked, the browser asks for a joke again, as in Figure 3.

You can try my solution. You can also download the files, but do it yourself first!

Hint: what happens when color and background-color are the same?

Upload your solution to your hosting account. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • Session memory is separate from any one page.
  • Pages can store data in session memory, and retrieve it.
  • Sessions can be destroyed by PHP pages. Sessions expire after a time of inactivity.

What now?

This chapter is about restricting access to pages on a Web site. You just saw how sessions let PHP pages share information. Now let’s talk about storing data about users and their permissions.

Storing user data

Where are we?

You’ve seen the workflow we want in DogToys. You’ve seen how sessions work.

We’ll need some way to remember user names and passwords, and the permissions that each user has. Let’s adding user data to the DogToys database.

This lesson’s goals

Learn:

  • Create a users table in the database. It will have user names, passwords, and permission flags.
  • Good passwords have lowercase letters, uppercase letters, digits, and special characters. They don’t correspond to a dictionary word.

The users table

Let’s add a table called users to the DogToys database. It will have the data we need for authentication and permissions. Here are the fields:

users table

Figure 1. users table

user_id is the primary key, an unsigned integer. auto_increment means that MySQL will automatically supply values when new records are added to the users table.

user_name is, er, the user name. It can be up to 20 characters long.

password is the user’s password. Like user_name, it can be up to 20 characters long.

We’re storing the password in clear text. You wouldn’t do that in a secure application.

permission_add is a one-character field that is either y or n. If it’s y, the user has permission to add products.

permission_edit is a one-character field as well. If it’s y, the user can change data about existing products.

permission_delete controls whether users are allowed to delete product records.

Sample data

Here’s some data. I broke the image into pieces so it would fit on this page.

Users data

Figure 2. Users data

You can see that Kieran and Louise are allowed to do anything to the data. But Renata can only edit data. She can’t add products or delete them.

Look at the passwords. Renata’s password is terrible. If a hacker learned Renata’s user name, one of the first things s/he would try is using the same thing for the password.

Loiuse’s password isn’t very good either. The characters are all the same.

CC’s password is bad as well. It’s a single word. It’s vulnerable to “dictionary” attacks, where a program tries to log in using English words as passwords. Eventually, it would hit “sheltie.” The word is in dictionary.com.

Kieran’s password is the only good one. It uses four different character types:

  • Lowercase letters
  • Uppercase letters
  • Digits
  • Special characters

It’s not vulnerable to dictionary attacks, and is hard to guess.

Maintaining user data

We need to be able to:

  • Add new user records
  • Let users change their passwords
  • Delete records when people leave the company
  • Change permissions when people change jobs

In a real Web application, we’d write Web pages for these tasks. We’d assign someone as an administrator. We’d add a permissions field that would let an administrator access those the pages that change user data.

To keep things simple, we don’t do that in this chapter. It’s not core.

You can change the user data with phpMyAdmin.

Exercise: DogJokes users table

Add a users table to the DogJokes database you created in the previous chapter. Use the same fields I used for the DogToys users table. Add some records.

Can't find the 'comment' module! Was it selected?

Summary

  • Create a users table in the database. It will have user names, passwords, and permission flags.
  • Good passwords have lowercase letters, uppercase letters, digits, and special characters. They don’t correspond to a dictionary word.

What now?

Now let’s see how you use the table in the log in process.

Logging in

Where are we?

This chapter is about restricting access to pages on a Web site.

You know how sessions let PHP pages share information. You’ve seen how we can store data about users and their permissions in a database table.

Now let’s create the log in system that uses that data.

This lesson’s goals

You will learn that:

  • The log in form gets a user name and password from the user.
  • It sends the data to a page that checks whether the user name and password is in the database table users.
  • If the user name and password are found, permission information is stored in the session.

Overview

We want the user to see a log in form, like this:

Louise logs in

Figure 1. Louise logs in

If the user name and password match a record in the database, then the user would see the admin menu.

Admin menu

Figure 2. Admin menu

Here are the pages we’ll create:

Pages involved in log in

Figure 3. Pages involved in log in

log-in.php shows the form. The user completes it, and clicks the submit button. log-in.php passes the data to check-log-in.php. check-log-in.php checks the database for a matching record in the users table. If it finds one, it stores some data in the session, and sends the browser to the admin menu page, admin/index.php. If there is no matching record, the browser is sent back to the log in form.

Let’s look at the code.

log-in.php

Here’s the log in form again:

Louise logs in

Figure 1 (again). Louise logs in

The form does some error checking. There are client-side checks for empty fields:

Empty field errors

Figure 4. Empty field errors

But what if the user fills in both fields with an unknown user name and password? This check cannot be done on the client-side. A server-side program has to look up the user name and password before finding that the user name/password combination is unknown.

Here’s the error reporting for a unknown user name/password combination:

Unknown user name/password error

Figure 5. Unknown user name/password error

So the page needs to:

  • Check for missing data on the client side.
  • Report errors found on the server side.

The page will also have to:

  • Skip the log in process if the user is already logged in.
  • Implement the template system.

That’s quite a lot of stuff for log-in.php to do!

Here’s the complete code. We’ll go over all the pieces.

<?php
//Show the admin menu
//Input: order(optional). Sort order for article list. GET.

//Path from this page to the site root.
$path_to_root = '..';
//Already logged in?
session_start();
if ( $_SESSION['logged in'] == 'y' ) {
  //Yes - jump to admin menu.
  header("location:$path_to_root/admin/index.php");
  exit();
}
//Title of this page.
$page_title = 'DogToys Log In';
require $path_to_root . '/library/start_page.inc';
require $path_to_root . '/library/header.inc';
require $path_to_root . '/library/left_nav.inc';
?>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="<?php print $path_to_root;?>/library/show-error-messages.js"></script>
<script type="text/javascript">
  $(document).ready(function() {
    //Initialization.
    $("#global_error_message_container").hide();
    $("#user_name").focus();
    <?php
      if ( $_GET['err'] == 'auth' ) {
        print("show_global_error_message('Sorry, user name or password not found.');");
      }
    ?>
    //Form submit event.
    $("#log_in_form").submit(function() {
      //Set flag showing everything is OK.
      var data_ok = true;

      //Check the user name field.
      var user_name = $("#user_name").val();
      if ( user_name == '' ) {
        data_ok = false;
        show_field_error_message('Sorry, you must enter a user name.', 'user_name');
        show_global_error_message("Sorry, I can't log you in.");
      }
      else {
        hide_error_message('user_name');
      }

      //Check the password field.
      var user_password = $("#user_password").val();
      if ( user_password == '' ) {
        data_ok = false;
        show_field_error_message('Sorry, you must enter a password.', 'user_password');
        show_global_error_message("Sorry, I can't log you in.");
      }
      else {
        hide_error_message('user_password');
      }

      return data_ok;
    });
  });
</script>

<div id="center_region">
  <h1><?php print $page_title; ?></h1>
  <p id="global_error_message_container" class="message_container">
    <img src="<?php print $path_to_root; ?>/library/error.png" alt="Error">
    <span id="global_error_message"/>
  </p>
  <form id="log_in_form" method="post" action="check-log-in.php">
    <p>
      User name<br>
      <input type="text" id="user_name" name="user_name" size="20"><br>
      <span id="user_name_message_container" class="message_container">
        <img src="<?php print $path_to_root; ?>/library/error.png" alt="Error">
        <span id="user_name_message"/>
      </span>
    </p>
    <p>
      Password<br>
      <input type="password" id="user_password" name="user_password" size="20"><br>
      <span id="user_password_message_container" class="message_container">
        <img src="<?php print $path_to_root; ?>/library/error.png" alt="Error">
        <span id="user_password_message"/>
      </span>
    </p>
    <p>
      <button type="submit">Log in</button>
    </p>
  </form>
</div>
<?php
require $path_to_root . '/library/footer.inc';
require $path_to_root . '/library/end_page.inc';
?>

Figure 6. log-in.php

Line 8 starts the session.

Lines 9 to 13 check whether the user has already logged in. This…

$_SESSION['logged in']

...looks for the session variable logged in. If it has the value y, the user is already logged in. The browser is sent to the admin menu immediately, skipping the log in process.

Lines 14 to 18 are part of the templating system.

Lines 20 to 62 mostly implement client-side error checking. But lines 27 to 31 are new. They show errors from the server.

Here are the pages again:

Pages involved in log in

Figure 3 (again). Pages involved in log in

check-log-in.php is the page that finds whether the user name and password are valid. If they aren’t, is sends something to log-in.php, to tell it to show an error message. But what should it send, and how?

Here’s one way to do it. When check-log-in.php finds an error, it tells the browser to jump to log-in.php. It attaches something to the URL. Here is some code from check-log-in.php :

header("location:$path_to_root/admin/log-in.php?err=auth");

See the err=auth bit? This tells log-in.php to show an authentication error. Here’s how the URL looks in a browser’s address bar:

Error flag in URL

Figure 7. Error flag in URL

When log-in.php receives this, it can show the error message.

We already have some JavaScript code in log-in.php for showing errors. It uses colors, shows an icon, and other Good Stuff. Let’s use that existing JavaScript code to report the server error.

Here’s some code from the JavaScript part of log-in.php.

$(document).ready(function() {
  //Initialization.
  $("#global_error_message_container").hide();
  $("#user_name").focus();
  <?php
    if ( $_GET['err'] == 'auth' ) {
      print("show_global_error_message('Sorry, user name or password not found.');");
    }
  ?>

Figure 8. Part of log-in.php

The JavaScript function show_global_error_message() shows an error message at the top of the page. Line 29 outputs JavaScript that would call that function:

show_global_error_message('Sorry, user name or password not found.');

The PHP in line 28 adds this JavaScript if log-in.php has err passed to it, with the value auth. Recall that GET attaches stuff to the URL.

So, if the server program check-log-in.php can’t find the user name and password in the users database table, it jumps back to log-in.php, with err set to auth.

If log-in.php gets err set to auth, it calls some JavaScript to show an error message. This is PHP writing JavaScript. Again.

The rest of log-in.php should be familiar. But there’s a new thing in line 81:

<input type="password" id="user_password" name="user_password" size="20">

An <input> field with a type of password is almost the same as one with a type of text. The only difference is that the browser doesn’t show what the user types into a password field:

Password field

Figure 9. Password field

check-log-in.php

Here are the pages again:

Pages involved in log in

Figure 3 (again). Pages involved in log in

check-log-in.php gets a user name and password from log-in.php. It looks them up in the database. If they’re found, it sets some session data, and jumps to the admin menu. If the user name and password are not found, check-log-in.php jumps back to log-in.php.

Here is the code:

<?php
//Path from this page to the site root.
$path_to_root = '..';
//Get the values the user typed.
$user_name = $_POST['user_name'];
$user_password = $_POST['user_password'];
//Validate
if ( $user_name == '' || $user_password == '') {
  header("location:$path_to_root/admin/log-in.php");
  exit();
}
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($db_host, $db_user_name, $db_password, $db_name);
if ( mysqli_connect_error() ) {
  print '<p>Error! Could not connect to the database. ';
  print 'Error message: '.mysqli_connect_error().'</p>';
  exit();
}
//Fetch article data.
$query = "select permission_add, permission_edit, permission_delete
    from users
    where binary user_name = '$user_name'
        and binary user_password = '$user_password'";
$record_set = $db->query($query);
if ( $record_set->num_rows == 1 ) {
  session_start();
  $_SESSION['logged in'] = 'y';
  $_SESSION['user name'] = $user_name;
  //Store permissions in session.
  $row = $record_set->fetch_assoc();
  $_SESSION['permission add'] = $row['permission_add'];
  $_SESSION['permission edit'] = $row['permission_edit'];
  $_SESSION['permission delete'] = $row['permission_delete'];
  //Jump to admin menu.
  header("location:$path_to_root/admin/index.php");
  exit();
}
//Authentication problem.
header("location:$path_to_root/admin/log-in.php?err=auth");
exit();
?>

Figure 10. check-log-in.php

Lines 5 and 6 get the data sent to the page:

$user_name = $_POST['user_name'];
$user_password = $_POST['user_password'];

Lines 8 to 11 make sure that the data is there:

if ( $user_name '' || $user_password '') {
   header("location:$path_to_root/admin/log-in.php");
   exit();
}

If it isn’t, the browser is sent back to log-in.php. This should never happen, of course, because of the client-side validation in log-in.php. These lines are an extra security check.

Now we need to check the database, and see if the user name and password are valid. Lines 13 to 19 connect to the database.

Lines 21 to 24 create the SQL query that will look up the user name and password:

select permission_add, permission_edit, permission_delete
from users
where binary user_name = '$user_name'
and binary user_password = '$user_password'

Two things to notice. The first is the and. The select will only return records where both conditions are true.

The second thing is the binary keyword. MySQL tests are usually not case-sensitive. So louise, Louise, and lOUise are all the same.

But we don’t want that here. Applications are more secure if user names and passwords as case-sensitive, so louise and Louise are not the same. Adding the binary keyword makes the tests case-sensitive.

Line 25 is:

$record_set = $db->query($query);

It tells MySQL to run the query, returning a record set.

If the user name and password are valid, the record set should return exactly one record. That gets tested in the next piece of code.

if ( $record_set->num_rows == 1 ) {
  session_start();
  $_SESSION['logged in'] = 'y';
  $_SESSION['user name'] = $user_name;
  //Store permissions in session.
  $row = $record_set->fetch_assoc();
  $_SESSION['permission add'] = $row['permission_add'];
  $_SESSION['permission edit'] = $row['permission_edit'];
  $_SESSION['permission delete'] = $row['permission_delete'];
  //Jump to admin menu.
  header("location:$path_to_root/admin/index.php");
  exit();
}

Figure 11. Part of check-log-in.php

The code…

$record_set->num_rows

...returns the number of rows in the record set. If one record was returned, the user name and password is valid. In that case, we want to store stuff in the session.

This line…

$_SESSION['logged in'] = 'y';

...sets logged in to y. This lets other pages easily test whether there is a logged in user. We’ll see that later.

This line…

$_SESSION['user name'] = $user_name;

...stores the user name passed into check-log-in.php in the session.

Line 31 fetches the row in the record set. Remember that we already know there is only one row.

Lines 32 to 34 get the values of the three permissions fields. They are stored in the session.

Line 36 jumps to the admin menu.

Here’s what happens if the user name/password combination is not found:

if ( $record_set->num_rows == 1 ) {
  ...
  exit();
}
//Authentication problem.
header("location:$path_to_root/admin/log-in.php?err=auth");

Figure 12. Another part of check-log-in.php

The browser is sent back to log-in.php, with a flag showing that log-in.php should show an error message.

Summary

  • The log in form gets a user name and password from the user.
  • It sends the data to a page that checks whether the user name and password is in the database table users.
  • If the user name and password are found, permission information is stored in the session.

What now?

Now we have a log in system that leaves data in the session. Let’s see how other pages use that data for security.

Checking permissions

Where are we?

We want to restrict access to the administration section of a Web site. We have some log in code that checks for a valid user name and password. If it finds one, it puts data into the session.

Let’s see how we can use that session data to restrict access to pages.

This lesson’s goals

Learn:

  • Every admin page checks the log in flag in the session. You can put the code in a separate file, and use the require statement to insert it.
  • Admin pages can check permission data in the session.
  • Use permission data from the session to change the admin interface. Don’t show users actions they’re not allowed to do.

Using session data

Here’s Louise’s workflow again:

Louise's workflow

Figure 1. Louise’s workflow

The figure omits pages Louise doesn’t see, like check-log-in.php.

We saw the log in process in the previous lesson. It stores session data about the user, like this:

$_SESSION['logged in'] = 'y';
$_SESSION['user name'] = $user_name;
...
$_SESSION['permission add'] = $row['permission_add']; $_SESSION['permission edit'] = $row['permission_edit'];
$_SESSION['permission delete'] = $row['permission_delete'];

Figure 2. Storing log in data into the session

Pages like index.php, add-product.php, and edit-product.php will use the session data to check:

  • Whether the user is logged in.
  • What permissions the user has.

Let’s see how that works.

Is the user logged in?

During log in, check-log-in.php does this if the user gives a valid user name and password:

$_SESSION['logged in'] = 'y';

Other pages, like add-product.php, can use this to check whether the user is logged in. Here’s some code.

<?php
//Start session mechanism.
session_start();
//User logged in?
if ( $_SESSION['logged in'] != 'y' ) {
  //No - jump to log in page.
  header("location:$path_to_root/admin/log-in.php");
  exit();
}
?>

Figure 3. Log in check

Line 3 starts the session. Line 5 checks the variable $_SESSION['logged in']. Recall that != means “not equal to.” So if $_SESSION['logged in'] has anything other than y, the browser is sent back to the log in page.

W00f!

This check needs to be done on every page of the site’s admin section. We could copy-and-paste the code everywhere, but there’s a better way. We’ll put the code in Figure 3 in the file library/restrict.php. Then we’ll use the require statement to insert it into all the admin pages. Here’s how add-product.php starts:

<?php
//Get data from user for a new product.
//Input:
//  None.

//Path from this page to the site root.
$path_to_root = '..';
//Security check
require $path_to_root . '/library/restrict.php';

Figure 4. Code from add-product.php

Line 9 loads the file. That’s all add-product.php needs to do.

Checking permissions

We can make an admin page check whether a user is logged in. But what about checking individual permissions?

There are two parts to this:

  • Stopping the user from doing things s/he is not allowed to do.
  • Changing the interface, so that links to things the user is not allowed to do don’t even show up.

Stopping the user

Remember this, from check-log-in.php:

$_SESSION['permission add'] = $row['permission_add'];

Admin pages can check $_SESSION['permission add'], to see whether the user is allowed to add products.

Here’s code from add-products.php:

<?php
...
//Security check
require $path_to_root . '/library/restrict.php';
if ( $_SESSION['permission add'] != 'y' ) {
  //Exit if user doesn't have add permission.
  header("location:$path_to_root/admin/index.php");
  exit();
}

Figure 5. More code from add-product.php

Line 4 checks whether the user is logged in, as we saw above.

Line 5 checks whether the user has permission to add products. If not, the browser is sent to the admin menu.

Here’s code from delete-product.php.

<?php
...
//Security check
require $path_to_root . '/library/restrict.php';
if ( $_SESSION['permission delete'] != 'y' ) {
  //Exit if user doesn't have delete permission.
  header("location:$path_to_root/admin/index.php");
  exit();
}

Figure 6. Code from delete-product.php

It’s the same, except that it checks a different permission.

Changing the interface

The admin interface should change, depending on what permissions the user has. Here’s Kieran’s admin menu:

Kieran's admin menu

Figure 7. Kieran’s admin menu

Because Kieran has permission to add, edit, and delete products, links for all of those tasks appear in the menu.

Here’s Renata’s admin menu:

Renata's admin menu

Figure 8. Renata’s admin menu

Renata does not have permission to add or delete products. Only to edit products. So only edit links show up in the interface.

How to make this happen? Let’s start with the “Add a new product” link. We want that to appear only for users who have permission to add new products. Here’s code from the admin menu:

<p>User: <?php print $_SESSION['user name']; ?></p>
<p>What do you want to do?</p>
<ul>
  <li><a href="log-out.php">Log out</a></li>
  <?php
  if ( $_SESSION['permission add'] == 'y' ) {
    ?>
      <li><a href="add-product.php">Add a new product</a></li>
    <?php
  }
  ?>

Figure 9. Code from admin/index.php

Line 1 shows the user name. You can see it on the page:

Kieran's admin menu

Figure 7 (again). Kieran’s admin menu

Line 4 shows a link to the log out page. We’ll look at that in the next lesson.

The next lines are:

if ( $_SESSION['permission add'] == 'y' ) {
   ?>
   <li><a href="add-product.php">Add a new product</a></li>
   <?php
}

The link to add-product.php is output to the page only if the user has permission to add pages.

What about the edit and delete links?

Kieran's admin menu

Figure 7 (again). Kieran’s admin menu

They’re done the same way.

print "
...
<td>
";
if ( $_SESSION['permission edit'] == 'y' ) {
  print "<a href='edit-product.php?id=$product_id'>Edit</a><br>";
}
if ( $_SESSION['permission delete'] == 'y' ) {
  print "<a href='confirm-delete-product.php?id=$product_id'>Delete</a>";
}
print "
</td>
...";

Figure 10. More code from admin/index.php

The code that outputs the links is wrapped in if statements that check permissions.

Summary

  • Every admin page checks the log in flag in the session. You can put the code in a separate file, and use the require statement to insert it.
  • Admin pages can check permission data in the session.
  • Use permission data from the session to change the admin interface. Don’t show users actions they’re not allowed to do.

What now?

You know how to make log in pages store security data in the session. You know how to use that data to restrict what users can do.

Now let’s see how users can log out.

Logging out

Where are we?

This chapter is about restricting access to the admin section of a Web site.

You know how to add a log in page, store log in data in sessions, and check it in every admin page.

The only thing left to do is let the user log out.

This lesson’s goals

Learn:

  • How to add a log out link on the admin menu.
  • The log out page gets rid of the session data.

The admin menu (admin/index.php) has a log out link. The user clicks it to log out:

Log out link

Figure 1. Log out link

Here’s the HTML that makes the link:

<a href="log-out.php">Log out</a>

Just a simple link. But that does the log out page do?

Logging out

The code we wrote to restrict access uses session data. The log out code gets rid of it.

Here’s the code for log-out.php.

<?php
//Log out
//Path from this page to the site root.
$path_to_root = '..';
session_start();
//Kill all the the session variables.
$_SESSION = array();
//Kill the session itself.
session_destroy();
//Back to the log in page.
header("location:$path_to_root/admin/log-in.php");
exit();
?>

Figure 2. log-out.php

Line 7 erases the session data, including the log in flag and all permissions information. Line 9 destroys the session itself. Line 11 jumps back to the log in page.

That’s it!

W00f!

Renata
Renata

The erases all of the session data, right?

Kieran
Kieran

Yes.

Renata
Renata

Is there session data you might not want to erase?

Kieran
Kieran

Ooo, good question!

You can use the session to store data about anything. Like the winner of the Tokyo dog show.

In the code we’ve been looking at, we only used the session to store information about log in and permission. So erasing it all makes sense.

But if you’re using the session to store other information as well, you might want to just erase the log in and permission stuff, like this:

$_SESSION['logged in'] = '';
$_SESSION['permission add'] = '';
$_SESSION['permission edit'] = '';
$_SESSION['permission delete'] = '';

Summary

  • There’s a log out link on the admin menu.
  • The log out page gets rid of the session data.

What now?

Now for some exercises.

Exercises: Restricting access

Looking ahead