PHP Advanced and Object-Oriented Programming (2013)

Visual Quickpro Guide

1. Advanced PHP Techniques


In This Chapter

Multidimensional Arrays

Advanced Function Definitions

The Heredoc Syntax

Using printf() and sprintf()

Review and Pursue


One thing the advanced PHP programmer does better than the beginner is learn to take advantage of the obscure or hard-to-comprehend features of the language. For example, though you already know how to use arrays, you may not have mastered multidimensional arrays: creating them, sorting them, and so on. You have written your own functions by this point but may not understand how to use recursion and static variables. In this chapter, issues like these will be discussed, as well as other beyond-the-basics concepts, like the heredoc syntax and the printf()/sprintf() family of functions.

Multidimensional Arrays

Because of their power and flexibility, arrays are widely used in all PHP programming. In advanced situations, the multidimensional array often solves problems where other variable types just won’t do.

For the first of the two examples, you’ll see how to sort a multidimensional array. It’s a common question users have and it isn’t as hard as you might think. For the second example, you’ll create a database-driven to-do list image.

image

image One use of multidimensional arrays will be to create a nested to-do list.

Sorting multidimensional arrays

Sorting arrays is easy in PHP, thanks to the sort()ksort(), and related functions. Using them, you can sort a one-dimensional array by key, by value, in reverse order, and so forth. But these functions will not work on multidimensional arrays (not as you’d probably like, at least).

Say you have an array defined like so:

$a = array(
array('key1' => 940, 'key2' => 'blah'),
array('key1' => 23, 'key2' => 'this'),
array('key1' => 894, 'key2' => 'that')
);


The Short Array Syntax

New in PHP 5.4 is the short array syntax, which is simply an alternative way of creating an array. To use the short array syntax, replace calls to the array() function with the square array brackets:

// Old way:
$a = array(); // Empty
$b = array('this' => 'that');
// New way:
$a = []; // Empty
$b = ['this' => 'that'];


This is a simple two-dimensional array (an array whose elements are also arrays) that you might need to sort using key1 (a numeric sort) or key2 (an alphabetical sort). To sort a multidimensional array, you define your own sort function and then tell PHP to use that function by invoking the built-in usort()uasort(), or uksort() function.

The function you define must take exactly two parameters and return a value indicating which parameter should be first in the sorted list. A negative or false value means that the first parameter should be listed before the second. A positive or true value means the second parameter should come first. A value of 0 indicates the parameters have the same value.

To sort the preceding array on the first key, the sorting function would be defined as

function asc_number_sort($x, $y) {
  if ($x['key1'] > $y['key1']) {
    return true;
  } elseif ($x['key1'] < $y['key1']) {
    return false;
  } else {
    return 0;
  }
}

Then the PHP code would use this function image:

usort($a, 'asc_number_sort');

image

image The multidimensional array sorted by numeric value (key1).

PHP will continue sending the inner arrays to this function so that they may be sorted. If you want to see this in detail, print the values being compared in the function image.

image

image By printing out the values of $x['key1'] and $y['key1'], you can see how the user-defined sorting function is invoked.

The usort() function sorts by values but does not maintain the keys (for the outermost array). When you use uasort(), the keys will be maintained. When you use uksort(), the sort is based on the keys.

To sort on the second key in the preceding example, you would want to compare two strings. That code would be image

function string_sort($x, $y) {
  return strcasecmp($x['key2'], $y['key2']);
}
usort($a, 'string_sort');

image

image An alphabetical sort on the example array using key2.

Or you could just use strcmp() to perform a case-sensitive sort. (In case you’re not clear as to how strcasecmp() works, it will be explained in the following steps.)

To see this in action for yourself, let’s run through an example.

To sort a multidimensional array

1. Create a new PHP script in your text editor or IDE, to be named sort.php, starting with the HTML code (Script 1.1):

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Sorting Multidimensional Arrays</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 1.1 - sort.php

Script 1.1. This script defines a two-dimensional array, which is then sorted based upon the inner array values.


1    <!doctype html>
2    <html lang="en">
3    <head>
4       <meta charset="utf-8">
5       <title>Sorting Multidimensional Arrays</title>
6       <link rel="stylesheet" href="style.css">
7    </head>
8    <body>
9    <?php # Script 1.1 - sort.php
10
11   /* This page creates a multidimensional array
12    * of names and grades.
13    * The array is then sorted twice:
14    * once by name and once by grade.
15    */
16
17   // Create the array...
18   // Array structure:
19   // studentID => array('name' => 'Name', 'grade' => XX.X)
20   $students = array(
21      256 => array('name' => 'Jon', 'grade' => 98.5),
22      2 => array('name' => 'Vance', 'grade' => 85.1),
23      9 => array('name' => 'Stephen', 'grade' => 94.0),
24      364 => array('name' => 'Steve', 'grade' => 85.1),
25      68 => array('name' => 'Rob', 'grade' => 74.6)
26   );
27
28   // Name sorting function:
29   function name_sort($x, $y) {
30      return strcasecmp($x['name'], $y['name']);
31   }
32
33   // Grade sorting function:
34   // Sort in DESCENDING order!
35   function grade_sort($x, $y) {
36      return ($x['grade'] < $y['grade']);
37   }
38
39   // Print the array as is:
40   echo '<h2>Array As Is</h2><pre>' . print_r($students, 1) . '</pre>';
41
42   // Sort by name:
43   uasort($students, 'name_sort');
44   echo '<h2>Array Sorted By Name</h2><pre>' . print_r($students, 1) . '</pre>';
45
46   // Sort by grade:
47   uasort($students, 'grade_sort');
48   echo '<h2>Array Sorted By Grade</h2><pre>' . print_r($students, 1) . '</pre>';
49
50   ?>
51   </body>
52   </html>


