Chapter 4. Arrays

Introduction

Arrays are lists: lists of people, lists of sizes, lists of books. To store a group of related items in a variable, use an array. Like a list on a piece of paper, the elements in array have an order. Usually, each new item comes after the last entry in the array, but just as you can wedge a new entry between a pair of lines already in a paper list, you can do the same with arrays in PHP.

In many languages, there is only one type of array: this is called a numerical array (or just an array). In a numerical array, if you want to find an entry, you need to know its position within the array, known as an index. Positions are identified by numbers: they start at 0 and work upward one by one.

In some languages, there is also another type of array: an associative array, also known as a hash. In an associative array, indexes aren’t integers, but strings. So in a numerical array of U.S. presidents, “Abraham Lincoln” might have index 16; in the associative-array version, the index might be “Honest.” However, while numerical arrays have a strict ordering imposed by their keys, associative arrays frequently make no guarantees about the key ordering. Elements are added in a certain order, but there’s no way to determine the order later.

In a few languages, there are both numerical and associative arrays. But usually the numerical array $presidents and the associative array $presidents are distinct arrays. Each array type has a specific behavior, and you need to operate on it accordingly. PHP has both numerical and associative arrays, but they don’t behave independently.

In PHP, numerical arrays are associative arrays, and associative arrays are numerical arrays. So which kind are they really? Both and neither. The line between them constantly blurs back and forth from one to another. At first, this can be disorienting, especially if you’re used to rigid behavior, but soon you’ll find this flexibility an asset.

To assign multiple values to an array in one step, use array():

$fruits = array('Apples', 'Bananas', 'Cantaloupes', 'Dates');

Now, the value of $fruits[2] is 'Cantaloupes'.

array() is very handy when you have a short list of known values. The same array is also produced by:

$fruits[0] = 'Apples';
$fruits[1] = 'Bananas';
$fruits[2] = 'Cantaloupes';
$fruits[3] = 'Dates';

and:

$fruits[] = 'Apples';
$fruits[] = 'Bananas';
$fruits[] = 'Cantaloupes';
$fruits[] = 'Dates';

Assigning a value to an array with an empty subscript is shorthand for adding a new element to the end of the array. So PHP looks up the length of $fruits and uses that as the position for the value you’re assigning. This assumes, of course, that $fruits isn’t set to a scalar value, such as 3, and isn’t an object. PHP complains if you try to treat a non-array as an array; however, if this is the first time you’re using this variable, PHP automatically converts it to an array and begins indexing at 0.

An identical feature is the function array_push(), which pushes a new value on top of the array stack. However, the $foo[] notation is the more traditional PHP style; it’s also faster. But sometimes, using array_push() more accurately conveys the stack nature of what you’re trying to do, especially when combined with array_pop() , which removes the last element from an array and returns it.

So far, we’ve placed integers and strings only inside arrays. However, PHP allows you to assign any data type you want to an array element: booleans, integers, floating-point numbers, strings, objects, resources, NULL, and even other arrays. So you can pull arrays or objects directly from a database and place them into an array:

while ($row = mysql_fetch_row($r)) {
    $fruits[] = $row;
}

while ($obj = mysql_fetch_object($s)) {
    $vegetables[] = $obj;
}

The first while statement creates an array of arrays; the second creates an array of objects. See Recipe 4.2 for more on storing multiple elements per key.

To define an array using not integer keys but string keys, you can also use array(), but specify the key/value pairs with =>:

$fruits = array('red' => 'Apples', 'yellow' => 'Bananas', 
                'beige' => 'Cantaloupes', 'brown' => 'Dates');

Now, the value of $fruits['beige'] is 'Cantaloupes'. This is shorthand for:

$fruits['red'] = 'Apples';
$fruits['yellow'] = 'Bananas';
$fruits['beige'] = 'Cantaloupes';
$fruits['brown'] = 'Dates';

Each array can only hold one unique value for each key. Adding:

$fruits['red'] = 'Strawberry';

overwrites the value of 'Apples'. However, you can always add another key at a later time:

$fruits['orange'] = 'Orange';

The more you program in PHP, the more you find yourself using associative arrays instead of numerical ones. Instead of creating a numeric array with string values, you can create an associative array and place your values as its keys. If you want, you can then store additional information in the element’s value. There’s no speed penalty for doing this, and PHP preserves the ordering. Plus, looking up or changing a value is easy because you already know the key.

The easiest way to cycle though an array and operate on all or some of the elements inside is to use foreach:

$fruits = array('red' => 'Apples', 'yellow' => 'Bananas', 
                'beige' => 'Cantaloupes', 'brown' => 'Dates');

foreach ($fruits as $color => $fruit) {
    print "$fruit are $color.\n";
}
Apples are red.
Bananas are yellow.
Cantaloupes are beige.
Dates are brown.

Each time through the loop, PHP assigns the next key to $color and the key’s value to $fruit. When there are no elements left in the array, the loop finishes.

To break an array apart into individual variables, use list():

$fruits = array('Apples', 'Bananas', 'Cantaloupes', 'Dates');

list($red, $yellow, $beige, $brown) = $fruits;

4.1. Specifying an Array Not Beginning at Element 0

Problem

You want to assign multiple elements to an array in one step, but you don’t want the first index to be 0.

Solution

Instruct array() to use a different index using the => syntax:

$presidents = array(1 => 'Washington', 'Adams', 'Jefferson', 'Madison');

Discussion

Arrays in PHP—like most, but not all, computer languages—begin with the first entry located at index 0. Sometimes, however, the data you’re storing makes more sense if the list begins at 1. (And we’re not just talking to recovering Pascal programmers here.)

In the Solution, George Washington is the first president, not the zeroth, so if you wish to print a list of the presidents, it’s simpler to do this:

foreach ($presidents as $number => $president) {
    print "$number: $president\n";
}

than this:

foreach ($presidents as $number => $president) {
    $number++;
    print "$number: $president\n";
}

The feature isn’t restricted to the number 1; any integer works:

$reconstruction_presidents = array(16 => 'Lincoln', 'Johnson', 'Grant');

Also, you can use => multiple times in one call:

$whig_presidents = array(9 => 'Harrison', 'Tyler',[2] 12 => 'Taylor', 'Fillmore');

PHP even allows you to use negative numbers in the array() call. (In fact, this method works for non-integer keys, too.) What you’ll get is technically an associative array, although as we said, the line between numeric arrays and associative arrays is often blurred in PHP; this is just another one of these cases:

$us_leaders = array(-1 => 'George II', 'George III', 'Washington');

If Washington is the first U.S. leader, George III is the zeroth, and his grandfather George II is the negative-first.

Of course, you can mix and match numeric and string keys in one array() definition, but it’s confusing and very rarely needed:

$presidents = array(1 => 'Washington', 'Adams', 'Honest' => 'Lincoln', 'Jefferson');

This is equivalent to:

$presidents[1]         = 'Washington';   // Key is 1
$presidents[]          = 'Adams';        // Key is 1 + 1 => 2
$presidents['Honest']  = 'Lincoln';      // Key is 'Honest'
$presidents[]          = 'Jefferson';    // Key is 2 + 1 => 3

