Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PHP

PHP

About - History

  • Originally written and released by Rasmus Lerdorf in 1995
  • PHP/FI
  • Personal Home Page Tools or Personal Home Page / Forms Interpreter
  • PHP 3.0 written and relased by Andi Gutmans and Zeev Suraski (ZEND) in 1997
  • PHP 4.0 Released in 1999-2000
  • PHP 5.0 Released in 2004
  • PHP 5.5.10 Released on 6 Marh 2014
  • PHP 5.6 is on its way
  • [History](http://www.php.net/manual/en/history.php" %}
  • PHP news

HTTP: A few words about how the web works

  • Browser -> HTTP Request -> Web Server (Apache, IIS, Nginx)
  • Web Server -> HTTP Response -> Browser
  • Simple response: Content of file as is (HTML, image, etc.)
  • CGI Response: Execute the file and return its output
  • PHP: Pass the file to the PHP Engine that runs within Apache
  • Others: JSP, ASP, mod_perl

Hello world

  • echo
  • <?php
  • .php

Embedded in an html file, code between <?php and ?> tags is executed as PHP code. The result printed by that code is embedded in the HTML page. While the mapping of URLs to files is configurable in the web server, PHP files usually end with the php extension.

<html>
<head><title></title></head>
<body>
<?php echo "Hello World"; ?>
<p>
Check out the source and see the PHP tags.
</p>
</body>
</html>

Hello world, no HTML

There is not even need for html around the code...

<?php echo "Hello World"; ?>

HTML inside PHP

<h1>HTML in PHP and other PHP tag</h1>

<?php
  echo "Hello";
  echo "<br>";
?>

<hr>

<?
  echo "World";
  echo "<br>\n";
?>

phpinfo

  • phpinfo
<?php phpinfo(); ?>

Comments

  • #
  • /*
<?php

/* 
  This is a comments
*/

# This is a one line comment

print "Can you see the comments here ?";  # this is too a comment;

?>

<!-- 

   An HTML comment

-->


Errors

Check how syntax error will look like


<?php
print 3 + 4
print 2 + 1
?>
  
Parse error: parse error in .../examples/intro/errors.php on line 4

print vs echo

  • print
  • echo

echo can get several parameters, print can only get one.

<?php
   echo 2, 3;
?>
<br>

<?php

   // print 2, 3;
   // Parse error: parse error

   print 2;
   print 3;

?>

Numbers

  • 0
  • 0x
<?php echo 42;   // 42  ?>

<br>

<?php echo 3.14;   // 3.14  ?>

<br>

<?php echo 0x11;   // 17 (hexa) ?>

<br>

<?php echo 011;   // 9  (octal)  ?>

Numerical Operations

  • /
<h1>Numerical Operations</h1>

<?php
  echo 19 + 23;  // 42
?>

<br>

<?php
  echo 2 * 3;    // 6 
?>

<br>

<?php
  echo 3 - 2;   // 1
?>

<br>

<?php

  echo 5/2;    // 2.5

?>

<br>

<?php

  echo 13 % 5;  // 3

?>

Strings - concatenation

  • "
  • .
<?php
  print "Name<br>";
  print "Foo" . " " . "Bar" . "<br>";
?>
   

Variables

Hold either numbers or strings. There is no need to declare the variable, nor is there a type involved.

<h1>Varriables</h1>
<?php
    $name   = "Table";
    $width  = 23;
    $length = 17;
    echo $name;
    echo "<br>";
    echo "Size: ";
    echo $width * $length;
?>

Variable interpolation (or variable expansion)

  • '

It happens in strings in double-quotes, but not in strings in single-quotes.

<?php

   $name = "Rasmus Lerdorf";
   $birthday = 1994;

   print "PHP was created in $birthday by $name<br>";
   print 'PHP was created in $birthday by $name<br>';

?>

String functions

  • strcmp
  • strcasecmp
  • explode
  • implode
  • nl2br
  • file_get_contents
  • str_replace
  • iconv
  • urlencode

There are lots of string functions in PHP. Here are a few examples:

  • strcmp - compare two string returns, -1, 0 +1 based on order according to ASCII table
  • strcasecmp
  • explode - split string to elements and return an array
  • implode - join array to make a string
  • nl2br - new line to html-break
  • file_get_contents - read in a whole file without explicitely opening (slurp)
  • str_replace - replace characters in a string
  • iconv - convert between character encodings: iconv('ISO-8859-8', 'UTF-8', $str)
  • urlencode - when we are generating URLs some characters (e.g. space) need to be encoded

addslashes

  • addslashes

Escaping special characters such as '

<?php
  $str = "fruit: 'banana'";
  echo $str;
  echo "<br>"; 
  echo addslashes($str);
?>

[addslashes](http://php.net/manual/en/function.addslashes.php" %}

stripslashes

  • stripslashes
<?php
  $str = "fruit: 'banana'";
  echo $str;
  echo "<br>"; 

  $with_slashes = addslashes($str);
  echo $with_slashes;
  echo "<br>"; 

  $back = stripslashes($with_slashes);
  echo $back;
  echo "<br>"; 

  if ($back == $str) {
      echo "They are the same";
      echo "<br>"; 
  }

?>


trim - remove white spaces from both ends of a string

  • trim
  • ltrim
  • rtrim
<?php
   $str = "   some text  ";
   echo "'$str'";
   echo "<br>";
   echo trim($str);
?>
   

trim

White space characters are \n, \r, \t and space itself. ltrim is left trim, rtrim is right trim.

PHP - Arrays

Array

  • array
<h1>Arrays</h1>
Arrays are ordered list of values
<br />


<?php

$fibonacci = array(1, 1, 2, 3, 5, 8, 13, 21, 34);
$fibo[0] = 1;
$fibo[1] = 1;
$fibo[2] = 2;
$fibo[3] = 3;
$fibo[4] = 5;
$fibo[5] = 8;
$fibo[6] = 13;

?>

<?php
print $fibonacci[6];
?>


Associative Array

  • hash
  • dictionary

Associative or Named Arrays are maps where the keys can be either strings or numbers.

<h1>Named Arrays</h1>
<?php
$length = array("php" => 3, "mysql" => 5, "linux" => 5);
print $length["php"];

print "x";
print $length["xyz"];
print "x";
?>


Array in key/value pairs

  • foreach
  • as
<?php
$a = array('two' => 2, 'one' => 1, 'three' => 3);
foreach ($a as $key => $value) {
   print "$key   $value\n";
}
?>

Array in key/value pairs using each

  • each
  • list
<?php

$a = array('two' => 2, 'one' => 1, 'three' => 3);
while (list($key, $value) = each($a)) {
    print "$key   $value\n";
}
?>

sort

  • sort

For arrays where the keys are numbers. reorder the array

<?php

$a = array(23, 3, 2, 14);
foreach ($a as $k => $val) {
    print $val;
    print "\n";
}

print "--------\n";

sort($a);
foreach ($a as $k => $val) {
    print $val;
    print "\n";
}


asort

  • asort
<?php
$a = array('two' => 2, 'one' => 1, 'three' => 3);

asort($a);  # sort according to values


while (list($key, $value) = each($a)) {
    print "$key   $value\n";
}

?>

ksort

  • ksort
<?php
$a = array('two' => 2, 'one' => 1, 'three' => 3);

ksort($a);  # sort according to keys

while (list($key, $value) = each($a)) {
    print "$key   $value\n";
}

?>

Special Arrays

  • _SERVER
  • _GET
<?php

print $_SERVER["HTTP_USER_AGENT"];
?>
<p>
<?php
print $_GET["name"];
?>
</p>

<a href="?name=Something">What else ?</a>


  • [About reserved variables](http://www.php.net/manual/en/reserved.variables.php" %}
  • CGI Specification

The _SERVER variable

  • _SERVER

Some of the values are server dependent!

<pre>
<?php
print_r($_SERVER);
?>
</pre>

A simple form

  • _GET

<form method="GET">
<table>
<tr><td>First name</td><td><input name="fname" ></td></tr>
<tr><td>Last name</td><td><input name="lname"  ></td></tr>
<tr><td></td><td><input type="submit" value="Register me"></td></tr>
</table>

<?php
print "First name: $_GET[fname]<br>";
print "Last name:  $_GET[lname]<br>";
?>


A simple calculator


<form method="GET">
<table>
<tr><td>Value:</td><td><input name="a" ></td></tr>
<tr><td>Value:</td><td><input name="b"  ></td></tr>
<tr><td></td><td><input type="submit" value="+"></td></tr>
</table>

<?php
print "Result: $_GET[a]+$_GET[b]=";
print $_GET[a] + $_GET[b];
?>


If statement

  • if
Your age please:
<form>
<input name="age">
<input type="submit" value="What can I drink ?">
</form>

<?php
  $age = $_GET["age"];
  
  if ($age > 18) {
      print "Beer";
  } else {
      print "Limonada";
  }
?>

<h2>The same without print statements</h2>
<?php
    if ($age > 18) {
        ?>
        <font style="color:red">Beer</font>
        <?php 
    } else { 
        ?>
        Limonada
        <?php
    }
?>

for loop

  • for
<?php

for($i=1; $i <= 9; $i++) {
    print "$i<br>";
}

?>

Exercise: improve the calculator

Improve the calculator to handle more operations

Exercise: keep values in the fields

Form with keeping the values in the fields after submit

PHP - Files

Read from file

  • fopen
  • fgets

Use fopen with the parameter r to open a file for reading and then use fgets to read the content line-by-line. Use fclose to close the filehandle. PHP closes the file at the end of the program but it is better practice to close it by ourself.


<?php
    $fd = fopen("data/text.txt", "r");
    while ($line = fgets($fd)) {  
        print "$line<br>";
    }
    fclose($fd);
?>
<hr>
File should be displayed now


Fail to open file

<?php
    $fd = fopen("no_such_dir/text.txt", "r");
    print "Still running";
?>

Warning: fopen(no_such_dir/text.txt): failed to open stream:
   No such file or directory in .../php/examples/files/open_fails.php on line 2
Still running

Checking if open file succeded

<?php
    if ($fd = fopen("no_such_dir/text.txt", "r")) {
       print "Sno_such_dir/text.txt succeeded<br>";
    }
    print "Still running<br>";

    if ($fd = fopen("data/text.txt", "r")) {
       print "data/text.txt succeeded<br>";
    }
?>

Warning: fopen(no_such_dir/text.txt): failed to open stream:
   No such file or directory in .../php/examples/files/open_fails.php on line 2
Still running
data/text.txt succeeded

Checking if open file succeded - suppress warnigs

  • @
<?php
    if ($fd = @fopen("no_such_dir/text.txt", "r")) {
       print "Sno_such_dir/text.txt succeeded<br>";
    }
    print "Still running<br>";

    if ($fd = @fopen("data/text.txt", "r")) {
       print "data/text.txt succeeded<br>";
    }
?>

Still running
data/text.txt succeeded

Read CSV file

  • fgetcsv

fgetcsv returns an array of the values.


<?php
    $fd = fopen("data/fields.csv", "r");
?>

<table border="1">
<?php
    while ($a = fgetcsv($fd, 80)) {  # read a csv file and split it up.
        if (!empty($a[0])) {
            print "<tr><td>$a[0]</td><td>$a[1]</td></tr>\n";
        }
    }
?>
</table>

Open file to read or write

  • $fh = fopen("file", r);
  • Other flags are "w", "r+", "w+", "a", "a+", "x" (create if not exists), "x+" (... also read)

Write to a file

  • fwrite

<?php
    $fd = fopen("data/out.txt", "w");
    for($i=0; $i < 10; $i++) {
        fwrite($fd, "Line $i\n");
    }
    fclose($fd);   
?>
<a href="data/out.txt">File</a> should be saved by now 

Fail to write to a file

<?php
    if ($fd = @fopen("no_such_dir/text.txt", "w")) {
        @fwrite($fd, "Something\n");
        @fclose($fd);   
        print "done";
    } else {
        print "Failed";
    }
?>

Registration form - save to fill


<?php echo time() ?>

<?php
if ((strcmp($_GET[fname], "") == 0) or
    (strcmp($_GET[lname], "") == 0) or
    (strcmp($_GET[password], "") == 0) or
    (strcmp($_GET[email], "") == 0)
   ) {
?>
<form method="GET">
<table>
<tr><td>First name</td>
    <td><input name="fname" value="<? echo $_GET[fname] ?>"></td></tr>
<tr><td>Last name</td>
    <td><input name="lname" value="<? echo $_GET[lname] ?>"></td></tr>
<tr><td>Email:</td>
    <td><input name="email" value="<? echo $_GET[email] ?>"></td></tr>
<tr><td>Password:</td>
    <td><input name="password" type="password" value=""></td></tr>
<tr><td></td>
    <td><input type="submit" value="Register me"></td></tr>
</table>
<?php

} else {
    if ($fh = fopen("data/register.txt", a)) {
        fwrite($fh, "$_GET[fname],$_GET[lname],$_GET[email],$_GET[password]\n");
        fclose($fh);
    }

?>

Thank you for registering
<?php
}
?>


copy - Copy a file

  • copy
<?php

  if (!copy($oldfile, $newfile)) {
     print "Failed to copy $oldfile to $newfile";
  }

?>

unlink - Delete a file

  • unlink
<?php

   unlink(filename);

?>

chmod

  • chmod
<?php

    chmod(filename, "644");
    chmod(filename, "a+w");

?>

chown - Change owner

  • chown
<?php

    chown($filename, $user_name);

?>

Get current working directory - getcwd

<?php
  print getcwd();
?>

Show source

  • file_get_contents
  • htmlspecialchars
<a href="source.php">source</a><p>
<?php
$html = "";
$file = $_GET['file'];
if (strcmp($file, "") == 0) {
    $file = $_SERVER['HTTP_REFERER'];
    ereg("([^/]*$)", $file, $res);
    $file = $res[0];
}
if (strcmp($file, "") == 0) {
    print "Filename missing";
} else {
    $html = file_get_contents($file);
    print htmlspecialchars($html);
}
?>

PHP - PCRE - Perl compatible Regular Expressions

preg_match - PHP Regex Match

preg_match

![](examples/regex/simple_match.php" %}

PCRE

PHP - more

Send mail

Registration form, mail to user

<?php
if ($_POST[fname] and $_POST[lname] and $_POST[email]) {
    mail($_POST[email], 'PHP Registration',
        "Dear $_POST[fname],\n\nThank you for your registration\n\n",
        "From: admin@php.net\r\n");
?>

Thank you for the registration.<br>
Mail sent<br>

<?php
} else {
?>

Registration form:
<form method="POST">
<table>
<tr><td>First name:</td>
    <td><input name="fname" value="<?= $_POST[fname]?>"></td></tr>
<tr><td>Last name: </td>
    <td><input name="lname" value="<?= $_POST[lname]?>"></td></tr>
<tr><td>E-mail:    </td>
    <td><input name="email" value="<?= $_POST[email]?>"></td></tr>
<tr><td></td><td><input type="submit" value="Register"></td></tr>
</table>
</form>

<?php 
}
?>



Phonebook

<?php
header('Content-type: text/html;charset=UTF-8');
?>

<a href="?">home</a>
<a href="?rm=list">list</a>
<a href="?rm=add_form">add_form</a>

<?php
$rm = $_GET[rm];
$filename = "data/phonebook.csv";
?>


<?php
if ($rm == "list") {
    $fd = fopen($filename, "r");

    ?>

    <table border="1">
    <tr><td>First name</td><td>Last name</td><td>E-mail</td><td>Phone</td></tr>
    
    <?php
    while ($a = fgetcsv($fd, 80)) {  # read a csv file and split it up.
        if (!empty($a[0])) {
            print "<tr><td>$a[0]</td><td>$a[1]</td>";
            print "<td><a href=\"?rm=mail_form&email=$a[2]\">$a[2]</a></td>";
            print "<td>$a[3]</td></tr>";
        }
    }
    ?>
    </table>

    <?php
}

if ($rm == "add") {
    if ($_GET[fname]) {
        $fd = fopen($filename, "a");
        fwrite($fd, "$_GET[fname],$_GET[lname],$_GET[email],$_GET[phone]\n");
        fclose($fd);
        print "added";
    } else {
        $rm = "add_form";
    }
}

?>

<?php

if ($rm == "add_form") {

    ?>

    <form method="GET">
    <input type="hidden" name="rm" value="add">
    First name: <input name="fname" value="<? echo $_GET[fname] ?>">
    Last name:  <input name="lname" value="<? echo $_GET[lname] ?>">
    E-mail:     <input name="email" value="<? echo $_GET[email] ?>">
    Phone:      <input name="phone" value="<? echo $_GET[phone] ?>">
    <input type="submit" value="Add">
    </form>

    <?php 
} 
?>

Exercise: enlarge the phonebook

to hold more data

Exercise: enable editing the entries

Exercise: complex

Form where people can register, save the values in a csv file, save e-mail to registran, send e-mail to administrator, generate random string (use rand(1, 20)), another form to type in given string and change the entry of the registran. Enable listing values.

Parse XML file

<?php
$file = "data.xml";
$depth = array();

function startElement($parser, $name, $attrs) {
    global $debth;
    for ($i = 0; $i < $depth[$parser]; $i++) {
        echo "   ";
    }
    echo "\n";
    $depth[$parser]++;
}


$xml_parser = xml_parser_create();
xml_set_element_handler($xml_parser, "startElement");
 


?>

Functions


<a href="?fib=4">fib</a>
<?php

function fib($n) {
    if ($n < 1) 
        return(1);
    
    return(fib($n-1) + fib($n-2));
}


echo fib($_GET[fib]);

?>


Resources

  • PHP.net
  • Editors: any text editor with syntax highlighting
  • Read the source code of applications you can download from site
  • Google and Google groups

PHP - mysql

MySQL - connect

  • mysql_connect
  • mysql_error
  • mysql_select_db
<?php

$db = mysql_connect('localhost', 'username');
if (! $db) {
    die ("Could not connect" . mysql_error());
}

$selected = mysql_select_db('db_name', $db);
if (! $selected) {
    die("Could not select: " . mysql_error());
}


Fetch data from MySQL server

  • mysql_query
  • SELECT
  • mysql_fetch_assoc
  • mysql_close
Hi SQL
<hr>

<?php

include "connect.php";

#$result = @mysql_query("DESCRIBE users");
#if (! $result) {
#    die ("Could not fetch describe: " . mysql_error());
#}

$result = mysql_query("SELECT * FROM users");
if (! $result) {
    die("Failed select: " . mysql_error());
}

?>

<table border="1">
<tr><td>fname</td></tr>

<?php

while ($row = mysql_fetch_assoc($result)) {
    echo "<tr>";

    echo "<td>" . $row['fname'] . "</td>";
    echo "</tr>\n";
}
?>

</table>

<?php
mysql_close($db);
?>

<hr>
Done

INSERT data into MySQL

  • mysql_query
  • INSERT
<?php

include "connect.php";

if ($_POST["fname"] and $_POST["lname"] and $_POST["email"]) {

    mail($_POST["email"],
        'Registration',
        "Dear " . $_POST["fname"]  . "\n\nThank you for your registration\n\n",
        "From: foobar@examples.com\r\n"
    );

    $res = mysql_query("INSERT INTO users (fname, lname, email) VALUES ('" .
           $_POST['fname'] . "', '" .
           $_POST['lname'] . "','" . 
           $_POST['email'] . "')");
    if (! $res) {
        die("Failed insert: " . mysql_error());
    }
?>

  Thank you for the registration.<br>
  Mail sent<br>

<?php
} else {
?>

   Registration form:
   <form method="POST">
   <table>
   <tr><td>First name:</td>
       <td><input name="fname" value="<?= $_POST[fname]?>"></td></tr>
   <tr><td>Last name: </td>
       <td><input name="lname" value="<?= $_POST[lname]?>"></td></tr>
   <tr><td>E-mail:    </td>
       <td><input name="email" value="<?= $_POST[email]?>"></td></tr>
   <tr><td></td><td><input type="submit" value="Register"></td></tr>
   </table>
   </form>

<?php 
}
?>



Testing PHP

Starting the server

  • php

In the examples directory start apache2.pl start then you can access the server via http://localhost:8081/php/

Almost manually testing add()

In the first examples we recreate a path similar to what we took in the Perl introduction so you might skip it or just flip through the pages.

Let's assume we have a simple PHP library with a bunch of functions. It is located in our includes directory in the mylib.php file. It is used by a "web application" called basic_calc.php that provides a - surprise - calculator for the web visitor.

In order to test this we create a separate PHP script that will require the relevant library and call the add() function supplying various arguments and displaying the results.

Then we eyeball those results to see if they are what they should be.

<?php

require_once(dirname(__FILE__) . '/../includes/mylib.php');

print add(1,1) . '<br>';
print add(2,2) . '<br>';
print add(1,1,1) . '<br>';

?>

Result:

2
4
2

Not much of an output but if we are careful we'll see that the third line is incorrect. The problem is that it is a lot of effort to check if the results are correct.

dirname(FILE) just gives the path to the directory of currently executing file and we know the library we are testing is relative to it.

Print expected values as well

So the problem is that the test runner has to compute the expected results every time she runs the test script.

A slight improvement will be to show the expected results next to the actual values.

<?php

require_once(dirname(__FILE__) . '/../includes/mylib.php');

print add(1,1)   . ' == 2<br>';
print add(2,2)   . ' == 4<br>';
print add(1,1,1) . ' == 3<br>';

?>

Result:

2 == 2
4 == 4
2 == 3

This is still hard to follow when there are lots of cases or even a few cases with large output but it seems to be a step in the good direction.

Compare actual with expected values and print only pass/fail

<?php

require_once(dirname(__FILE__) . '/../includes/mylib.php');

print (add(1,1)   == 2 ? 'pass' : 'fail') . '<br>';
print (add(2,2)   == 4 ? 'pass' : 'fail') . '<br>';
print (add(1,1,1) == 3 ? 'pass' : 'fail') . '<br>';

?>

Result:

pass
pass
fail

Instead of manually comparing the actual results with the expected values let the computer do the hard work and let it only print if there was a success or a failure. We introduce some new code that will print "pass" for every case when the result was the expected value and "fail" when they were different.

This is certainly an improvement but before we further improve our display let's turn our attention to our own testing code.

Refactor to get assertTrue

As we are also programmers we will soon recognize that there is code duplication in our test script and will factor out the whole printing of pass/fail part to a function that we call assertTrue(). It should receive a true or false value and it will print "pass" or "fail" accordingly.

<?php

require_once(dirname(__FILE__) . '/../includes/mylib.php');

assertTrue(add(1,1)   == 2);
assertTrue(add(2,2)   == 4);
assertTrue(add(1,1,1) == 3);


function assertTrue($condition) {
    print ($condition ? 'pass' : 'fail') . '<br>';
}

?>

Result:

pass
pass
fail

As in every refactoring the functionality and the output should remain the same, just the implementation improves. (Hopefully)

Introduction to the PHP SimpleTest framework

  • simpletest

That's all very nice but someone has already implemented this with a lot of other nice features. We are going to look at the SimpleTest framework of PHP.

Simpletest

I am looking at the currently latest 1.0.1 version that already includes autorun.php.

Ubuntu packages the PHP Simpletest package but sudo aptitude install php-simpletest only gets you version 1.0 which is relatively old.

So the best course of action is to download the simpletest_1.0.1.tar.gz file from the Simpletest web site and unzip it in a place where your web server can reach it.

assertTrue in SimpleTest

  • asssertTrue

So we unzipped the simpletest zip file to a directory in the course directory structure some 3 levels above the actual examples.

The first thing we'll have to do in our code is to load the autorun.php file from the simpletest directory. That file, as its name also reveals will run our tests automatically when we hit the page.

The next thing is to pay the overhead of the test writing. Luckily we'll only need to pay it once for every group of tests. This is Object Oriented code but even if you are not yet familiar with OO you don't have to worry. The tests themselves are simple functions calls.

We need to create a class that extends UnitTestCase class provided by SimpleTest. The name ( in our example TestOfAdd ) of the class does not really matter but as usual, it is better if it is descriptive of what the tests are going to, err ... test.

Withing the class we need to implement a function ( testAdd in our example ) that will include the test cases. In the case of the function the name has to start with "test" but beyond that it does not matter what exactly it is. It should be descriptive.

Within the function we can call the assertTrue method on the $this object. For those not familiar with OO, $this is provided automatically and the -> notion is just a fancy way of calling the assertTrue() function.

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');

require_once(dirname(__FILE__) . '/../includes/mylib.php');

class TestOfAdd extends UnitTestCase {
    function testAdd() {
        $this->assertTrue(add(1,1) == 2);
        $this->assertTrue(add(2,2) == 4);
        $this->assertTrue(add(1,1,1) == 3);
    }
}


?>


Output:

{% embed include file="src/examples/php/simpletest/st01.txt)

With the last row being RED

So TestSimple collapses all our individual results and conveniently only shows the aggregated result and the individual tests that actually failed.

Unfortunately this report only gives us the line number of where the assertion that failed. No other details about the failure.

SimpleTest showing success

Just in order so we see it I include an example where I removed the failing test and in the following example all of the assertions are successful.

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');

require_once(dirname(__FILE__) . '/../includes/mylib.php');

class TestOfAdd extends UnitTestCase {
    function testAdd() {
        $this->assertTrue(add(1,1) == 2);
        $this->assertTrue(add(2,2) == 4);
    }
}


?>


Output:

{% embed include file="src/examples/php/simpletest/st02.txt)

With the last row being GREEN

assertEqual showing the actual values

  • asssertEqual

SimpleTest and its UnitTestCase class provides further methods for assertions with better error reporting. In the end they all report assertions but these others have better capabilities in providing details on the failures.

In our case we could use assertEqual method instead of the assertTrue method. This one should receive two values. One of them should be the expected value the other one the actual result. The library does not make a recommendation which is which, it treats the two values in the same way and only checks if they are equal or not.

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');

require_once(dirname(__FILE__) . '/../includes/mylib.php');

class TestOfAdd extends UnitTestCase {
    function testAdd() {
        $this->assertEqual(add(1,1), 2);
        $this->assertEqual(add(2,2), 4);
        $this->assertEqual(add(1,1,1), 3);
    }
}


?>


Output:

{% embed include file="src/examples/php/simpletest/st03.txt)

With the last row being RED

As you can see there is now a better explanation of what failed.

SimpleTest showing description of error

The assert* methods of SimpleTest allow an optional additional parameter which is the description of the current assertion. In case of failure it will be shown instead of the details of the error

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');

require_once(dirname(__FILE__) . '/../includes/mylib.php');

class TestOfAdd extends UnitTestCase {
    function testAdd() {
        $this->assertEqual(add(1,1), 2, "1+1");
        $this->assertEqual(add(2,2), 4, "2+2");
        $this->assertEqual(add(1,1,1), 3, "three params 1+1+1");
    }
}


?>


Output:

{% embed include file="src/examples/php/simpletest/st04.txt)

With the last row being RED

PHP SimpleTest

The PHP SimpleTest framework provides lots of additional tools for testing your application. The documentation for the UnitTestCase class with the list of various assert methods can be found here: unit test documentation

Testing PHP on the command line

Running the test using the web browser will work well during the development of each test and the relevant code but as the number of tests grow we will prefer to automate this process and execute it by some automated tool. Eg. crontab on unix, the scheduler on windows, a smoke testing system or the post commit hook of your version control system. That will only work if we can run our tests from the command line.

Luckily SimpleTest and the autorun.php can let us do this. We can take the last example and run it from the command line:

$ php examples/php/simpletest/st04.php

The Output is quite similar to what we have in the browser but this is without any colors.

{% embed include file="src/examples/php/simpletest/st04.out)

We can now create a batch file and run all the test files we might have from the command line. We'll come back to this later.

Testing PHP Application

While it is nice to know we'll be able to test each one of the functions or classes on its own in many cases that's not how things work. For this testing method to work yo have to be able to separate the functionality of each function and class and test them in isolation.

Especially when you already have a partially or fully working application probably written by someone else who wrote spaghetti code, it would be impossible to write tests in isolation. It will be probably also a waste of energy as soon you are going to start to clean up that code changing the internal structure, changing how functions work and building up

  • hopefully - a cleaner codebase.

In such cases it is much better to start from the top down. Test the functionality of the application without even knowing about the internal structure of the code. Actually the application does not even need to be written in PHP for this type of testing.

We are going to imitate the web browser, access a web site and check if the returned page contains the information as we expect.

We subclass the WebTestCase class which provides a get method to retrieve a web page given a URL.

In itself that's not yet an assertion so we wrap it with the already familiar assertTrue method. We can do that as WebTestClass is a subclass of UnitTestCase.

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');
require_once(dirname(__FILE__) . '/../../../tools/simpletest/web_tester.php');


class TestOfCalculator extends WebTestCase {
    function testBasicCalc() {
        $url = 'http://localhost:8081/php/calc/basic_calc.php';
        $this->assertTrue($this->get($url));
    }
}


?>

The resulting output is similar to what we saw earlier when we just tried to test an internal function.

Check web page content

The previous test only checked if a page was returned but not the content of the page so we add another assertion that checks if a given string can be found in the text of the web page.

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');
require_once(dirname(__FILE__) . '/../../../tools/simpletest/web_tester.php');


class TestOfCalculator extends WebTestCase {
    function testBasicCalc() {
        $url = 'http://localhost:8081/php/calc/basic_calc.php';
        $this->assertTrue($this->get($url));

        $this->assertText('Basic Calculator');
    }
}


?>

Check web page title

The same way can can check if the page title is correct using the assertTitle method.

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');
require_once(dirname(__FILE__) . '/../../../tools/simpletest/web_tester.php');


class TestOfCalculator extends WebTestCase {
    function testBasicCalc() {
        $url = 'http://localhost:8081/php/calc/basic_calc.php';
        $this->assertTrue($this->get($url));
        $this->assertText('Basic Calculator');

        $this->assertTitle('Scientific Calculator');
    }
}


?>

Check web page content a failure

It's really nice to see everything working but there will be times when you encounter a problem. For example that certain text is missing from the web page:

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');
require_once(dirname(__FILE__) . '/../../../tools/simpletest/web_tester.php');


class TestOfCalculator extends WebTestCase {
    function testBasicCalc() {
        $url = 'http://localhost:8081/php/calc/basic_calc.php';
        $this->assertTrue($this->get($url));
        $this->assertText('Basic Calculator');
        $this->assertTitle('Scientific Calculator');

        $this->assertText('Real Calculator');
    }
}


?>

Will fail with the following output

web04.php
Fail: TestOfCalculator -> testBasicCalc -> Text [Real Calculator] not detected in
  [String: Scientific Calculator Basic Calculator Value: Value:]
     at [.../examples/php/simpletest/web04.php line 14]
1/1 test cases complete: 3 passes, 1 fails and 0 exceptions.

Checking forms

To further check if the page is correct we could check, using assertField(), if the form we expect to be on the page has the input fields as we expect them. We can even check if they have the correct preset values.

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');
require_once(dirname(__FILE__) . '/../../../tools/simpletest/web_tester.php');


class TestOfCalculator extends WebTestCase {
    function testBasicCalc() {
        $url = 'http://localhost:8081/php/calc/basic_calc.php';
        $this->assertTrue($this->get($url));
        $this->assertText('Basic Calculator');
        $this->assertTitle('Scientific Calculator');

        $this->assertField('a', '');
        $this->assertField('b', '');
    }
}

?>

Unfortunately due to external limitations currently this code cannot recognize if there are more than one forms on the page and will mash them together for our purposes.

Submit form

The setField can be used to set the value in a field and the click method to submit submit the form by clicking a button. After submitting the form we should check if

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');
require_once(dirname(__FILE__) . '/../../../tools/simpletest/web_tester.php');


class TestOfCalculator extends WebTestCase {
    function testBasicCalc() {
        $url = 'http://localhost:8081/php/calc/basic_calc.php';
        $this->assertTrue($this->get($url));
        $this->assertText('Basic Calculator');
        $this->assertTitle('Scientific Calculator');

        $this->assertField('a', '');
        $this->assertField('b', '');


        $this->setField('a', 19);
        $this->setField('b', 23);
        $this->assertTrue($this->click('Add'));
        $this->assertText('Result: 19+23=42');
    }
}


?>

Check for text that should not be there.

In a previous example we checked if the parts of a form are in place and then immediately we submitted a form with correct values.

We could in fact check a couple of more things. For example we could check if the "Result" string appears on the page when we access it for the first time without parameters.

We could also submit the form with missing or bogus data and see how the application reacts. Especially we can use the assertNoText assertion to check if a certain text does NOT appear on the page.

<?php

require_once(dirname(__FILE__) . '/../../../tools/simpletest/autorun.php');
require_once(dirname(__FILE__) . '/../../../tools/simpletest/web_tester.php');


class TestOfCalculator extends WebTestCase {
    function testBasicCalc() {
        $url = 'http://localhost:8081/php/calc/basic_calc.php';
        $this->assertTrue($this->get($url));
        $this->assertText('Basic Calculator');
        $this->assertTitle('Scientific Calculator');

        $this->assertField('a', '');
        $this->assertField('b', '');
        $this->assertNoText('Result:');

        $this->assertTrue($this->clickSubmit('Add'));
        $this->assertText('Result:');
    }
}


?>

This way we can write test for an application without caring how it was written or in fact in what languages it is written. Once we are reasonably comfortable with our tests we can start to refactor the application.

Testing PHP with PHPUnit

Introduction to PHPUnit

PHPUnit was developed and is maintained by Sebastian Bergmann. http://sebastian-bergmann.de/ It has an extensive manual and it seems to be more wide-spread than SimpleTest we saw earlier.

The home page is at http://www.phpunit.de/

assertTrue with PHPUnit

In the first example we'll try to solve the same simple problem as we did in the SimpleTest example. We have a function called add() in the mylib.php file. We would like to test that function.

We include that file in our test script and include the already installed PHPUnit/Framework.php

We have to create a class that extends PHPUnit_Framework_TestCase - the name of the class does not matter. In the class we need to create testing functions. The name of each function has to start with 'test', otherwise the name does not matter. It should be descriptive as any function name.

<?php


require_once(dirname(__FILE__) . '/../includes/mylib.php');

require_once 'PHPUnit/Framework.php';

class AddTest extends PHPUnit_Framework_TestCase {

    public function testAddTwo() {
        $this->assertTrue(2 == add(1, 1));
    }
    public function testAddThree() {
        $this->assertTrue(3 == add(1, 1, 1));
    }
}


?>

As opposed to the SimpleTest framework, in PHPUnit the primary way to execute the tests is from the command line using the phpunit script. so you do the following:

phpunit examples/php/phpunit/cal01.php

The output looks like this:

{% embed include file="src/examples/php/phpunit/calc01.php.out)

In the results you can see a single .F meaning one of the testing functions failed. In addition you'll see details of the failure giving both the name of of the test function and the test class.

PHPunit showing success

Just in order to see how it looks like I removed the failing test from the test script and ran the test again.

the code

<?php


require_once(dirname(__FILE__) . '/../includes/mylib.php');

require_once 'PHPUnit/Framework.php';

class AddTest extends PHPUnit_Framework_TestCase {

    public function testAddTwo() {
        $this->assertTrue(2 == add(1, 1));
    }
}


?>

Output

PHPUnit 3.3.17 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion)

PHPUnit with assertEqual

Just as was in the case of the SimpleTest, PHPUnit also has an assertEqual method that allows the separation of the expected value and the actual result.

The first parameter of the assertEqual method is the expected value and the second parameter is the actual result. Making the roles of the two values clear is important so in the reporting we can already know what was the expected and what was the actual result.

<?php


require_once(dirname(__FILE__) . '/../includes/mylib.php');

require_once 'PHPUnit/Framework.php';

class AddTest extends PHPUnit_Framework_TestCase {

    public function testAddTwo() {
        $this->assertEquals(2, add(1, 1));
    }
    public function testAddThree() {
        $this->assertEquals(3, add(1, 1, 1));
    }
}


?>

Output

PHPUnit 3.3.17 by Sebastian Bergmann.

.F

Time: 0 seconds

There was 1 failure:

1) testAddThree(AddTest)
Failed asserting that <integer:2> matches expected value <integer:3>.
/home/gabor/work/gabor/training/testing/examples/php/phpunit/calc03.php:14

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

So the difference between this output and the one from the first example is that in this case the reader can know more details about the failure. The reader can actually see that the expected values was 3 while the actual value was 2. That can already give a clue where the problem might be.

assetEqual with message

assertEqual can actually get a third parameter too. This can hold a short description of what is being tested.

<?php


require_once(dirname(__FILE__) . '/../includes/mylib.php');

require_once 'PHPUnit/Framework.php';

class AddTest extends PHPUnit_Framework_TestCase {

    public function testAddTwo() {
        $this->assertEquals(2, add(1, 1), '1+1=2');
    }
    public function testAddThree() {
        $this->assertEquals(3, add(1, 1, 1), '1+1+1=3');
    }
}


?>

Output

In case of success this does not make a difference but when the test fails PHPUnit will display this message along with the name of the test function and the name of the test class. If the message is worded carefully it can further help in understanding the failure without even looking at the source code.

PHPUnit 3.3.17 by Sebastian Bergmann.

.F

Time: 0 seconds

There was 1 failure:

1) testAddThree(AddTest)
1+1+1=3
Failed asserting that <integer:2> matches expected value <integer:3>.
/home/gabor/work/gabor/training/testing/examples/php/phpunit/calc04.php:14

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

Installing PHP Unit

You should be able to install PHPUnit either by the package management system of your operating system or via PEAR. http://pear.php.net/

In Ubuntu 9.04 I found I could install it using

sudo aptitude install phpunit

but that seems to bring a very old version of PHPUnit.

In order to use PEAR first I had to install the command line tool using

sudo aptitude install php-pear

Then I followed the instructions on the PHPUnit web site though I had to use sudo for that as it would not work as regular user.

sudo channel-discover pear.phpunit.de

gave me the following error message:

$ sudo pear install phpunit/PHPUnit Did not download optional dependencies: pear/Image_GraphViz, pear/Log, use --alldeps to download automatically phpunit/PHPUnit requires PEAR Installer (version >= 1.8.1), installed version is 1.7.1 phpunit/PHPUnit can optionally use package "pear/Image_GraphViz" (version >= 1.2.1) phpunit/PHPUnit can optionally use package "pear/Log" phpunit/PHPUnit can optionally use PHP extension "pdo_sqlite" phpunit/PHPUnit can optionally use PHP extension "xdebug" (version >= 2.0.0) No valid packages found install failed

So I had to first upgrade PEAR which was easy:

$ sudo pear upgrade-all

and then I could install PHPUnit:

$ sudo pear install phpunit/PHPUnit

It might be slightly different on your operating system.