You’ll note that I’m using HTML5 for all of the examples in this book, although that won’t have an impact on any of the PHP code. I’m also using a simple style sheet from the HTML5 Boilerplate (http://html5boilerplate.com/).

You can download all of the book’s code from LarryUllman.com.

2. Define a multidimensional array:

$students = array(
  256 => array('name' => 'Jon', 'grade' => 98.5),
  2 => array('name' => 'Vance', 'grade' => 85.1),
  9 => array('name' => 'Stephen', 'grade' => 94.0),
  364 => array('name' => 'Steve', 'grade' => 85.1),
  68 => array('name' => 'Rob', 'grade' => 74.6)
);

The outer array, $students, has five elements, each of which is also an array. The inner arrays use the student’s ID for the key (a made-up value) and store two pieces of data: the student’s name and their grade.

3. Define the name-sorting function:

function name_sort($x, $y) {
  return strcasecmp($x['name'], $y['name']);
}

The strcasecmp() function returns a number—negative, 0, or positive—indicating how similar two strings are. If a negative value is returned, the first string comes before the second alphabetically; if a positive value is returned, the second string comes first. If 0 is returned, the strings are the same.

4. Define the grade sorting function:

function grade_sort($x, $y) {
  return ($x['grade'] < $y['grade']);
}

This example is like the demo in the introduction to these steps, but in its shortest format. One significant difference is that this example should perform a descending sort, listing the highest grades first. This is easily accomplished: change the comparison operator from greater than to less than. Thus, if the first argument is less than the second, the value true is returned, which indicates the second argument should come first in the ordered list.

5. Print the array as it’s initially defined:

echo '<h2>Array As Is</h2><pre>' . print_r($students, 1) . '</pre>';

To quickly print out the array’s contents, use the print_r() function. The output will be wrapped within <pre> tags for improved legibility.

6. Sort the array by name and print the results:

uasort($students, 'name_sort');
echo '<h2>Array Sorted By Name</h2><pre>' . print_r ($students, 1) . '</pre>';

Here the uasort() function is used so that the keys—the student IDs—are not lost image. If just usort() was invoked, the sorting would drop those keys image.

image

image The array sorted by name.

image

image Failure to use uasort() would cause the keys, which store meaningful values (see Script 1.1), to be lost.

7. Sort the array by grade and print the results image:

uasort($students, 'grade_sort');
echo '<h2>Array Sorted By Grade</h2><pre>' . print_r ($students, 1) . '</pre>';

image

image The array sorted by grade, in descending order.

8. Complete the page:

?>
</body>
</html>

9. Save the file as sort.php, place it in your Web directory, and test in your Web browser.

Database-driven arrays

If you think about it, most database queries return a multidimensional array image. When the query results are fetched one record at a time, the multidimensional structure doesn’t add any complication to your code. However, if you need to do something more elaborate with the results, you’ll need a way to comprehend and manage the nested structure.

image

image Selecting multiple columns from multiple rows in a database results in a multidimensional array.

To demonstrate this, the next example will create a database-driven, Web-based to-do list system. If the to-do list were one-dimensional, this wouldn’t be that hard. But this list needs to be nestable, where each item can have multiple steps. The result will be a tree-like structure, where each branch can have its own offshoots image.

image

image How a nested to-do list looks as a tree.

The database table required to accomplish this is surprisingly simple:

CREATE TABLE tasks (
task_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
parent_id INT UNSIGNED NOT NULL DEFAULT 0,
task VARCHAR(100) NOT NULL,
date_added TIMESTAMP NOT NULL,
date_completed TIMESTAMP,
PRIMARY KEY (task_id),
INDEX parent (parent_id),
INDEX added (date_added),
INDEX completed (date_completed)
);

The task_id is an automatically incremented primary key. The value will also be used as the parent_id if a task is a substep. The task itself goes into a VARCHAR(100) column, which you could also define as a text type if you wanted to allow for longer descriptions. Two timestamp columns round out the table, one documenting when the task was added and another to indicate its completion. Three standard indexes are placed on columns that might be used in queries.

The trick to this application is that each item has a parent_id attribute. If an item is a substep, its parent_id would be the task number of the item that it falls under image.

image

image This table represents the same data as in image and image. There will be a pseudo-foreign key-primary key relationship between the task_id and the parent_id columns.

If an item is not a substep, its parent_id would be 0. It’s a very simple setup that allows for the flexible, nested structure, but handling this in PHP will take some effort.

You’re reading an advanced book on PHP, so I’m going to assume that you’re fully capable of creating this database table for yourself. The next few pages will walk through the PHP script that adds new tasks to this table. In the upcoming sections of the chapter, you’ll see how to use recursive functions to handle the multidimensional array.

To add tasks to the database

1. Begin a new PHP script in your text editor or IDE, to be named add_task.php, starting with the HTML (Script 1.2):

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Add a Task</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 1.2 - add_task.php

2. Connect to the database:

$dbc = mysqli_connect('localhost', 'username', 'password', 'test');

I’ll be using MySQL and the Improved MySQL functions in this script. You’ll need to change the particulars—username, password, and database name—to match what’s correct for your setup.

For simplicity’s sake, no error handling is involved, although you would certainly add that in a real-world application.

Script 1.2. Tasks are added to the database using this script. Tasks can even be filed under other tasks using the drop-down menu.


1    <!doctype html>
2    <html lang="en">
3    <head>
4       <meta charset="utf-8">
5       <title>Add a Task</title>
6       <link rel="stylesheet" href="style.css">
7    </head>
8    <body>
9    <?php # Script 1.2 - add_task.php
10
11   /* This page adds tasks to the tasks table.
12    * The page both displays and handles the form.
13    */
14
15   // Connect to the database:
16   $dbc = mysqli_connect('localhost', 'username', 'password', 'test');
17
18   // Check if the form has been submitted:
19   if (($_SERVER['REQUEST_METHOD'] == 'POST') && !empty($_POST['task'])) {
20
21      // Sanctify the input...
22
23      // The parent_id must be an integer:
24      if (isset($_POST['parent_id']) &&
25      filter_var($_POST['parent_id'], FILTER_VALIDATE_INT, array('min_range' => 1)) ) {
26         $parent_id = $_POST['parent_id'];
27      } else {
28         $parent_id = 0;
29      }
30
31      // Escape the task:
32      $task = mysqli_real_escape_string($dbc, strip_tags($_POST['task']));
33
34      // Add the task to the database.
35      $q = "INSERT INTO tasks (parent_id, task) VALUES ($parent_id, '$task')";
36      $r = mysqli_query($dbc, $q);
37
38      // Report on the results:
39      if (mysqli_affected_rows($dbc) == 1) {
40         echo '<p>The task has been added!</p>';
41      } else {
42         echo '<p>The task could not be added!</p>';
43      }
44
45   } // End of submission IF.
46
47   // Display the form:
48   echo '<form action="add_task.php" method="post">
49   <fieldset>
50      <legend>Add a Task</legend>
51      <p>Task: <input name="task" type="text" size="60" maxlength="100" required></p>
52      <p>Parent Task: <select name="parent_id"><option value="0">None</option>';
53
54   // Retrieve all the uncompleted tasks:
55   $q = 'SELECT task_id, parent_id, task FROM tasks WHERE date_completed="0000-00-00 00:00:00" ORDER BY date_added ASC';
56   $r = mysqli_query($dbc, $q);
57
58   // Also store the tasks in an array for use later:
59   $tasks = array();
60
61   // Fetch the records:
62   while (list($task_id, $parent_id, $task) = mysqli_fetch_array($r, MYSQLI_NUM)) {
63
64      // Add to the select menu:
65      echo "<option value=\"$task_id\">$task</option>\n";
66
67      // Add to the array:
68      $tasks[] = array('task_id' => $task_id, 'parent_id' => $parent_id, 'task' => $task);
69
70   }
71
72   // Complete the form:
73   echo '</select></p>
74   <input name="submit" type="submit" value="Add This Task">
75   </fieldset>
76   </form>';
77
78   // Sort the tasks by parent_id:
79   function parent_sort($x, $y) {
80      return ($x['parent_id'] > $y['parent_id']);
81   }
82   usort($tasks, 'parent_sort');
83
84   // Display all the tasks:
85   echo '<h2>Current To-Do List</h2><ul>';
86   foreach ($tasks as $task) {
87      echo "<li>{$task['task']}</li>\n";
88   }
89   echo '</ul>';
90   ?>
91   </body>
92   </html>


3. Check if the form has been submitted:

if (($_SERVER['REQUEST_METHOD'] == 'POST') && !empty($_POST['task'])) {

The form has one main text box and a drop-down menu image. To test for the form’s submission, the conditional checks that the request method is POST and that the text box (named task) isn’t empty. Certainly a more well-rounded script would do better, but that’s sufficient for now.

image

image The HTML form for adding tasks.

4. Ensure that the parent_id value is an integer:

if (isset($_POST['parent_id']) &&
filter_var($_POST['parent_id'], FILTER_VALIDATE_INT, array ('min_range' => 1)) ) {
  $parent_id = $_POST['parent_id'];
} else {
  $parent_id = 0;
}

The parent_id value is another task’s task_id. It will come from the drop-down menu, which means that it should be an integer. But you shouldn’t make assumptions (because if someone hacked the form to send text as the parent_id, it would break the query), so the Filter extension is used to guarantee that the value is an integer greater than 1. If that’s not the case, for whatever reason, 0 will be used instead.

The Filter extension was added to core PHP as of version 5.2. If you’re not familiar with it, see the PHP manual.

5. Secure the task value:

$task = mysqli_real_escape_string($dbc, strip_tags($_POST['task']));

The mysqli_real_escape_string() function will make whatever submitted task value safe to use in the query. To prevent Cross-Site Scripting (XSS) attacks, the task is also run through strip_tags().

6. Add the task to the database:

$q = "INSERT INTO tasks (parent_id, task) VALUES ($parent_id, '$task')";
$r = mysqli_query($dbc, $q);

7. Report on the query results:

if (mysqli_affected_rows($dbc) == 1) {
  echo '<p>The task has been added!</p>';
} else {
  echo '<p>The task could not be added!</p>';
}

8. Complete the submission conditional and start the form:

} // End of submission IF.
echo '<form action="add_task.php" method="post">
<fieldset>
  <legend>Add a Task</legend>
  <p>Task: <input name="task" type="text" size="60" maxlength="100" required></p>
  <p>Parent Task: <select name="parent_id"><option value="0">None</option>';

The form has one text input and one drop-down menu. The menu will be populated from the list of existing tasks. The first possible value will be 0, for tasks that are not subservient to other tasks.

9. Retrieve all the uncompleted tasks:

$q = 'SELECT task_id, parent_id, task FROM tasks WHERE date_completed="0000-00-00 00:00:00" ORDER BY date_added ASC';
$r = mysqli_query($dbc, $q);

The query returns three pieces of information for every uncompleted task (once a task has been completed, its date_completed column would have a nonzero value). I’m only selecting uncompleted tasks because it would not make sense to add a subtask to a task that has been completed.

The task_id and the task itself will be used in the drop-down menu. The parent_id will be used later to nest the tasks.

10. Create an array for storing the tasks:

$tasks = array();

This script will list all the tasks twice: once in the drop-down menu and once after the form image. This array will store the tasks to be used in the second list.

image

image The page contains the list of tasks two times.

11. Retrieve each database record and use it accordingly:

while (list($task_id, $parent_id, $task) = mysqli_fetch_array ($r, MYSQLI_NUM)) {
  echo "<option value=\"$task_id\">$task</option>\n"; $tasks[] = array('task_id' => $task_id, 'parent_id' => $parent_id, 'task' => $task);
}

Within the while loop the retrieved record is used to populate the drop-down menu image and is also stored in the $tasks array. This array will be multidimensional.

image

image The PHP-generated HTML source code for the drop-down menu.

12. Complete the form:

echo '</select></p>
<input name="submit" type="submit" value="Add This Task">
</fieldset>
</form>';


Type Hinting Function Parameters

PHP5 quietly added the ability to perform type hinting of a function’s parameters. Type hinting is the act of indicating what type a variable needs to be. For example, this code insists that the function’s single parameter is an array:

function f(array $input) {}

If the function is called without providing an array, an error will be triggered.

As of this writing, there are limitations as to what types of values you can hint. You can hint arrays and objects, but not scalar types such as strings and integers. As you’ll see later in the book, type hinting is most useful in object-oriented programming. The topic will be fully covered in Chapter 6, “More Advanced OOP.”


13. Sort the tasks by parent_id:

function parent_sort($x, $y) {
  return ($x['parent_id'] > $y['parent_id']);
}
usort($tasks, 'parent_sort');

The parent_id value is what separates primary tasks from secondary ones, so working with this value in the PHP script is important. Using the information discussed earlier in the chapter, a user-defined function will sort the multidimensional array.

14. Display the full list of uncompleted tasks:

echo '<h2>Current To-Do List</
h2><ul>';
foreach ($tasks as $task) {
  echo "<li>{$task['task']} </li>\n";
}
echo '</ul>';

This loop will display each task in order of its parent_id. This is the first step toward making the list shown in image, although as you can see in image, the list isn’t displayed quite as it should be yet. This will be solved later in the chapter.

15. Complete the page:

?>
</body>
</html>

16. Save the file as add_task.php, place it in your Web directory, and test in your Web browser image.

image

image Adding a task that’s a subset of an existing task.

Advanced Function Definitions

Being able to define and use your own functions is integral to any programming language. After gaining even a modicum of PHP experience, you’ve no doubt created many. But there are four potential features of user-defined functions that arise in more advanced programming. These are:

• Recursive functions

• Static variables

• Accepting values by reference

• Anonymous functions

While not often used, sometimes these concepts are indispensable. In discussing and demonstrating these first two concepts, I’ll continue to build on the tasks example just begun in the chapter.

Recursive functions

Recursion is the act of a function calling itself:

function somefunction() {
  // Some code.
  somefunction();
  // Possible other code.
}

The end result of a recursion is that the function’s code is executed repeatedly, as if called from within a loop.

Recursive functions are necessary when you have a process that would be followed to an unknown depth. For example, a script that searches through a directory may have to search through any number of subdirectories. That can easily be accomplished using recursion:

function list_dir($start) {
  $contents = scandir($start);
  foreach ($contents as $item) {
    if (is_dir("$start/$item") && (substr($item, 0, 1) != '.') ) {
      // Use $item.
      list_dir("$start/$item");
    } else {
      // Use $item.
    } // End of if-else.
  } // End of foreach.
} // End of function.
list_dir('.');

The function call—the last line—invokes the list_dir('.'); function, providing the current directory as the starting point. Within the function, scandir() gets the directory’s contents, and a foreach goes through each item. If an item is a directory, the function will be called again, using that new directory as the starting point. Recursion will continue down through all the subdirectories.

You need to be aware of two things when using recursion. The first is that, as with any loop, you need to ensure that there’s an “out”: a point at which the function will stop calling itself. In the above example, the function stops calling itself when it stops finding directories to iterate through.

The second issue is that recursion can be quite expensive in terms of server resources. Keep in mind that each function call requires memory and processing, and the very first call is not completed until they all are. Recursion that goes deeper than, say, 100 iterations could crash the server (depending on the server). Simply put, sometimes recursion is the only solution, but often the same need can be more efficiently addressed using a loop.

With the tasks table created earlier in the chapter, retrieving and displaying all the tasks is not hard (see image in the previous section). However, the method used in add_task.php (Script 1.2) does not properly nest the tasks like that in image (in the first section). To help you accomplish that desired end, this next new script requires a multidimensional array and a recursive function.

To use recursion

1. Begin a new PHP script in your text editor or IDE, to be named view_tasks.php, starting with the HTML (Script 1.3):

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>View Tasks</title>
  <link rel="stylesheet"
href="style.css">
</head>
<body>
<h2>Current To-Do List</h2>
<?php # Script 1.3 - view_tasks.php

2. Begin defining a recursive function:

function make_list($parent) {
  global $tasks;
  echo '<ol>';

The purpose of the function will be to convert an array of items into an ordered list:

<ol>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ol>

This function will take one argument, which will always be an array. Within the function, the $tasks array (the main array) needs to be available—you’ll soon see why. Then the ordered list is begun.

I will add that, generally speaking, it’s best not to use global variables, and so I’ll discuss alternative solutions later in the chapter.

Script 1.3. One recursive function and a multidimensional array will properly display the nested list of tasks.


1    <!doctype html>
2    <html lang="en">
3    <head>
4       <meta charset="utf-8">
5       <title>View Tasks</title>
6       <link rel="stylesheet" href="style.css">
7    </head>
8    <body>
9    <h2>Current To-Do List</h2>
10   <?php # Script 1.3 - view_tasks.php
11
12   /*  This page shows all existing tasks.
13    *  A recursive function is used to show the
14    *  tasks as nested lists, as applicable.
15    */
16
17   // Function for displaying a list.
18   // Receives one argument: an array.
19   function make_list($parent) {
20
21      // Need the main $tasks array:
22      global $tasks;
23
24      echo '<ol>'; // Start an ordered list.
25
26      // Loop through each subarray:
27      foreach ($parent as $task_id => $todo) {
28
29         // Display the item:
30         echo "<li>$todo";
31
32         // Check for subtasks:
33         if (isset($tasks[$task_id])) {
34            // Call this function again:
35            make_list($tasks[$task_id]);
36         }
37
38         echo '</li>'; // Complete the list item.
39
40      } // End of FOREACH loop.
41
42      echo '</ol>'; // Close the ordered list.
43
44   } // End of make_list() function.
45
46   // Connect to the database:
47   $dbc = mysqli_connect('localhost', 'username', 'password', 'test');
48
49   // Retrieve all the uncompleted tasks:
50   $q = 'SELECT task_id, parent_id, task FROM tasks WHERE date_completed="0000-00-00 00:00:00" ORDER BY parent_id, date_added ASC';
51   $r = mysqli_query($dbc, $q);
52
53   // Initialize the storage array:
54   $tasks = array();
55
56   // Loop through the results:
57   while (list($task_id, $parent_id, $task) = mysqli_fetch_array($r, MYSQLI_NUM)) {
58
59      // Add to the array:
60      $tasks[$parent_id][$task_id] = $task;
61
62   }
63
64   // For debugging:
65   //echo '<pre>' . print_r($tasks,1) . '</pre>';
66
67   // Send the first array element
68   // to the make_list() function:
69   make_list($tasks[0]);
70
71   ?>
72   </body>
73   </html>


3. Loop through the array, printing each item:

foreach ($parent as $task_id => $todo) {
  echo "<li>$todo";

foreach loop will go through the array, printing each item within <li> tags. Those tags are begun here.

4. Call this function again if any subtasks exist:

if (isset($tasks[$task_id])) {
  make_list($tasks[$task_id]);
}

This is the most important part of the script. The tasks retrieved from the database will be tossed into a multidimensional array image. For the main array, each key is a parent_id. The value for each key is an array of tasks that fall under that parent_id. Thus, after printing the initial <li> and task, the function needs to check if this task has any subtasks (i.e., is this task a parent?); in other words: is there an array element in $tasks whose key is this task ID? If so, then the current task has subtasks and this function should be called again, sending that other part of the array (the element whose key is this task_id and whose value is an array of subtasks) as the argument.

image

image The PHP script takes the tasks from the database and creates this multidimensional array.

The end results will be code like

<ol>
<li>Item 1</li>
<li>Item 2
  <ol>
  <li>Subitem 1</li>
  <li>Subitem 2</li>
  </ol>
</li>
<li>Item 3</li>
</ol>

5. Complete the foreach loop and the function:

    echo '</li>';
  } // End of FOREACH loop.
  echo '</ol>';
} // End of make_list() function.

6. Connect to the database:

$dbc = mysqli_connect('localhost', 'username', 'password', 'test');

With the recursive function defined, the rest of the script needs to retrieve all the tasks, organize them in an array, and then call the make_list() function once.

7. Define and execute the query:

$q = 'SELECT task_id, parent_id, task FROM tasks WHERE date_completed="0000-00-00 00:00:00" ORDER BY parent_id, date_added ASC';
$r = mysqli_query($dbc, $q);

The query retrieves three pieces of information for each task: its ID, its parent_id, and the task itself. The conditional means that only noncompleted tasks are selected. The results are also ordered by the parent_id so that every top-level task (with a parent_id of 0) is returned first. A secondary ordering by date_added returns the tasks in the order they were added (an assumption being that’s how they are prioritized).

8. Add each task to an array:

$tasks = array();
while (list($task_id, $parent_id, $task) = mysqli_fetch_array($r, MYSQLI_NUM)) {
  $tasks[$parent_id][$task_id] = $task;
}

The $tasks array will store every task in a two-dimensional array image. As described in Step 4, the outermost array uses the parent_id values from the table for its keys. The values of this outermost array’s elements are arrays of the tasks that have that parent_id.

9. Add a debugging line, if desired:

echo '<pre>' . print_r($tasks,1) . '</pre>';

When dealing with multidimensional arrays, it’s vitally important to confirm and understand the structure with which you’re working. When you uncomment this line (by removing the two slashes), the script will print out the array for debugging purposes, as in image.

10. Call the make_list() function, sending it the array of top-level tasks:

make_list($tasks[0]);

Although the $tasks variable is a multidimensional array, the make_list() function needs to be called only once, sending it the first array element. This element’s value is an array of tasks whose parent_id is 0. Within the function, for each of these tasks, a check will see if there are subtasks. In the end, the function will end up accessing every task thanks to its recursive nature.

11. Complete the page:

?>
</body>
</html>

12. Save the file as view_tasks.php, place it in your Web directory, and test in your Web browser image.

image

image The page of tasks, as a bunch of nested lists.

13. Add some more subtasks using add_task.php and retest this script in your Web browser image.

image

image There is no limit to the number of subtasks that this system supports.


Tip

This page does assume that some tasks were returned by the database. You could add a conditional checking that $tasks isn’t empty prior to calling the make_list() function.


Using static variables

When working with recursion or, in fact, any script in which the same function may be called multiple times, you might want to consider using the static statement. static forces the function to remember the value of a variable from function call to function call, without using global variables. For example, the make_list() function could be rewritten to take the full tasks list as an optional second argument and assign this to a static local variable:

function make_list($parent, $all = null) {
  static $tasks;
  if (isset($all)) {
    $tasks = $all;
  }

Now the function can be called initially using

make_list($tasks[0], $tasks);

Within the function, subsequent calls would just be

make_list($tasks[$task_id]);

Thanks to the use of a static variable and a little function redesign, no global variable would be required.

As a simple example of this, the very astute reader may have wondered how I achieved the result shown in image under “Sorting Multidimensional Arrays.” Showing the values being compared is not hard, but counting the iterations requires the use of static. To demonstrate, sort.php will be modified in the following steps.

To use static variables

1. Open sort.php (Script 1.1) in your text editor or IDE.

2. Modify the name_sort() function to read as follows (Script 1.4):

function name_sort ($x, $y) {
  static $count = 1;
  echo "<p>Iteration $count: {$x['name']} vs. {$y['name']} </p>\n";
  $count++;
  return strcasecmp($x['name'], $y['name']);
}

Script 1.4. This modified version of the sorting script reveals how many times each sorting function is invoked, thanks to a static variable.


1    <!doctype html>
2    <html lang="en">
3    <head>
4       <meta charset="utf-8">
5       <title>Sorting Multidimensional Arrays</title>
6       <link rel="stylesheet" href="style.css">
7    </head>
8    <body>
9    <?php # Script 1.4 - sort2.php
10
11   /*  This page creates a multidimensional array
12    *  of names and grades.
13    *  The array is then sorted twice:
14    *  once by name and once by grade.
15    *  A static variable has been added to both
16    *  functions to see how many times they are called.
17    */
18
19   // Create the array...
20   // Array structure:
21   // studentID => array('name' => 'Name', 'grade' => XX.X)
22   $students = array(
23      256 => array('name' => 'Jon', 'grade' => 98.5),
24      2 => array('name' => 'Vance', 'grade' => 85.1),
25      9 => array('name' => 'Stephen', 'grade' => 94.0),
26      364 => array('name' => 'Steve', 'grade' => 85.1),
27      68 => array('name' => 'Rob', 'grade' => 74.6)
28   );
29
30   // Name sorting function:
31   function name_sort($x, $y) {
32      // Show iterations using a static variable:
33      static $count = 1;
34      echo "<p>Iteration $count: {$x['name']} vs. {$y['name']}</p>\n";
35      $count++;
36      return strcasecmp($x['name'], $y['name']);
37   }
38
39   // Grade sorting function:
40   // Sort in DESCENDING order!
41   function grade_sort($x, $y) {
42      // Show iterations using a static variable:
43      static $count = 1;
44      echo "<p>Iteration $count: {$x['grade']} vs. {$y['grade']}</p>\n";
45      $count++;
46      return ($x['grade'] < $y['grade']);
47   }
48
49   // Sort by name:
50   uasort($students, 'name_sort');
51   echo '<h2>Array Sorted By Name</h2><pre>' . print_r($students, 1) . '</pre>';
52
53   // Sort by grade:
54   uasort($students, 'grade_sort');
55   echo '<h2>Array Sorted By Grade</h2><pre>' . print_r($students, 1) . '</pre>';
56
57   ?>
58   </body>
59   </html>


Three lines of code have been added to the function. The first is the declaration of the static variable $count. It’s initially set to 1, but that assignment only applies the first time this function is called (because it’s a static variable). Then the iteration number is printed (how many times this function has been called in this execution of the script), along with the values being compared. Finally, the $count variable is incremented.

3. Modify the grade_sort() function to read

function grade_sort($x, $y) {
  static $count = 1;
  echo "<p>Iteration $count: {$x['grade']} vs. {$y['grade']} </p>\n";
  $count++;
  return ($x['grade'] < $y['grade']);
}

The same three lines of code that were added to name_sort() are added to grade_sort(), except the key being compared here is grade, not name.

4. Save the file as sort2.php, place it in your Web directory, and test in your Web browser image.

image

image Sorting the original five-element array by name requires six calls of the sorting function.

(I’ve also removed from the script the line that prints the array’s original structure.)

5. Add more items to the $students array and rerun the script image.

image

image After adding three more elements to the main array, the name sort now requires 20 iterations.

Anonymous functions

A final more advanced topic that I’d like to cover is the use of anonymous functions, also called lambdas. Simply put, an anonymous function is a function without a name. The ability to create an anonymous function was added in PHP 5.3 and expanded in version 5.4.

Anonymous functions are created by defining a function as you would any other, but without a name. However, in order to be able to later reference that function (e.g., call it), the unnamed definition needs to be assigned to a variable:

$hello = function($who) {
  echo "<p>Hello, $who</p>";
};

This might seem like utter madness, but the premise is simply this: instead of assigning a string or number to the variable, you assign a function definition. For this reason, a semicolon is required at the end to complete the assignment statement.

Speaking of madness, to invoke this function (after it’s been defined), add parentheses to the variable’s name. Because this function takes an argument, put that within the parentheses image:

$hello('World!');
$hello('Universe!');

image

image The anonymous function is called through the variable.

This particular use of anonymous functions has its benefits, but there’s a more obvious and easier use of them we need to discuss. Several functions in PHP take a function as an argument. For example, the array_map() function takes a function as its first argument and an array whose elements will be run through that function as its second:

function format_names($value) {
  // Do whatever with $value.
}
array_map('format_names', $names);

As in the above, historically the name of the function was provided. If you don’t need a reusable format_names() function, you could just use an anonymous function by defining it inline:

array_map(function($value) {
  // Do whatever with $value.
}, $names);

The benefits of this approach are:

• Related code—the function definition and its implied invocation—is kept tightly together.

• PHP will only need to maintain the anonymous function’s definition while the function is being directly used.

The major downside to using anonymous functions is that it’s easy to create parse errors. If you find yourself having that problem, define the anonymous function separately and then move the entire definition into place (i.e., within the other function call).

If you’re paying close attention to this chapter, you’ve already seen another situation in which you could use anonymous functions: when sorting multidimensional arrays. In the following steps, you’ll update sort.php one last time to use anonymous functions. Note that you’ll only be able to execute this script if you’re using PHP 5.3 or later.

To use anonymous functions

1. Open sort.php (Script 1.4) in your text editor or IDE, if it is not already.

2. Remove the current definitions of the name_sort() and grade_sort() functions (Script 1.5).

These two functions will be defined anonymously, and therefore are no longer needed.

3. Modify the first invocation of uasort() to use an anonymous function:

uasort($students, function($x, $y) {
  return strcasecmp($x['name'], $y['name']);
});

The anonymous function definition is the same as in the original script. It takes two arguments and returns the case-sensitive comparison of the two strings.

4. Modify the second invocation of uasort() to use an anonymous function:

uasort($students, function ($x, $y) {
  return ($x['grade'] < $y['grade']);
});

Again, I’m returning to the original sorting function definition.

5. Save the file as sort3.php, place it in your Web directory, and test in your Web browser image.

image

image The end result is not affected by the use of anonymous functions.


Tip

Prior to version 5.3, you could create anonymous-like functions in PHP using create_function().



Tip

Anonymous functions can be used as closures, a fairly advanced concept. Closures are less common in PHP but are used frequently in JavaScript.


Script 1.5. This new take on sorting multidimensional arrays makes use of inline anonymous functions.


1    <!doctype html>
2    <html lang="en">
3    <head>
4       <meta charset="utf-8">
5       <title>Sorting Multidimensional Arrays</title>
6       <link rel="stylesheet" href="style.css">
7    </head>
8    <body>
9    <?php # Script 1.5 - sort3.php
10
11   /*  This page creates a multidimensional array
12    *  of names and grades.
13    *  The array is then sorted twice:
14    *  once by name and once by grade.
15    *  This version uses anonymous functions!
16    */
17
18   // Create the array...
19   // Array structure:
20   // studentID => array('name' => 'Name', 'grade' => XX.X)
21   $students = array(
22      256 => array('name' => 'Jon', 'grade' => 98.5),
23      2 => array('name' => 'Vance', 'grade' => 85.1),
24      9 => array('name' => 'Stephen', 'grade' => 94.0),
25      364 => array('name' => 'Steve', 'grade' => 85.1),
26      68 => array('name' => 'Rob', 'grade' => 74.6)
27   );
28
29   // Sort by name:
30   uasort($students, function($x, $y) {
31      return strcasecmp($x['name'], $y['name']);
32   });
33   echo '<h2>Array Sorted By Name</h2><pre>' . print_r($students, 1) . '</pre>';
34
35   // Sort by grade:
36   uasort($students, function ($x, $y) {
37      return ($x['grade'] < $y['grade']);
38   });
39   echo '<h2>Array Sorted By Grade</h2><pre>' . print_r($students, 1) . '</pre>';
40
41   ?>
42   </body>
43   </html>



References and Functions

As a default, function parameters are passed by value. This means that a function receives the value of a variable, not the actual variable itself. The function can also be described as making a copy of the variable. One result of this behavior is that changing the value within the function has no impact on the original variable outside of it:

function increment($var) {
  $var++;
}
$num = 2;
increment($num);
echo $num; // Still 2!

The alternative to this default behavior is to have a function’s parameters be passed by reference, instead of by value. There are two benefits to doing so. The first is it allows you to change an external variable within a function without making that variable global. The second benefit is one of performance. In situations where the data being passed is large, passing by reference means that PHP will not need to make a duplicate of that data. For strings and numbers, the duplication is not an issue, but for a large data set as in the tasks example, it would be better if PHP did not have to make that copy.

To pass a variable by reference instead of by value, precede the variable in the parameter list with the ampersand (&):

function increment(&$var) {
  $var++;
}
$num = 2;
increment($num);
echo $num; // 3

Alternatively, the function definition can stay the same and how the function is called would change:

function increment($var) {
  $var++;
}
$num = 2;
increment(&$num);
echo $num; // 3

You probably won’t (or shouldn’t) find yourself passing values by reference often, but like the other techniques in this chapter, it’s often the perfect solution to an advanced problem.


The Heredoc Syntax

Heredoc is an alternative way for encapsulating strings. It’s used and seen much less often than the standard single or double quotes, but it fulfills the same role. Heredoc is like putting peanut butter on bananas: you either grow up doing it or you don’t.

The heredoc approach works just like a double quote in that the values of variables will be printed but you can define your own delimiter. Heredoc is a particularly nice alternative to using double quotation marks when you are printing oodles of HTML (which normally has its own double quotation marks). The only catch to heredoc is that its syntax is very particular!

The heredoc syntax starts with <<<, immediately followed by an identifier. The identifier is normally a word in all caps. It can only contain alphanumeric characters plus the underscore (no spaces), and it cannot begin with a number. There should be nothing on the same line after the initial identifier, not even a space! Use of heredoc might begin like

echo <<<EOT
blah...

or

$string = <<<EOD
blah...

At the end of the string, use the same identifier without the <<<. The closing identifier has to be the very first item on the line (it cannot be indented at all) and can only be followed by a semicolon!

Examples image:

$var = 23;
$that ='test';
echo <<<EOT
Somevar $var
Thisvar $that
EOT;
$string = <<<EOD
string with $var \n
EOD;
echo $string;

image

image As you can see from this output, the heredoc syntax has the same end result as using double quotation marks.

Using EOD and EOT as delimiters is common (they’re unlikely to show up in the string) but not required. The heredoc syntax is a nice option but—and I’m trying to drive this point home—it’s very particular. Failure to get the syntax 100 percent correct—even an errant space—results in a parse error.

To illustrate, let’s write a new version of the view_tasks.php page that allows for marking tasks as updated image.

image

image The page for viewing tasks will now have check boxes to mark tasks as complete.

To use the heredoc syntax

1. Open view_tasks.php (Script 1.3) in your text editor or IDE.

2. Within the make_list() function, change the printing of the task to (Script 1.6):

echo <<<EOT
<li><input type="checkbox" name="tasks[$task_id]" value="done"> $todo
EOT;

Script 1.6. The original view_tasks.php listing (Script 1.3) has been modified as a form so that tasks can be checked off. The heredoc syntax aids in the creation of some of the HTML.


1    <!doctype html>
2    <html lang="en">
3    <head>
4       <meta charset="utf-8">
5       <title>View Tasks</title>
6       <link rel="stylesheet" href="style.css">
7    </head>
8    <body>
9    <h2>Current To-Do List</h2>
10   <?php # Script 1.6 - view_tasks2.php
11
12   /*  This page shows all existing tasks.
13    *  A recursive function is used to show the
14    *  tasks as nested lists, as applicable.
15    *  Tasks can now be marked as completed.
16    */
17
18   // Function for displaying a list.
19   // Receives one argument: an array.
20   function make_list ($parent) {
21      global $tasks;
22      echo '<ol>'; // Start an ordered list.
23      foreach ($parent as $task_id => $todo) {
24
25         // Start with a checkbox!
26         echo <<<EOT
27   <li><input type="checkbox" name="tasks[$task_id]" value="done"> $todo
28   EOT;
29
30         // Check for subtasks:
31         if (isset($tasks[$task_id])) {
32            make_list($tasks[$task_id]);
33         }
34         echo '</li>'; // Complete the list item.
35      } // End of FOREACH loop.
36      echo '</ol>'; // Close the ordered list.
37   } // End of make_list() function.
38
39   // Connect to the database:
40   $dbc = mysqli_connect('localhost', 'username', 'password', 'test');
41
42   // Check if the form has been submitted:
43   if (($_SERVER['REQUEST_METHOD'] == 'POST')
44      && isset($_POST['tasks'])
45      && is_array($_POST['tasks'])
46      && !empty($_POST['tasks'])) {
47
48      // Define the query:
49      $q = 'UPDATE tasks SET date_completed=NOW() WHERE task_id IN (';
50
51      // Add each task ID:
52      foreach ($_POST['tasks'] as $task_id => $v) {
53         $q .= $task_id . ', ';
54      }
55
56      // Complete the query and execute:
57      $q = substr($q, 0, -2) . ')';
58      $r = mysqli_query($dbc, $q);
59
60      // Report on the results:
61      if (mysqli_affected_rows($dbc) == count($_POST['tasks'])) {
62         echo '<p>The task(s) have been marked as completed!</p>';
63      } else {
64         echo '<p>Not all tasks could be marked as completed!</p>';
65      }
66
67   } // End of submission IF.
68
69   // Retrieve all the uncompleted tasks:
70   $q = 'SELECT task_id, parent_id, task FROM tasks WHERE date_completed="0000-00-00 00:00:00" ORDER BY parent_id, date_added ASC';
71   $r = mysqli_query($dbc, $q);
72   $tasks = array();
73   while (list($task_id, $parent_id, $task) = mysqli_fetch_array($r, MYSQLI_NUM)) {
74      $tasks[$parent_id][$task_id] =   $task;
75   }
76
77   // Make a form:
78   echo <<<EOT
79   <p>Check the box next to a task and click "Update" to mark a task as completed (it, and any subtasks, will no longer appear in this list).</p>
80   <form action="view_tasks2.php" method="post">
81   EOT;
82
83   make_list($tasks[0]);
84
85   // Complete the form:
86   echo <<<EOT
87   <input name="submit" type="submit" value="Update" />
88   </form>
89   EOT;
90
91   ?>
92   </body>
93   </html>


This is a good use of the heredoc syntax, as it’s an alternative to:

echo "<li><input type=\"checkbox\" name=\"tasks[$task_id]\" value=\"done\">$todo";

That syntax, in my opinion, has way too many double quotation marks to escape. The single quotation mark example isn’t as bad but requires concatenation:

echo '<li><input type="checkbox" name="tasks['. $task_id . ']" value="done"> ' . $todo;

With the heredoc code, be absolutely certain that nothing follows the opening identifier (EOT) except a return (a carriage return or newline) and that the closing identifier starts as the very first thing on its own line.

3. After connecting to the database, begin a conditional that checks for the form submission:

if (($_SERVER['REQUEST_METHOD'] == 'POST')
  && isset($_POST['tasks'])
  && is_array($_POST['tasks'])
  && !empty($_POST['tasks'])) {

The database update (marking the tasks as complete) will only occur if the form has been submitted, $_POST['tasks'] has a value, and it is a non-empty array. Even if only one check box is selected, $_POST['tasks'] would still be an array.

4. Dynamically generate the query:

$q = 'UPDATE tasks SET date_completed=NOW() WHERE task_id IN (';
foreach ($_POST['tasks'] as $task_id => $v) {
  $q .= $task_id . ', ';
}
$q = substr($q, 0, -2) . ')';
$r = mysqli_query($dbc, $q);

The update query will be something like

UPDATE tasks SET date_completed=NOW() WHERE task_id IN (X, Y, Z)

This will set each applicable task’s date_completed column to the current date and time so that it will no longer show up in the view list (because that query checks for an empty date_completed value).

5. Report on the results and complete the submission conditional:

  if (mysqli_affected_rows($dbc) == count($_POST['tasks'])) {
    echo '<p>The task(s) have been marked as completed!</p>';
  } else {
    echo '<p>Not all tasks could be marked as completed!</p>';
  }
} // End of submission IF.

6. Before calling the make_list() function, add the initial form tag:

echo <<<EOT
<p>Check the box next to a task and click "Update" to mark a task as completed (it, and any subtasks, will no longer appear in this list).</p>
<form action="view_tasks2.php" method="post">
EOT;

Because of the way the make_list() function works, if a parent task is marked as completed, its subtasks will never be shown. A comment indicating such is added to the form.

I’m using heredoc syntax again here, mostly because that’s the focus of this particular example.

7. After calling the make_list() function, complete the form:

echo <<<EOT
<input name="submit" type="submit" value="Update" />
</form>
EOT;

8. Save the file as view_tasks2.php, place it in your Web directory, and test in your Web browser by checking tasks to be marked as completed image and submitting the form image.

image

image The updated tasks list.


The Nowdoc Syntax

Added in PHP 5.3 is the nowdoc syntax. Nowdoc is to heredoc as single quotes are to double quotes. This is to say that nowdoc provides another way to encapsulate a string, but any variables within the nowdoc syntax will not be replaced with their values.

In terms of syntax, nowdoc uses the same rules as heredoc except that the delimiting string needs to be placed within single quotes on the first line:

$var = 23;
$string = <<<'EOD'
string with $var
EOD;

To be clear, $string now has the literal value of string with $var, not string with 23.


Using printf() and sprintf()

For most PHP programmers, the print() and echo() functions are all they need for printing text and variables. The advanced PHP programmer might occasionally use the more sophisticated printf() function. This function also prints text but has the added ability to format the output. The PHP manual definition of this function is

printf(string format [, mixed arguments]);

The format is a combination of literal text and special formatting parameters, beginning with the percent sign (%). After that, you may have any combination of the following (in order):

• A sign specifier (+/-) to force a positive number to show the plus sign.

• A padding specifier that indicates the character used for right-padding (space is the default, but you might want to use 0 for numbers).

• An alignment specifier (default is right-justified, use - to force left-justification).

• A number indicating the minimum width to be used.

• A precision specifier for how many decimal digits should be shown for floating-point numbers (or how many characters in a string).

• The type specifier; see Table 1.1.

Table 1.1. Type Specifiers

image

This all may seem complicated, and well, it kind of is. You can start practicing by playing with a number image:

printf('b: %b <br>c: %c <br>d: %d <br>f: %f', 80, 80, 80, 80);

image

image The same number printed using four different type specifiers.

That’s four different representations of the same number. The first format will print 80 as a binary number, the second as 80’s corresponding ASCII character (the capital letter P), the third as an integer, and the fourth as a floating-point number.

From there, take the two most common number types—d and f—and add some formatting image:

printf('%0.2f <br>%+d <br>%0.2f <br>', 8, 8, 1235.456);

image

image Using printf() to format how numbers are printed.

First, the number 8 is printed as a floating-point number, with two digits after the decimal and padded with zeros. Next, the number 8 is printed as a signed integer. Finally, the number 1235.456 is printed as a floating-point number with two digits after the decimal (resulting in the rounding of the number).

Taking this idea further, mix in the string type image:

printf('The cost of %d %s at $%0.2f each is $%0.2f.', 4, 'brooms', 8.50, (4*8.50));

image

image Printing a mix of numbers and strings.

The sprintf() function works exactly like printf(), but instead of printing the formatted string, it returns it. This function is great for generating database queries, without an ugly mixing of SQL and variables (and potentially function calls).

To use sprintf()

1. Open add_task.php (Script 1.2) in your text editor or IDE.

2. Delete the call to the mysqli_real_escape_string() function (Script 1.7).

I’ll now call this function in the line that defines the query (Step 3).

3. Change the line that defines the INSERT query to read

$q = sprintf("INSERT INTO tasks (parent_id, task) VALUES (%d, '%s')", $parent_id, mysqli_real_escape_string($dbc, strip_tags($_POST['task'])));

When you use the sprintf() function, the query can be created without interspersing SQL and variables. While doing so wasn’t too ugly in the original script, in more complex queries the result can be hideous (lots of {$var['index']} and such), prone to errors, and hard to debug.

This syntax separates the query from the data being used and is still able to incorporate a function call, all without using concatenation or other techniques.

Script 1.7. A minor modification to the add_task.php page (Script 1.1) shows an alternative way to create a database query.


1    <!doctype html>
2    <html lang="en">
3    <head>
4       <meta charset="utf-8">
5       <title>Add a Task</title>
6       <link rel="stylesheet" href="style.css">
7    </head>
8    <body>
9    <?php # Script 1.7 - add_task2.php
10
11   /* This page adds tasks to the tasks table.
12    * The page both displays and handles the form.
13    *
14    */
15
16   $dbc = mysqli_connect('localhost', 'username', 'password', 'test');
17
18   if (($_SERVER['REQUEST_METHOD'] == 'POST') && !empty($_POST['task'])) {
19      if (isset($_POST['parent_id']) &&
20      filter_var($_POST['parent_id'], FILTER_VALIDATE_INT, array('min_range' => 1)) ) {
21         $parent_id = $_POST['parent_id'];
22      } else {
23         $parent_id = 0;
24      }
25
26      // Add the task to the database.
27      $q = sprintf("INSERT INTO tasks (parent_id, task) VALUES (%d, '%s')", $parent_id, mysqli_real_escape_string($dbc, strip_tags($_POST['task'])));
28      $r = mysqli_query($dbc, $q);
29
30      if (mysqli_affected_rows($dbc) == 1) {
31         echo '<p>The task has been added!</p>';
32      } else {
33         echo '<p>The task could not be added!</p>';
34      }
35
36   } // End of submission IF.
37
38   // Display the form:
39   echo '<form action="add_task2.php" method="post">
40   <fieldset>
41      <legend>Add a Task</legend>
42      <p>Task: <input name="task" type="text" size="60" maxlength="100"></p>
43      <p>Parent Task: <select name="parent_id"><option value="0">None</option>';
44   $q = 'SELECT task_id, parent_id, task FROM tasks WHERE date_completed="0000-00-00 00:00:00" ORDER BY date_added ASC';
45   $r = mysqli_query($dbc, $q);
46   $tasks = array();
47   while (list($task_id, $parent_id, $task) = mysqli_fetch_array($r, MYSQLI_NUM)) {
48      echo "<option value=\"$task_id\">$task</option>\n";
49      $tasks[] = array('task_id' => $task_id, 'parent_id' => $parent_id, 'task' => $task);
50   }
51   echo '</select></p>
52   <input name="submit" type="submit" value="Add This Task">
53   </fieldset>
54   </form>';
55   ?>
56   </body>
57   </html>


4. Change the action attribute of the form to add_task2.php.

This script will be renamed to differentiate it from the original, and so the action attribute’s value must be changed to match.

5. If you want, remove all the lines required to view the list of tasks.

The view_tasks.php page (and its second version) both do this much better, so there’s no need to still include that code here.

6. Save the file as add_task2.php, place it in your Web directory, and test in your Web browser image.

image

image The page should still work exactly as it had before.


Tip

To use a literal percent sign in a string, escape it with another percent sign:

printf('The tax rate is %0.2f%%', $tax);



Tip

The vprintf() function works exactly like printf() but takes only two arguments: the format and an array of values.



Tip

The scanf() and fscanf() functions also work exactly like printf() and sprintf() in terms of formatting parameters. The scanf() function is used for reading input; fscanf() is used to read data from a file.


Review and Pursue

New in this edition of the book, each chapter ends with a “Review and Pursue” section. In these sections, you’ll find questions regarding the material just covered and prompts for ways to expand your knowledge and experience on your own.

If you have any problems with these sections, either in answering the questions or pursuing your own endeavors, turn to the book’s supporting forum (www.LarryUllman.com/forums/). I’ve also provided page references below in case you cannot remember a particular answer off the top off your head.

Review

• What PHP function is used to sort a multidimensional array by value? To sort the multidimensional array while maintaining the keys? To sort the multidimensional array by key? (See page 3.)

• When writing a function to be used to sort a multidimensional array, what values should it return? (See page 3.)

• What is a recursive function? In what situations are they useful? What are the two things to be careful of when using them? (See page 17.)

• What is a static variable? In what situations are static variables helpful? (See page 24.)

• What is an anonymous function? When were anonymous functions added to PHP? (See page 27.)

• What are the rules for using the heredoc syntax? What advantages does heredoc have over alternative approaches? (See page 31.)

• How do you use the printf() and sprintf() functions? What benefits do they offer? (See page 37.)

Pursue

• Modify sort.php to use a custom function to generate the output in a more attractive manner.

• Round out add_task.php so that it includes proper error reporting.

• Implement the recursive directory function example to list the contents of a directory using nested unordered lists.

• Using one of the suggestions in the chapter, such as passing by reference or using static variables, update make_list() so that it does not require a global variable.

• If you’re curious, modify one of the function definitions in this chapter to require an array as an argument using type hinting.

• Search online for more examples of anonymous functions in PHP, if you’re curious.

• Update view_tasks.php to display both completed and to-be-completed tasks, formatting each differently.

• Add a link to view_tasks.php that passes a value in the URL indicating whether all tasks should be displayed, or just the incomplete ones. Change the SQL query based on this value.

• Modify add_task.php so that the drop-down menu reflects the hierarchy of tasks, too.

• Look into other uses of printf() and sprintf(). Hint: A good place to start is the PHP manual pages for these functions.