See Also

Documentation on array() at http://www.php.net/array.

4.2. Storing Multiple Elements Per Key in an Array

Problem

You want to associate multiple elements with a single key.

Solution

Store the multiple elements in an array:

$fruits = array('red' => array('strawberry','apple'),
                'yellow' => array('banana'));

Or use an object:

while ($obj = mysql_fetch_object($r)) {
    $fruits[] = $obj;
}

Discussion

In PHP, keys are unique per array, so you can’t associate more than one entry in a key without overwriting the old value. Instead, store your values in an anonymous array:

$fruits['red'][] = 'strawberry';
$fruits['red'][] = 'apple';
$fruits['yellow'][] = 'banana';

Or, if you’re processing items in a loop:

while (list($color,$fruit) = mysql_fetch_array($r)) {
    $fruits[$color][] = $fruit;
}

To print the entries, loop through the array:

foreach ($fruits as $color=>$color_fruit) {
    // $color_fruit is an array
    foreach ($color_fruit as $fruit) {
        print "$fruit is colored $color.<br>";
    }
}

Or use the pc_array_to_comma_string() function from Recipe 4.9.

foreach ($fruits as $color=>$color_fruit) {
    print "$color colored fruits include " . 
        pc_array_to_comma_string($color_fruit) . "<br>";
}

In PHP 5.0.0 and above, you don’t need pc_array_range(): just pass an increment to range() as a third argument:

$odd = range(1, 52, 2);
$even = range(2, 52, 2);

See Also

Recipe 4.9 for how to print arrays with commas.

4.3. Initializing an Array to a Range of Integers

Problem

You want to assign a series of consecutive integers to an array.

Solution

Use range($start, $stop):

$cards = range(1, 52);

Discussion

For increments other than 1, you can use:

function pc_array_range($start, $stop, $step) {
    $array = array();
    for ($i = $start; $i <= $stop; $i += $step) {
        $array[] = $i;
    }
    return $array;
}

So for odd numbers:

$odd = pc_array_range(1, 52, 2);

And for even numbers:

$even = pc_array_range(2, 52, 2);

In PHP 5.0.0 and above, you don’t need pc_array_range(): just pass an increment to range() as a third argument:

$odd = range(1, 52, 2);
$even = range(2, 52, 2);

See Also

Recipe 2.4 for how to operate on a series of integers; documentation on range() at http://www.php.net/range.

4.4. Iterating Through an Array

Problem

You want to cycle though an array and operate on all or some of the elements inside.

Solution

Use foreach:

foreach ($array as $value) {
    // Act on $value
}

Or to get an array’s keys and values:

foreach ($array as $key => $value) {
    // Act II
}

Another technique is to use for:

for ($key = 0, $size = count($array); $key < $size; $key++) {
   // Act III
}

Finally, you can use each() in combination with list() and while:

reset($array) // reset internal pointer to beginning of array
while (list($key, $value) = each ($array)) {
    // Final Act
}

Discussion

A foreach loop is the most concise to iterate through an array:

// foreach with values
foreach ($items as $cost) {
    ...
}

// foreach with keys and values
foreach($items as $item => $cost) {
    ...
}

With foreach, PHP iterates over a copy of the array instead of the actual array. In contrast, when using each() and for, PHP iterates over the original array. So if you modify the array inside the loop, you may (or may not) get the behavior you expect.

If you want to modify the array, reference it directly:

reset($items);
while (list($item, $cost) = each($items)) {
    if (! in_stock($item)) { 
        unset($items[$item]);           // address the array directly
    }
}

The variables returned by each() aren’t aliases for the original values in the array: they’re copies, so if you modify them, it’s not reflected in the array. That’s why you need to modify $items[$item] instead of $item.

When using each(), PHP keeps track of where you are inside the loop. After completing a first pass through, to begin again at the start, call reset() to move the pointer back to the front of the array. Otherwise, each() returns false.

The for loop works only for arrays with consecutive integer keys. Unless you’re modifying the size of your array, it’s inefficient to recompute the count() of $items each time through the loop, so we always use a $size variable to hold the array’s size:

for ($item = 0, $size = count($items); $item < $size; $item++) {
    ...
}

If you prefer to count efficiently with one variable, count backward:

for ($item = count($items) - 1; $item >= 0; $item--) {
    ...
}

The associative array version of the for loop is:

for (reset($array); $key = key($array); next($array) ) {
    ...
}

This fails if any element holds a string that evaluates to false, so a perfectly normal value such as 0 causes the loop to end early.

Finally, use array_map() to hand off each element to a function for processing:

// lowercase all words
$lc = array_map('strtolower', $words);

The first argument to array_map() is a function to modify an individual element, and the second is the array to be iterated through.

Generally, we find this function less flexible than the previous methods, but it is well-suited for the processing and merging of multiple arrays.

If you’re unsure if the data you’ll be processing is a scalar or an array, you need to protect against calling foreach with a non-array. One method is to use is_array():

if (is_array($items)) {
    // foreach loop code for array
} else {
    // code for scalar
}

Another method is to coerce all variables into array form using settype():

settype($items, 'array');
// loop code for arrays

This turns a scalar value into a one-element array and cleans up your code at the expense of a little overhead.

See Also

Documentation on for at http://www.php.net/for, foreach at http://www.php.net/foreach, while at http://www.php.net/while, each() at http://www.php.net/each, reset() at http://www.php.net/reset, and array_map() at http://www.php.net/array-map .

4.5. Deleting Elements from an Array

Problem

You want to remove one or more elements from an array.

Solution

To delete one element, use unset():

unset($array[3]);
unset($array['foo']);

To delete multiple noncontiguous elements, also use unset():

unset($array[3], $array[5]);
unset($array['foo'], $array['bar']);

To delete multiple contiguous elements, use array_splice():

array_splice($array, $offset, $length);

Discussion

Using these functions removes all references to these elements from PHP. If you want to keep a key in the array, but with an empty value, assign the empty string to the element:

$array[3] = $array['foo'] = '';

Besides syntax, there’s a logical difference between using unset() and assigning '' to the element. The first says, “This doesn’t exist anymore,” while the second says, “This still exists, but its value is the empty string.”

If you’re dealing with numbers, assigning 0 may be a better alternative. So if a company stopped production of the model XL1000 sprocket, it would update its inventory with:

unset($products['XL1000']);

However, if the company temporarily ran out of XL1000 sprockets but was planning to receive a new shipment from the plant later this week, this is better:

$products['XL1000'] = 0;

If you unset() an element, PHP adjusts the array so that looping still works correctly. It doesn’t compact the array to fill in the missing holes. This is what we mean when we say that all arrays are associative, even when they appear to be numeric. Here’s an example:

// create a "numeric" array
$animals = array('ant', 'bee', 'cat', 'dog', 'elk', 'fox');
print $animals[1];  // prints 'bee'
print $animals[2];  // prints 'cat'
count($animals);    // returns 6

