This tutorial will walk you through the creation of a simple web application in php, that accesses a postgresql database. The tutorial focuses on simple ways to implement the application; keep in mind that simplest is not always best, but in this tutorial we focus on simple.
To get the most out of the tutorial, don't just read it. Execute the programs, use the 'view source' option on your browser to see the html that the php pages are generating, and change the programs to make sure you understand them.
So, what is the application ? We will create a database of users, where each user can have many emails (hey, we said the app would be simple :). Accordingly, we have two tables, with the following structure (the file emails_db.sql also inserts a few tuples on each table):
1:
CREATE TABLE MyUser (
2:
userName VARCHAR(12) PRIMARY KEY,
3:
password VARCHAR(20) NOT NULL,
4:
fullName VARCHAR(30) NOT NULL,
5:
isAdmin boolean NOT NULL DEFAULT False -- postgreSql specific datatype
6:
);
7:
8:
CREATE TABLE UserEmails (
9:
userName VARCHAR(12) NOT NULL REFERENCES MyUser(userName),
10:
email VARCHAR(30) NOT NULL,
11:
CONSTRAINT UserEmails_PK PRIMARY KEY (userName,email)
12:
);
And our application will let us display all users, display the emails for each user, and add users and emails.
It is usually a good idea to create a template for your web pages; this way, you can just 'save as' and you ensure consistency and save some work. I tend to create a file called empty.html and an empty.php, with the templates.
Here is the empty.html file. Notice that it references a css stylesheet. This lets us
1:
<html>
2:
<head>
3:
<title> Forms </title>
4:
<link rel="stylesheet" href="mystyle.css">
5:
</style>
6:
</head>
7:
<body>
8:
9:
10:
</body>
11:
</html>
Now, for the php template, we probably want to include the code for connecting to the database, since in most pages we will be connecting to the db (hey, this is a database application); we could just put the call to pg_connect, but if we ever need to change the password (or the database or user, for that matter) then we would need to change ALL of the php pages, so we better put the code to connect in one file and include it in all the others. We can use the include function (actually, include_once or require once would probably be safer). So we create a file called connect.php that contains the following code:
1:
<?php
2:
$conn=pg_connect("host=127.0.0.1 user=emails dbname=emails password=somepwd");
3:
?>
And then our php template (called empty.html) would look like this:
1:
<?php include ('connect.php');?>
2:
<html>
3:
<head>
4:
<title> Forms </title>
5:
<link rel="stylesheet" href="mystyle.css">
6:
</style>
7:
</head>
8:
<body>
9:
10:
<?php
11:
12:
?>
13:
14:
</body>
15:
</html>
We can start by creating the page to list all users. The beginning is the same as in our template; we include connect.php and put all the html boilerplate. In the php code, we start by defining a function; the function is called display_users and takes as argument the handle returned by pg_query. As with most languages, this is just the definition of the function, nothing is done until the function is called.
The actual execution starts in line 22. First, we define a string variable, containing the query we will execute. Notice there is nothing magic about it yet; it is just a string variable. It's value is the string representation of the SQL command we want to execute, but as far as php is concerned, this is just a string.
Then, we call pg_query. We pass it the string stored in $query, so that is the query sent to the database (remember we had already established the connection, in the file we include at the top), and we store the returned value in the variable $result.
Notice pg_query returns a handle (basically an integer identifying a particular resource); this is an opaque reference to the result set of the query; we pass this value to the other pg functions (pg_num_rows and pg_fetch_object etc)
Now we pass the value returned by pg_query into the display_users function. This function displays the elements in the result set as a table. (Now would be a good time to execute the listUsers0.php program, and make sure you understand the code and the html that is being generated)
1:
<?php include ('connect.php'); ?>
2:
<html>
3:
<head>
4:
<title> List of Users</title>
5:
<link rel="stylesheet" href="mystyle.css">
6:
</style>
7:
</head>
8:
<body>
9:
<h1>List of users</h1>
10:
<?php
11:
function display_users($result)
12:
{
13:
print "<table>\n";
14:
print " <tr><th>Username</th><th>Name</th></tr>\n";
15:
for($i=0; $i<pg_num_rows($result); ++$i) {
16:
$row=pg_fetch_object($result,$i);
17:
print " <tr><td>$row->username</td><td>$row->fullname</td></tr>\n";
18:
}
19:
print "</table>\n";
20:
}
21:
22:
$query=" SELECT userName, fullName
23:
FROM MyUser";
24:
$result=pg_query($query);
25:
display_users($result);
26:
?>
27:
28:
</body>
29:
</html>
Now, our html pages reference a css stylesheet (mystyle.css). A stylesheet lets us change the way the browser will display our html; we can change font and background colors, add borders etc. The stylesheet we have is:
1:
h1 {text-align: center;}
2:
3:
table {border: 2px solid black;}
4:
.odd {background: #b0d0ff;}
5:
.even {background: #ffffff;}
6:
The first line says that anything inside an h1 tag should be centered (we normally use h1 for the title at the top).The third line says that tables should have a solid border around them, 2 pixels wide and in black color.
The next two lines (4 and 5) are slightly more complicated; line 4 says that anything inside a tag of class odd should have a bluish background color; line 5 says anything of class even should have a white background; We can use this trick to make our tables alternate colors; we simply add a class="odd" or class="even" to the <tr> tags.
The file listUsers.php does just that. Notice that the only thing we changed is the display_users function (that's how functions make our lives easier; we know where to go make changes !).
1:
<?php include ('connect.php'); ?>
2:
<html>
3:
<head>
4:
<title> List of Users</title>
5:
<link rel="stylesheet" href="mystyle.css">
6:
</style>
7:
</head>
8:
<body>
9:
<h1>List of users</h1>
10:
<?php
11:
function display_users($result)
12:
{
13:
print "<table>\n";
14:
print " <tr><th>Username</th><th>Name</th></tr>\n";
15:
for($i=0; $i<pg_num_rows($result); ++$i) {
16:
$row=pg_fetch_object($result,$i);
17:
if($i%2==0)
18:
$class="even";
19:
else
20:
$class="odd";
21:
print " <tr class=\"$class\"><td>$row->username</td><td>$row->fullname</td></tr>\n";
22:
}
23:
print "</table>";
24:
}
25:
26:
27:
$query=" SELECT userName, fullName
28:
FROM MyUser";
29:
$result=pg_query($query);
30:
display_users($result);
31:
?>
32:
33:
</body>
34:
</html>
The next step is to display a user with all its emails. This entails two queries (one to get the info from the MyUser table and another from the UserEmails table). Now this page needs to know who to display the info about, so we need to pass it the username of the desired user.
The simplest way to pass info to a php page is to use an html form; we just need to make sure to point to the right php page in the action parameter, and create fields with the right name. So, a very simple form could look like this:
1:
<html>
2:
<head>
3:
<title> Display user </title>
4:
<link rel="stylesheet" href="mystyle.css">
5:
</style>
6:
</head>
7:
<body>
8:
<h1>List user</h1>
9:
<form action="displayUser.php">
10:
<p>Username: <input type="text" name="username"></p>
11:
<input type="submit" value="list user">
12:
</form>
13:
14:
</body>
15:
</html>
And then the php for it could be:
1:
<?php include ('connect.php'); ?>
2:
<html>
3:
<head>
4:
<title> Forms </title>
5:
<link rel="stylesheet" href="mystyle.css">
6:
</style>
7:
</head>
8:
<body>
9:
<?php
10:
function display_emails($result)
11:
{
12:
print "<table>\n";
13:
print " <tr><th>Email</th></tr>\n";
14:
for($i=0; $i<pg_num_rows($result); ++$i) {
15:
$row=pg_fetch_object($result,$i);
16:
if($i%2==0)
17:
$class="even";
18:
else
19:
$class="odd";
20:
print " <tr class=\"$class\"><td>$row->email</td></tr>\n";
21:
}
22:
print "</table>";
23:
}
24:
25:
$username=$_REQUEST['username'];
26:
$q1="SELECT fullName
27:
FROM MyUser WHERE userName=$1";
28:
$r1=pg_query_params($q1,array($username));
29:
$row=pg_fetch_object($r1,0);
30:
print "<h1>$row->fullname</h1>";
31:
$q2="SELECT email FROM userEmails WHERE username=$1";
32:
$r2=pg_query_params($q2,array($username));
33:
display_emails($r2);
34:
?>
35:
36:
</body>
37:
</html>
In this page we first (line 10) define a function to display emails, similar to the one in listUsers; the actual execution starts in line 25; we get a value from the request (notice we do $_REQUEST['username'] , here username is the name of the field on the form), and store it in the variable $username. In line 26, we use that value to create a SQL query that will get, from the MyUser table, the name corresponding to that username. Line 28 actually sends that query to the database. Line 29 fetches the resulting row into an objetc (notice exactly one row should be returned) and line 30 prints it. Line 31 creates another SQL query, this time to get all the emails from the UserEmails table that correspond to the username. Line 32 executes that query, and line 33 calls the displayEmails function, that creates the html table etc
Now this page works, but only if it gets the right data; it is not checking whether the user actually exists etc; in fact, we are not even checking whether we are getting any data from the form !
Also, since the page uses data from the form directly into the query, a malicious user may enter invalid data that closes the ', adds a semicolon, and then another SQL command that changes your database, or reveals more info than it should (as a challenge, you may try to actually create such a string and type it into the form). In Security parlance, this is called a SQL injection attack. An easy way to avoid most of such attacks is to remove the ' character from the string. php has a function called addslashes that does just that (actually, it replaces the ' character with \', which guarantees that it enters your db as data instead of being a metacharacter). An even better approach, which we're doing in all our examples, is to use prepared statements (pg_query_params) to do all the queries involving user input.
So a better implementation would look like:
1:
<?php include ('connect.php'); ?>
2:
<html>
3:
<head>
4:
<title> Forms </title>
5:
<link rel="stylesheet" href="mystyle.css">
6:
</style>
7:
</head>
8:
<body>
9:
<?php
10:
function display_emails($result)
11:
{
12:
print "<table>\n";
13:
print " <tr><th>Email</th></tr>\n";
14:
for($i=0; $i<pg_num_rows($result); ++$i) {
15:
$row=pg_fetch_object($result,$i);
16:
if($i%2==0)
17:
$class="even";
18:
else
19:
$class="odd";
20:
print " <tr class=\"$class\"><td>$row->email</td></tr>\n";
21:
}
22:
print "</table>";
23:
}
24:
25:
if(isset($_REQUEST['username'])) {
26:
$username=addslashes($_REQUEST['username']);
27:
$q1="SELECT fullName
28:
FROM MyUser WHERE userName=$1";
29:
$r1=pg_query_params($q1,array($username));
30:
if (pg_num_rows($r1)==1) { // user exists
31:
$row=pg_fetch_object($r1,0);
32:
print "<h1>$row->fullname</h1>";
33:
$q2="SELECT email FROM userEmails WHERE username=$1";
34:
$r2=pg_query_params($q2,array($username));
35:
display_emails($r2);
36:
} else {
37:
print "Sorry user doesn't exist";
38:
}
39:
} else {
40:
print "This page needs to be accessed from a form that provides the username";
41:
}
42:
?>
43:
44:
</body>
45:
</html>
Here in line 25 we use the isset function to check whether the variable username is coming through the request; we then use addslashes in line 26, to sanitize the data and avoid the SQL injection attack; finally, in line 30, we check the number of rows returned by our first query; if there is no user with that username, then 0 rows are returned.
Now, our listUsers.php page displays all users; it would be nice if we could add a link there to display the emails for each particular user. If you tried the form above you probably saw that the variable name and value gets added to the url, so it now looks like: htt://okaram...../displayUser.php?username=okaram , the variable username, with value okaram is being sent to displayUser.php; we can use php to create a link of that form, and display the emails for the appropriate user.
So, here is listUsers2.php, that does just that.
1:
<?php include ('connect.php'); ?>
2:
<html>
3:
<head>
4:
<title> Forms </title>
5:
<link rel="stylesheet" href="mystyle.css">
6:
</style>
7:
</head>
8:
<body>
9:
<?php
10:
function display_users($result)
11:
{
12:
print "<table>\n";
13:
print " <tr><th>Username</th><th>Name</th><th>Actions</th></tr>\n";
14:
for($i=0; $i<pg_num_rows($result); ++$i) {
15:
$row=pg_fetch_object($result,$i);
16:
if($i%2==0)
17:
$class="even";
18:
else
19:
$class="odd";
20:
print " <tr class=\"$class\"><td>$row->username</td><td>$row->fullname</td>
21:
<td> <a href=\"displayUser.php?username=$row->username\">View emails</a></td></tr>\n";
22:
}
23:
print "</table>";
24:
}
25:
26:
$query=" SELECT userName, fullName
27:
FROM MyUser";
28:
$result=pg_query($query);
29:
display_users($result);
30:
?>
31:
32:
</body>
33:
</html>
Another obvious piece of functionality would be the ability to add data to the database, or modify existing data. The basic structure is the same. Create an html form that allows to send all the necessary data, and a php page that uses that data to create an SQL statement (in this case an INSERT, DELETE or UPDATE instead of a SELECT statement) and uses pg_query to have the DBMS execute that statement.
As an example, we create a form, addUser.html that has the data for the MyUser table:
1:
<html>
2:
<head>
3:
<title> Add User </title>
4:
<link rel="stylesheet" href="mystyle.css">
5:
</style>
6:
</head>
7:
<body>
8:
<form action="do_addUser.php">
9:
<p>UserName: <input type="text" name="username"></p>
10:
<p>Password: <input type="password" name="password"></p>
11:
<p>Name: <input type="text" name="fullname"></p>
12:
<p>Admin?: <input type="checkbox" name="isadmin"></p>
13:
<input type="submit" value="Add user">
14:
</form>
15:
16:
</body>
17:
</html>
Notice that we use an input of type checkbox at the end of the form, to display a textbox. We also use an input of type password; this is just a normal textbox, except that it displays * instead of the characters you type.
The php page is relatively simple; the only variable that gets special treatment is isadmin, which needs to be transformed. If the checkbox is not checked, the variable is not sent at all, wheras if it is checked, the string 'on' is sent as its value; so we use isset to check whether it has been sent or not, and we transform that to "true" or "false", since that is what needs to go in the SQL statement.
1:
<?php include ('connect.php'); ?>
2:
<html>
3:
<head>
4:
<title> Forms </title>
5:
<link rel="stylesheet" href="mystyle.css">
6:
</style>
7:
</head>
8:
<body>
9:
<?php
10:
11:
if(isset($_REQUEST['isadmin']))
12:
$isAdmin="true";
13:
else
14:
$isAdmin="false";
15:
16:
$userName=addslashes($_REQUEST['username']);
17:
$fullName=addslashes($_REQUEST['fullname']);
18:
$password=addslashes($_REQUEST['password']);
19:
$query="INSERT INTO MyUser(username, password, fullName,isAdmin) VALUES
20:
($1,$2,$3,$4)";
21:
pg_query_params($query,array($userName,$fullName,$password,$isAdmin));
22:
?>
23:
</body>
24:
</html>
Notice that the main difference between this page and displayUser.php is in the kind of SQL statement. Line 19 creates the INSERT statement, and line 21 sends it to the DBMS.
Now, this doesn't do anything to validate the data. A more robust implementation would look like this:
1:
<?php include ('connect.php'); ?>
2:
<html>
3:
<head>
4:
<title> Forms </title>
5:
<link rel="stylesheet" href="mystyle.css">
6:
</style>
7:
</head>
8:
<body>
9:
<?php
10:
11:
function userExists($username)
12:
{
13:
$q1="SELECT fullName
14:
FROM MyUser WHERE userName=$1";
15:
$r1=pg_query_params($q1,array($username));
16:
return pg_num_rows($r1)==1;
17:
}
18:
19:
function validateIncomingData()
20:
{
21:
$valid=true;
22:
if(!isset($_REQUEST['username']) ||
23:
!isset($_REQUEST['fullname']) ||
24:
!isset($_REQUEST['password'])
25:
) {
26:
print "There must be a mistake. The form should provide username, fullname and password";
27:
return false;
28:
}
29:
30:
if($_REQUEST['username']=="") {
31:
print "<p>You need to provide a username</p>";
32:
$valid=false;
33:
}
34:
if($_REQUEST['fullname']=="") {
35:
print "<p>You need to provide a full name</p>";
36:
$valid=false;
37:
}
38:
if($_REQUEST['password']=="") {
39:
print "<p>You need to provide a password</p>";
40:
$valid=false;
41:
}
42:
if (userExists(addslashes($_REQUEST['username']))) {
43:
print "<p>That user already exists</p>";
44:
$valid=false;
45:
}
46:
return $valid;
47:
}
48:
49:
function addUser() {
50:
if(isset($_REQUEST['isadmin']))
51:
$isAdmin="true";
52:
else
53:
$isAdmin="false";
54:
55:
$userName=addslashes($_REQUEST['username']);
56:
$fullName=addslashes($_REQUEST['fullname']);
57:
$password=addslashes($_REQUEST['password']);
58:
$query="INSERT INTO MyUser(username, password, fullName,isAdmin) VALUES
59:
($1,$2,$3,$4)";
60:
if(pg_query_params($query,array($userName,$fullName,$password,$isAdmin))) {
61:
print "User successfully added";
62:
return true;
63:
}
64:
else {
65:
print "<p>".pg_last_error()."</p>\n";
66:
return false;
67:
}
68:
}
69:
70:
if (validateIncomingData()) {
71:
addUser();
72:
}
73:
?>
74:
</body>
75:
</html>
Notice that the validateIncomingData function checks that all fields are coming in the request, and that they all have a value. It also checks that the user doesn't already exist in the database.