// unset()
unset($animals[1]); // removes element $animals[1] = 'bee'
print $animals[1];  // prints '' and throws an E_NOTICE error
print $animals[2];  // still prints 'cat'
count($animals);    // returns 5, even though $array[5] is 'fox'

// add new element
$animals[] = 'gnu'; // add new element (not Unix)
print $animals[1];  // prints '', still empty
print $animals[6];  // prints 'gnu', this is where 'gnu' ended up
count($animals);    // returns 6 

// assign ''
$animals[2] = '';   // zero out value
print $animals[2];  // prints ''
count($animals);    // returns 6, count does not decrease

To compact the array into a densely filled numeric array, use array_values():

$animals = array_values($animals);

Alternatively, array_splice() automatically reindexes arrays to avoid leaving holes:

// create a "numeric" array
$animals = array('ant', 'bee', 'cat', 'dog', 'elk', 'fox');
array_splice($animals, 2, 2);
print_r($animals);
Array
(
    [0] => ant
    [1] => bee
    [2] => elk
    [3] => fox
)

This is useful if you’re using the array as a queue and want to remove items from the queue while still allowing random access. To safely remove the first or last element from an array, use array_shift() and array_pop(), respectively.

However, if you find yourself often running into problems because of holes in arrays, you may not be “thinking PHP.” Look at the ways to iterate through the array in Recipe 4.4 that don’t involve using a for loop.

See Also

Recipe 4.4 for iteration techniques; documentation on unset() at http://www.php.net/unset, array_splice() at http://www.php.net/array-splice, and array_values() at http://www.php.net/array-values .

4.6. Changing Array Size

Problem

You want to modify the size of an array, either by making it larger or smaller than its current size.

Solution

Use array_pad() to make an array grow:

// start at three
$array = array('apple', 'banana', 'coconut');

// grow to five
$array = array_pad($array, 5, '');

Now, count($array) is 5, and the last two elements, $array[3] and $array[4], contain the empty string.

To reduce an array, you can use array_splice():

// no assignment to $array
array_splice($array, 2);

This removes all but the first two elements from $array.

Discussion

Arrays aren’t a predeclared size in PHP, so you can resize them on the fly.

To pad an array, use array_pad(). The first argument is the array to be padded. The next argument is the size and direction you want to pad. To pad to the right, use a positive integer; to pad to the left, use a negative one. The third argument is the value to be assigned to the newly created entries. The function returns a modified array and doesn’t alter the original.

Here are some examples:

// make a four-element array with 'dates' to the right
$array = array('apple', 'banana', 'coconut');
$array = array_pad($array, 4, 'dates');
print_r($array);
Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
    [3] => dates
)

// make a six-element array with 'zucchinis' to the left
$array = array_pad($array, -6, 'zucchini');
print_r($array);
Array
(
    [0] => zucchini
    [1] => zucchini
    [2] => apple
    [3] => banana
    [4] => coconut
    [5] => dates
)

Be careful: array_pad($array, 4, 'dates') makes sure an $array is at least four elements long; it doesn’t add four new elements. In this case, if $array was already four elements or larger, array_pad() would return an unaltered $array.

Also, if you declare a value for a fourth element, $array[4]:

$array = array('apple', 'banana', 'coconut');
$array[4] = 'dates';

you end up with a four-element array with indexes 0, 1, 2, and 4:

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
    [4] => dates
)

PHP essentially turns this into an associative array that happens to have integer keys.

The array_splice() function, unlike array_pad(), has the side effect of modifying the original array. It returns the spliced-out array. That’s why you don’t assign the return value to $array. However, like array_pad(), you can splice from either the right or left. So calling array_splice() with a value of -2 chops off the last two elements from the end:

// make a four-element array
$array = array('apple', 'banana', 'coconut', 'dates');

// shrink to three elements
array_splice($array, 3);

// remove last element, equivalent to array_pop()
array_splice($array, -1);

// only remaining fruits are apple and banana
print_r($array);
Array
(
    [0] => apple
    [1] => banana
)

See Also

Documentation on array_pad() at http://www.php.net/array-pad and array_splice() at http://www.php.net/array-splice .

4.7. Appending One Array to Another

Problem

You want to combine two arrays into one.

Solution

Use array_merge():

$garden = array_merge($fruits, $vegetables);

Discussion

The array_merge() function works with both predefined arrays and arrays defined in place using array() :

$p_languages = array('Perl', 'PHP');
$p_languages = array_merge($p_languages, array('Python'));
print_r($p_languages);
Array
(
    [0] => PHP
    [1] => Perl
    [2] => Python
)

Accordingly, merged arrays can be either preexisting arrays, as with $p_languages, or anonymous arrays, as with array('Python').

You can’t use array_push(), because PHP won’t automatically flatten out the array into a series of independent variables, and you’ll end up with a nested array. Thus:

array_push($p_languages, array('Python'));
print_r($p_languages);
Array
(
    [0] => PHP
    [1] => Perl
    [2] => Array
        (
            [0] => Python
        )

)

Merging arrays with only numerical keys causes the arrays to get renumbered, so values aren’t lost. Merging arrays with string keys causes the second array to overwrite the value of any duplicated keys. Arrays with both types of keys exhibit both types of behavior. For example:

$lc = array('a', 'b' => 'b'); // lower-case letters as values
$uc = array('A', 'b' => 'B'); // upper-case letters as values
$ac = array_merge($lc, $uc);  // all-cases?
print_r($ac);
Array
(
    [0] => a
    [b] => B
    [1] => A
)

The uppercase A has been renumbered from index 0 to index 1, to avoid a collision, and merged onto the end. The uppercase B has overwritten the lowercase b and replaced it in the original place within the array.

The + operator can also merge arrays. The array on the right overwrites any identically named keys found on the left. It doesn’t do any reordering to prevent collisions. Using the previous example:

print_r($uc + $lc);
print_r($lc + $uc);
Array
(
    [0] => a
    [b] => b
)
Array
(
    [0] => A
    [b] => B
)

Since a and A both have a key of 0, and b and B both have a key of b, you end up with a total of only two elements in the merged arrays.

In the first case, $a + $b becomes just $b, and in the other, $b + $a becomes $a.

However, if you had two distinctly keyed arrays, this wouldn’t be a problem, and the new array would be the union of the two arrays.

See Also

Documentation on array_merge() at http://www.php.net/array-merge .

4.8. Turning an Array into a String

Problem

You have an array, and you want to convert it into a nicely formatted string.

Solution

Use join():

// make a comma delimited list
$string = join(',', $array);

Or loop yourself:

$string = '';

foreach ($array as $key => $value) {
    $string .= ",$value";
}

$string = substr($string, 1); // remove leading ","

Discussion

If you can use join(), do; it’s faster than any PHP-based loop. However, join() isn’t very flexible. First, it places a delimiter only between elements, not around them. To wrap elements inside HTML bold tags and separate them with commas, do this:

$left  = '<b>';
$right = '</b>';

$html = $left . join("$right,$left", $html) . $right;

Second, join() doesn’t allow you to discriminate against values. If you want to include a subset of entries, you need to loop yourself:

$string = '';

foreach ($fields as $key => $value) {
    // don't include password
    if ('password' != $key) {
        $string .= ",<b>$value</b>";
    }
}

$string = substr($string, 1); // remove leading ","

Notice that a separator is always added to each value and then stripped off outside the loop. While it’s somewhat wasteful to add something that will be subtracted later, it’s far cleaner and efficient (in most cases) than attempting to embed logic inside of the loop. To wit:

$string = '';
foreach ($fields as $key => $value) {
    // don't include password
    if ('password' != $value) {
        if (!empty($string)) { $string .= ','; }
        $string .= "<b>$value</b>";
    }
}

Now you have to check $string every time you append a value. That’s worse than the simple substr() call. Also, prepend the delimiter (in this case a comma) instead of appending it because it’s faster to shorten a string from the front than the rear.

See Also

Recipe 4.9 for printing an array with commas; documentation on join() at http://www.php.net/join and substr() at http://www.php.net/substr.

4.9. Printing an Array with Commas

Problem

You want to print out an array with commas separating the elements and with an “and” before the last element if there are more than two elements in the array.

Solution

Use the pc_array_to_comma_string() function shown in Example 4-1, which returns the correct string.

Example 4-1. pc_array_to_comma_string()
function pc_array_to_comma_string($array) {

    switch (count($array)) {
    case 0:
        return '';

    case 1:
        return reset($array);
    
    case 2:
        return join(' and ', $array);

    default:
        $last = array_pop($array);
        return join(', ', $array) . ", and $last";
    }
}

Discussion

If you have a list of items to print, it’s useful to print them in a grammatically correct fashion. It looks awkward to display text like this:

$thundercats = array('Lion-O', 'Panthro', 'Tygra', 'Cheetara', 'Snarf');
print 'ThunderCat good guys include ' . join(', ', $thundercats) . '.';
ThunderCat good guys include Lion-O, Panthro, Tygra, Cheetara, Snarf.

This implementation of this function isn’t completely straightforward, since we want pc_array_to_comma_string() to work with all arrays, not just numeric ones beginning at 0. If restricted only to that subset, for an array of size one, you return $array[0]. But if the array doesn’t begin at 0, $array[0] is empty. So you can use the fact that reset(), which resets an array’s internal pointer, also returns the value of the first array element.

For similar reasons, you call array_pop() to grab the end element, instead of assuming it’s located at $array[count($array)-1]. This allows you to use join() on $array.

Also note that the code for case 2 actually works correctly for case 1, too. And the default code works (though inefficiently) for case 2; however, the transitive property doesn’t apply, so you can’t use the default code on elements of size 1.

See Also

Recipe 4.8 for turning an array into a string; documentation on join() at http://www.php.net/join, array_pop() at http://www.php.net/array-pop, and reset() at http://www.php.net/reset.

4.10. Checking if a Key Is in an Array

Problem

You want to know if an array contains a certain key.

Solution

Use array_key_exists() to check for a key no matter what the associated value is:

if (array_key_exists('key', $array)) {
    /* there is a value for $array['key'] */
}

Use isset() to find a key whose associated value is anything but null:

if (isset($array['key'])) { /* there is a non-null value for 'key' in $array */ }

Discussion

The array_key_exists() function completely ignores array values—it just reports whether there is an element in the array with a particular key. isset(), however, behaves the same way on array keys as it does with other variables. A null value causes isset() to return false. See the Introduction to Chapter 5 for more information about the truth value of variables.

See Also

Documentation on isset() at http://www.php.net/isset and on array_key_exists() at http://www.php.net/array_key_exists.

4.11. Checking if an Element Is in an Array

Problem

You want to know if an array contains a certain value.

Solution

Use in_array():

if (in_array($value, $array)) {
    // an element has $value as its value in array $array
}

Discussion

Use in_array() to check if an element of an array holds a value:

$book_collection = array('Emma', 'Pride and Prejudice', 'Northhanger Abbey');
$book = 'Sense and Sensibility';

if (in_array($book, $book_collection) { 
    echo 'Own it.';
} else {
    echo 'Need it.';
}

The default behavior of in_array() is to compare items using the == operator. To use the strict equality check, ===, pass true as the third parameter to in_array():

$array = array(1, '2', 'three');

in_array(0, $array);        // true!
in_array(0, $array, true);  // false
in_array(1, $array);        // true
in_array(1, $array, true);  // true
in_array(2, $array);        // true
in_array(2, $array, true);  // false

The first check, in_array(0, $array), evaluates to true because to compare the number 0 against the string three, PHP casts three to an integer. Since three isn’t a numeric string, as is 2, it becomes 0. Therefore, in_array() thinks there’s a match.

Consequently, when comparing numbers against data that may contain strings, it’s safest to use a strict comparison.

If you find yourself calling in_array() multiple times on the same array, it may be better to use an associative array, with the original array elements as the keys in the new associative array. Looking up entries using in_array() takes linear time; with an associative array, it takes constant time.

If you can’t create the associative array directly but need to convert from a traditional one with integer keys, use array_flip() to swap the keys and values of an array:

$book_collection = array('Emma',
                         'Pride and Prejudice',
                         'Northhanger Abbey');

// convert from numeric array to associative array
$book_collection = array_flip($book_collection);
$book = 'Sense and Sensibility';

if (isset($book_collection[$book])) { 
    echo 'Own it.';
} else {
    echo 'Need it.';
}

Note that doing this condenses multiple keys with the same value into one element in the flipped array.

See Also

Recipe 4.12 for determining the position of a value in an array; documentation on in_array() at http://www.php.net/in-array and array_flip() at the following address: http://www.php.net/array-flip.

4.12. Finding the Position of a Value in an Array

Problem

You want to know if a value is in an array. If the value is in the array, you want to know its key.

Solution

Use array_search() . It returns the key of the found value. If the value is not in the array, it returns false:

$position = array_search($value, $array);
if ($position !== false) {
    // the element in position $position has $value as its value in array $array
}

Discussion

Use in_array() to find if an array contains a value; use array_search() to discover where that value is located. However, because array_search() gracefully handles searches in which the value isn’t found, it’s better to use array_search() instead of in_array(). The speed difference is minute, and the extra information is potentially useful:

$favorite_foods = array(1 => 'artichokes', 'bread', 'cauliflower', 'deviled eggs');
$food = 'cauliflower';
$position = array_search($food, $favorite_foods);

if ($position !== false) {
    echo "My #$position favorite food is $food";
} else {
    echo "Blech! I hate $food!";
}

Use the !== check against false because if your string is found in the array at position 0, the if evaluates to a logical false, which isn’t what is meant or wanted.

If a value is in the array multiple times, array_search() is only guaranteed to return one of the instances, not the first instance.

See Also

Recipe 4.11 for checking whether an element is in an array; documentation on array_search() at http://www.php.net/array-search; for more sophisticated searching of arrays using regular expression, see preg_replace(), which is found at http://www.php.net/preg-replace and Chapter 22.

4.13. Finding Elements That Pass a Certain Test

Problem

You want to locate entries in an array that meet certain requirements.

Solution

Use a foreach loop:

$movies = array(...);

foreach ($movies as $movie) {
    if ($movie['box_office_gross'] < 5000000) { $flops[] = $movie; }
}

Or array_filter():

$movies = array(...);

function flops($movie) {
    return ($movie['box_office_gross'] < 5000000) ? 1 : 0;
}

$flops = array_filter($movies, 'flops');

Discussion

The foreach loops are simple: you iterate through the data and append elements to the return array that match your criteria.

If you want only the first such element, exit the loop using break:

foreach ($movies as $movie) {
    if ($movie['box_office_gross'] > 200000000) { $blockbuster = $movie; break; }
}

You can also return directly from a function:

function blockbuster($movies) {
    foreach ($movies as $movie) {
        if ($movie['box_office_gross'] > 200000000) { return $movie; }
    }
}

With array_filter(), however, you first create a callback function that returns true for values you want to keep and false for values you don’t. Using array_filter(), you then instruct PHP to process the array as you do in the foreach.

It’s impossible to bail out early from array_filter(), so foreach provides more flexibility and is simpler to understand. Also, it’s one of the few cases in which the built-in PHP function doesn’t clearly outperform user-level code.

See Also

Documentation on array_filter() at http://www.php.net/array-filter.

4.14. Finding the Largest or Smallest Valued Element in an Array

Problem

You have an array of elements, and you want to find the largest or smallest valued element. For example, you want to find the appropriate scale when creating a histogram.

Solution

To find the largest element, use max():

$largest = max($array);

To find the smallest element, use min():

$smallest = min($array);

Discussion

Normally, max() returns the larger of two elements, but if you pass it an array, it searches the entire array instead. Unfortunately, there’s no way to find the index of the largest element using max(). To do that, you must sort the array in reverse order to put the largest element in position 0:

arsort($array);

Now the value of the largest element is $array[0].

If you don’t want to disturb the order of the original array, make a copy and sort the copy:

$copy = $array;
arsort($copy);

The same concept applies to min() but uses asort() instead of arsort().

See Also

Recipe 4.16 for sorting an array; documentation on max() at http://www.php.net/max, min() at http://www.php.net/min, arsort() at http://www.php.net/arsort, and asort() at http://www.php.net/asort.

4.15. Reversing an Array

Problem

You want to reverse the order of the elements in an array.

Solution

Use array_reverse():

$array = array('Zero', 'One', 'Two');
$reversed = array_reverse($array);

Discussion

The array_reverse() function reverses the elements in an array. However, it’s often possible to avoid this operation. If you wish to reverse an array you’ve just sorted, modify the sort to do the inverse. If you want to reverse a list you’re about to loop through and process, just invert the loop. Instead of:

for ($i = 0, $size = count($array); $i < $size; $i++) {
    ...
}

do the following:

for ($i = count($array) - 1; $i >=0 ; $i--) { 
    ...
}

However, as always, use a for loop only on a tightly packed array.

Another alternative would be, if possible, to invert the order elements are placed into the array. For instance, if you’re populating an array from a series of rows returned from a database, you should be able to modify the query to ORDER DESC. See your database manual for the exact syntax for your database.

See Also

Documentation on array_reverse() at http://www.php.net/array-reverse.

4.16. Sorting an Array

Problem

You want to sort an array in a specific way.

Solution

To sort an array using the traditional definition of sort, use sort():

$states = array('Delaware', 'Pennsylvania', 'New Jersey');
sort($states);

To sort numerically, pass SORT_NUMERIC as the second argument to sort():

$scores = array(1, 10, 2, 20);
sort($scores, SORT_NUMERIC);

This resorts the numbers in ascending order (1, 2, 10, 20) instead of lexicographical order (1, 10, 2, 20).

Discussion

The sort() function doesn’t preserve the key/value association between elements; instead, entries are reindexed starting at 0 and going upward. (The one exception to this rule is a one-element array; its lone element doesn’t have its index reset to 0. This is fixed as of PHP 4.2.3.)

To preserve the key/value links, use asort(). The asort() function is normally used for associative arrays, but it can also be useful when the indexes of the entries are meaningful:

$states = array(1 => 'Delaware', 'Pennsylvania', 'New Jersey');
asort($states);

while (list($rank, $state) = each($states)) {
    print "$state was the #$rank state to join the United States\n";

}

Use natsort() to sort the array using a natural sorting algorithm. Under natural sorting, you can mix strings and numbers inside your elements and still get the right answer:

$tests = array('test1.php', 'test10.php', 'test11.php', 'test2.php');
natsort($tests);

The elements are now ordered 'test1.php', 'test2.php', 'test10.php', and 'test11.php'. With natural sorting, the number 10 comes after the number 2; the opposite occurs under traditional sorting. For case-insensitive natural sorting, use natcasesort().

To sort the array in reverse order, use rsort() or arsort(), which is like rsort() but also preserves keys. There is no natrsort() or natcasersort(). You can also pass SORT_NUMERIC into these functions.

See Also

Recipe 4.17 for sorting with a custom comparison function and Recipe 4.18 for sorting multiple arrays; documentation on sort() at http://www.php.net/sort, asort() at http://www.php.net/asort, natsort() at http://www.php.net/natsort, natcasesort() at http://www.php.net/natcasesort, rsort() at http://www.php.net/rsort, and arsort() at http://www.php.net/arsort.

4.17. Sorting an Array by a Computable Field

Problem

You want to define your own sorting routine.

Solution

Use usort() in combination with a custom comparison function:

// sort in reverse natural order
function natrsort($a, $b) {
    return strnatcmp($b, $a);
}

$tests = array('test1.php', 'test10.php', 'test11.php', 'test2.php');
usort($tests, 'natrsort');

Discussion

The comparison function must return a value greater that 0 if $a > $b, 0 if $a == $b, and a value less than 0 if $a < $b. To sort in reverse, do the opposite. The function in the Solution, strnatcmp(), obeys those rules.

To reverse the sort, instead of multiplying the return value of strnatcmp($a, $b) by -1, switch the order of the arguments to strnatcmp($b, $a).

The sort function doesn’t need to be a wrapper for an existing sort. For instance, the pc_date_sort() function, shown in Example 4-2, shows how to sort dates.

Example 4-2. pc_date_sort()
// expects dates in the form of "MM/DD/YYYY"
function pc_date_sort($a, $b) {
    list($a_month, $a_day, $a_year) = explode('/', $a);
    list($b_month, $b_day, $b_year) = explode('/', $b);

    if ($a_year  > $b_year ) return  1;
    if ($a_year  < $b_year ) return -1;

    if ($a_month > $b_month) return  1;
    if ($a_month < $b_month) return -1;

    if ($a_day   > $b_day  ) return  1;
    if ($a_day   < $b_day  ) return -1;

    return 0;
}

$dates = array('12/14/2000', '08/10/2001', '08/07/1999');
usort($dates, 'pc_date_sort');

While sorting, usort() frequently recomputes the sort function’s return values each time it’s needed to compare two elements, which slows the sort. To avoid unnecessary work, you can cache the comparison values, as shown in pc_array_sort() in Example 4-3.

Example 4-3. pc_array_sort()
function pc_array_sort($array, $map_func, $sort_func = '') {
    $mapped = array_map($map_func, $array);    // cache $map_func() values

    if ('' == $sort_func) { 
        asort($mapped);                        // asort() is faster then usort()
    }  else { 
        uasort($mapped, $sort_func);           // need to preserve keys
    }

    while (list($key) = each($mapped)) {
        $sorted[] = $array[$key];              // use sorted keys
    }

    return $sorted;
}

To avoid unnecessary work, pc_array_sort() uses a temporary array, $mapped, to cache the return values. It then sorts $mapped, using either the default sort order or a user-specified sorting routine. Importantly, it uses a sort that preserves the key/value relationship. By default, it uses asort() because asort() is faster than uasort(). (Slowness in uasort() is the whole reason for pc_array_sort() after all.) Finally, it creates a sorted array, $sorted, using the sorted keys in $mapped to index the values in the original array.

For small arrays or simple sort functions, usort() is faster, but as the number of computations grows, pc_array_sort() surpasses usort(). The following example sorts elements by their string lengths, a relatively quick custom sort:

function pc_u_length($a, $b) {
    $a = strlen($a);
    $b = strlen($b);

    if ($a == $b) return  0;
    if ($a  > $b) return  1;
                  return -1;
}

function pc_map_length($a) {
    return strlen($a);
}

$tests = array('one', 'two', 'three', 'four', 'five',
               'six', 'seven', 'eight', 'nine', 'ten');

// faster for < 5 elements using pc_u_length()
usort($tests, 'pc_u_length');

// faster for >= 5 elements using pc_map_length()
$tests = pc_array_sort($tests, 'pc_map_length');

Here, pc_array_sort() is faster than usort() once the array reaches five elements.

See Also

Recipe 4.16 for basic sorting and Recipe 4.18 for sorting multiple arrays; documentation on usort() at http://www.php.net/usort, asort() at http://www.php.net/asort, and array_map() at http://www.php.net/array-map.

4.18. Sorting Multiple Arrays

Problem

You want to sort multiple arrays or an array with multiple dimensions.

Solution

Use array_multisort():

To sort multiple arrays simultaneously, pass multiple arrays to array_multisort():

$colors = array('Red', 'White', 'Blue');
$cities = array('Boston', 'New York', 'Chicago');

array_multisort($colors, $cities);
print_r($colors);
print_r($cities);
Array
(
    [0] => Blue
    [1] => Red
    [2] => White
)
Array
(
    [0] => Chicago
    [1] => Boston
    [2] => New York
)

To sort multiple dimensions within a single array, pass the specific array elements:

$stuff = array('colors' => array('Red', 'White', 'Blue'),
               'cities' => array('Boston', 'New York', 'Chicago'));

array_multisort($stuff['colors'], $stuff['cities']);
print_r($stuff);
Array
(
    [colors] => Array
        (
            [0] => Blue
            [1] => Red
            [2] => White
        )

    [cities] => Array
        (
            [0] => Chicago
            [1] => Boston
            [2] => New York
        )

)

To modify the sort type, as in sort(), pass in SORT_REGULAR, SORT_NUMERIC, or SORT_STRING after the array. To modify the sort order, unlike in sort(), pass in SORT_ASC or SORT_DESC after the array. You can also pass in both a sort type and a sort order after the array.

Discussion

The array_multisort() function can sort several arrays at once or a multidimensional array by one or more dimensions. The arrays are treated as columns of a table to be sorted by rows. The first array is the main one to sort by; all the items in the other arrays are reordered based on the sorted order of the first array. If items in the first array compare as equal, the sort order is determined by the second array, and so on.

The default sorting values are SORT_REGULAR and SORT_ASC, and they’re reset after each array, so there’s no reason to pass either of these two values, except for clarity:

$numbers = array(0, 1, 2, 3);
$letters = array('a', 'b', 'c', 'd');
array_multisort($numbers, SORT_NUMERIC, SORT_DESC,
                $letters, SORT_STRING , SORT_DESC);

This example reverses the arrays.

See Also

Recipe 4.16 for simple sorting and Recipe 4.17 for sorting with a custom function; documentation on array_multisort() at http://www.php.net/array-multisort .

4.19. Sorting an Array Using a Method Instead of a Function

Problem

You want to define a custom sorting routine to order an array. However, instead of using a function, you want to use an object method.

Solution

Pass in an array holding a class name and method in place of the function name:

usort($access_times, array('dates', 'compare'));

Discussion

As with a custom sort function, the object method needs to take two input arguments and return 1, 0, or 1, depending if the first parameter is larger than, equal to, or less than the second:

class pc_sort {
    // reverse-order string comparison
    function strrcmp($a, $b) {
        return strcmp($b, $a);
    }
}

usort($words, array('pc_sort', 'strrcmp'));

See Also

Chapter 7 for more on classes and objects; Recipe 4.17 for more on custom sorting of arrays.

4.20. Randomizing an Array

Problem

You want to scramble the elements of an array in a random order.

Solution

Use shuffle():

shuffle($array);

Discussion

It’s suprisingly tricky to properly shuffle an array. In fact, up until PHP 4.3, PHP’s shuffle() routine wasn’t a truly random shuffle. It would mix elements around, but certain combinations were more likely than others.

Therefore, you should use PHP’s shuffle() function whenever possible.

See Also

Documentation on shuffle() at http://www.php.net/shuffle.

4.21. Removing Duplicate Elements from an Array

Problem

You want to eliminate duplicates from an array.

Solution

If the array is already complete, use array_unique(), which returns a new array that contains no duplicate values:

$unique = array_unique($array);

If you create the array while processing results, here is a technique for numerical arrays:

foreach ($_REQUEST['fruits'] as $fruit) {
    if (!in_array($array, $fruit)) { $array[] = $fruit; }
}

Here’s one for associative arrays:

foreach ($_REQUEST['fruits'] as $fruit) {
    $array[$fruit] = $fruit;
}

Discussion

Once processing is completed, array_unique() is the best way to eliminate duplicates. But if you’re inside a loop, you can eliminate the duplicate entries from appearing by checking if they’re already in the array.

An even faster method than using in_array() is to create a hybrid array in which the key and the value for each element are the same. This eliminates the linear check of in_array() but still allows you to take advantage of the array family of functions that operate over the values of an array instead of the keys.

In fact, it’s faster to use the associative array method and then call array_values() on the result (or, for that matter, array_keys() , but array_values() is slightly faster) than to create a numeric array directly with the overhead of in_array().

See Also

Documentation on array_unique() at http://www.php.net/array-unique.

4.22. Applying a Function to Each Element in an Array

Problem

You want to apply a function or method to each element in an array. This allows you to transform the input data for the entire set all at once.

Solution

Use array_walk():

function escape_data(&$value, $key) {
	$value = htmlentities($value, ENT_QUOTES);
}

$names = array('firstname' => "Baba",
               'lastname'  => "O'Riley");
               
array_walk($names, 'escape_data');

foreach ($names as $name) {
	print "$name\n";
}

Baba
O&#039;Riley

For nested data, use array_walk_recursive():

function escape_data(&$value, $key) {
	$value = htmlentities($value, ENT_QUOTES);
}

$names = array('firstnames' => array("Baba", "Bill"),
               'lastnames'  => array("O'Riley", "O'Reilly"));

array_walk_recursive($names, 'escape_data');
               
foreach ($names as $nametypes) {
	foreach ($nametypes as $name) {
		print "$name\n";
	}
}

Baba
Bill
O&#039;Riley
O&#039;Reilly

Discussion

It’s frequently useful to loop through all the elements of an array. One option is to foreach through the data. However, an alternative choice is the array_walk() function.

This function takes an array and the name of a callback function, which is the function that processes the elements of the array. The callback function takes two parameters, a value and a key. It can also take an optional third parameter, which is any additional data you wish to expose within the callback.

Here’s an example that ensures all the data in the $names array is properly HTML encoded. The callback function, escape_data(), takes the array values, passes them to htmlentities() to encode the key HTML entities, and assigns the result back to $value:

function escape_data(&$value, $key) {
	$value = htmlentities($value, ENT_QUOTES);
}

$names = array('firstname' => "Baba",
               'lastname'  => "O'Riley");
               
array_walk($names, 'escape_data');

foreach ($names as $name) {
	print "$name\n";
}

Baba
O&#039;Riley

Since array_walk operates in-place instead of returning a modified copy of the array, you must pass in values by reference when you want to modify the elements. In those cases, as in this example, there is an & before the parameter name. However, this is only necessary when you wish to alter the array.

When you have a series of nested arrays, use the array_walk_recursive() function:

function escape_data(&$value, $key) {
	$value = htmlentities($value, ENT_QUOTES);
}

$names = array('firstnames' => array("Baba", "Bill"),
               'lastnames'  => array("O'Riley", "O'Reilly"));

array_walk_recursive($names, 'escape_data');
               
foreach ($names as $nametypes) {
	foreach ($nametypes as $name) {
		print "$name\n";
	}
}

Baba
Bill
O&#039;Riley
O&#039;Reilly

The array_walk_recursive() function only passes non-array elements to the callback, so you don’t need to modify a callback when switching from array_walk().

See Also

Documentation on array_walk() at http://www.php.net/array-walk, array_walk_recursive() at http://www.php.net/array_walk_recursive, and htmlentities() at http://www.php.net/htmlentities .

4.23. Finding the Union, Intersection, or Difference of Two Arrays

Problem

You have a pair of arrays, and you want to find their union (all the elements), intersection (elements in both, not just one), or difference (in one but not both).

Solution

To compute the union:

$union = array_unique(array_merge($a, $b));

To compute the intersection:

$intersection = array_intersect($a, $b);

To find the simple difference:

$difference = array_diff($a, $b);

And for the symmetric difference:

$difference = array_merge(array_diff($a, $b), array_diff($b, $a));

Discussion

Many necessary components for these calculations are built into PHP; it’s just a matter of combining them in the proper sequence.

To find the union, you merge the two arrays to create one giant array with all of the values. But array_merge() allows duplicate values when merging two numeric arrays, so you call array_unique() to filter them out. This can leave gaps between entries because array_unique() doesn’t compact the array. It isn’t a problem, however, as foreach and each() handle sparsely filled arrays without a hitch.

The function to calculate the intersection is simply named array_intersection() and requires no additional work on your part.

The array_diff() function returns an array containing all the unique elements in $old that aren’t in $new. This is known as the simple difference:

$old = array('To', 'be', 'or', 'not', 'to', 'be');
$new = array('To', 'be', 'or', 'whatever');
$difference = array_diff($old, $new);

$old = array('To', 'be', 'or', 'not', 'to', 'be');
$new = array('To', 'be', 'or', 'whatever');
$difference = array_diff($old, $new);
Array
(
    [3] => not
    [4] => to
)

The resulting array, $difference contains 'not' and 'to' because array_diff() is case-sensitive. It doesn’t contain 'whatever' because it doesn’t appear in $old.

To get a reverse difference, or in other words, to find the unique elements in $new that are lacking in $old, flip the arguments:

$old = array('To', 'be', 'or', 'not', 'to', 'be');
$new = array('To', 'be', 'or', 'whatever');
$reverse_diff = array_diff($new, $old);

$old = array('To', 'be', 'or', 'not', 'to', 'be');
$new = array('To', 'be', 'or', 'whatever');
$reverse_diff = array_diff($new, $old);
Array
(
    [3] => whatever
)

The $reverse_diff array contains only 'whatever'.

If you want to apply a function or other filter to array_diff(), roll your own diffing algorithm:

// implement case-insensitive diffing; diff -i

$seen = array();
foreach ($new as $n) {
    $seen[strtolower($n)]++;
}

foreach ($old as $o) {
    $o = strtolower($o);
    if (!$seen[$o]) { $diff[$o] = $o; }
}

The first foreach builds an associative array lookup table. You then loop through $old and, if you can’t find an entry in your lookup, add the element to $diff.

It can be a little faster to combine array_diff() with array_map():

$diff = array_diff(array_map('strtolower', $old), array_map('strtolower', $new));

The symmetric difference is what’s in $a but not $b, and what’s in $b but not $a:

$difference = array_merge(array_diff($a, $b), array_diff($b, $a));

Once stated, the algorithm is straightforward. You call array_diff() twice and find the two differences. Then you merge them together into one array. There’s no need to call array_unique() since you’ve intentionally constructed these arrays to have nothing in common.

See Also

Documentation on array_unique() at http://www.php.net/array-unique, array_intersect() at http://www.php.net/array-intersect, array_diff() at http://www.php.net/array-diff, array_merge() at http://www.php.net/array-merge, and array_map() at http://www.php.net/array-map .

4.24. Making an Object Act like an Array

Problem

You have an object, but you want to be able to treat it as an array. This allows you to combine the benefits from an object-oriented design with the familiar interface of an array.

Solution

Implement SPL’s ArrayAccess interface:

class FakeArray implements ArrayAccess {

	private $elements;
	
	public function __construct() {
		$this->elements = array();
	}
	
	public function offsetExists($offset) {
		return isset($this->elements[$offset]);
	}

	public function offsetGet($offset) {
		return $this->elements[$offset];
	}
	
	public function offsetSet($offset, $value) {
		return $this->elements[$offset] = $value;
	}
	
	public function offsetUnset($offset) {
		unset($this->elements[$offset]);
	}
}

$array = new FakeArray;

// What's Opera, Doc?
$array['animal'] = 'wabbit';

// Be very quiet I'm hunting wabbits
if (isset($array['animal']) &&
	// Wabbit tracks!!!
    $array['animal'] == 'wabbit') {
    
	// Kill the wabbit, kill the wabbit, kill the wabbit
	unset($array['animal']);
	// Yo ho to oh! Yo ho to oh! Yo ho...
}    

// What have I done?? I've killed the wabbit....
// Poor little bunny, poor little wabbit...
if (!isset($array['animal'])) {
	print "Well, what did you expect in an opera? A happy ending?\n";
}
Well, what did you expect in an opera? A happy ending?

Discussion

The ArrayAccess interface allows you to manipulate data in an object using the same set of conventions you use for arrays. This allows you to leverage the benefits of an object-oriented design, such as using a class hierarchy or implementing additional methods on the object, but still allow people to interact with the object using a familiar interface. Alternatively, it allows you create an “array” that stores its data in an external location, such as shared memory or a database.

An implementation of ArrayAccess requires four methods: offsetExists(), which indicates whether an element is defined; offsetGet(), which returns an element’s value; offsetSet() , which sets an element to a new value; and offsetUnset(), which removes an element and its value.

This example stores the data locally in an object property:

class FakeArray implements ArrayAccess {

	private $elements;
	
	public function __construct() {
		$this->elements = array();
	}
	
	public function offsetExists($offset) {
		return isset($this->elements[$offset]);
	}

	public function offsetGet($offset) {
		return $this->elements[$offset];
	}
	
	public function offsetSet($offset, $value) {
		return $this->elements[$offset] = $value;
	}
	
	public function offsetUnset($offset) {
		unset($this->elements[$offset]);
	}
}

The object constructor initializes the $elements property to a new array. This provides you with a place to store the keys and values of your array. That property is defined as private, so people can only access the data through one of the accessor methods defined as part of the interface.

The next four methods implement everything you need to manipulate an array. Since offsetExists() checks if an array element is set, the method returns the value of isset($this->elements[$offset]).

The offsetGet() and offsetSet() methods interact with the $elements property as you would normally use those features with an array.

Last, the offsetUnset() method simply calls unset() on the element. Unlike the other three methods, it does not return the value from its operation. That’s because unset() is a statement, not a function, and doesn’t return a value.

Now you can instantiate an instance of FakeArray and manipulate it like an array:

$array = new FakeArray;

// What's Opera, Doc?
$array['animal'] = 'wabbit';

// Be very quiet I'm hunting wabbits
if (isset($array['animal']) &&
	// Wabbit tracks!!!
    $array['animal'] == 'wabbit') {
    
	// Kill the wabbit, kill the wabbit, kill the wabbit
	unset($array['animal']);
	// Yo ho to oh! Yo ho to oh! Yo ho...
}    

// What have I done?? I've killed the wabbit....
// Poor little bunny, poor little wabbit...
if (!isset($array['animal'])) {
	print "Well, what did you expect in an opera? A happy ending?\n";
}
Well, what did you expect in an opera? A happy ending?

Each operation calls one of your methods: assigning a value to $array['animal'] triggers offsetSet(), checking isset($array['animal']) invokes offsetExists(), offsetGet() comes into play when you do the comparison $array['animal'] == 'wabbit', and offsetUnset() is called for unset($array['animal']).

As you can see, after all this, the wabbit is “dead.”

See Also

More on objects in Chapter 7; the ArrayAccess reference page at http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html; and the Wikipedia entry on “What’s Opera, Doc?” at http://en.wikipedia.org/wiki/What%27s_Opera%2C_Doc .

4.25. Program: Printing a Horizontally Columned HTML Table

Converting an array into a horizontally columned table places a fixed number of elements in a row. The first set goes in the opening table row, the second set goes in the next row, and so forth. Finally, you reach the final row, where you might need to optionally pad the row with empty table data cells.

The function pc_grid_horizontal(), shown in Example 4-4, lets you specify an array and number of columns. It assumes your table width is 100%, but you can alter the $table_width variable to change this.

Example 4-4. pc_grid_horizontal()
function pc_grid_horizontal($array, $size) {

    // compute <td> width %ages
    $table_width = 100;
    $width = intval($table_width / $size);

    // define how our <tr> and <td> tags appear
    // sprintf() requires us to use %% to get literal %
    $tr = '<tr align="center">';
    $td = "<td width=\"$width%%\">%s</td>";

    // open table
    $grid = "<table width=\"$table_width%%\">$tr";

    // loop through entries and display in rows of size $sized
    // $i keeps track of when we need a new table tow
    $i = 0;
    foreach ($array as $e) {
        $grid .= sprintf($td, $e);
        $i++;

        // end of a row
        // close it up and open a new one
        if (!($i % $size)) {
            $grid .= "</tr>$tr";
        }
    }

    // pad out remaining cells with blanks
    while ($i % $size) {
        $grid .= sprintf($td, '&nbsp;');
        $i++;
    }

    // add </tr>, if necessary
    $end_tr_len = strlen($tr) * -1;
    if (substr($grid, $end_tr_len) != $tr) {
        $grid .= '</tr>';
    } else {
        $grid = substr($grid, 0, $end_tr_len);
    }

    // close table
    $grid .= '</table>';

    return $grid;
}

The function begins by calculating the width of each <td> as a percentage of the total table size. Depending on the number of columns and the overall size, the sum of the <td> widths might not equal the <table> width, but this shouldn’t affect the displayed HTML in a noticeable fashion. Next, define the <td> and <tr> tags, using printf-style formatting notation. To get the literal % needed for the <td> width percentage, use a double %%.

The meat of the function is the foreach loop through the array in which we append each <td> to the $grid. If you reach the end of a row, which happens when the total number of elements processed is a multiple of the number of elements in a row, you close and then reopen the <tr>.

Once you finish adding all the elements, you need to pad the final row with blank or empty <td> elements. Put a non-breaking space inside the data cell instead of leaving it empty to make the table render properly in the browser. Now, make sure there isn’t an extra <tr> at the end of the grid, which occurs when the number of elements is an exact multiple of the width (in other words, if you didn’t need to add padding cells). Finally, you can close the table.

For example, let’s print the names of the 50 U.S. states in a six-column table:

// establish connection to database
$dsn = 'mysql://user:password@localhost/table';
$dbh = DB::connect($dsn);
if (DB::isError($dbh)) { die ($dbh->getMessage()); }

// query the database for the 50 states
$sql = "SELECT state FROM states";
$sth = $dbh->query($sql);

// load data into array from database
while ($row = $sth->fetchRow(DB_FETCHMODE_ASSOC)) {
  $states[] = $row['state'];
}

// generate the HTML table
$grid = pc_grid_horizontal($states, 6);

// and print it out
print $grid;

When rendered in a browser, it looks like Figure 4-1.

The United States of America
Figure 4-1. The United States of America

Because 50 doesn’t divide evenly by 6, there are four extra padding cells in the last row.



[2] John Tyler was elected as Harrison’s vice president under the Whig Party platform but was expelled from the party shortly after assuming the presidency following the death of Harrison.

Get PHP Cookbook, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.