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 an 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.

Most languages have numerical arrays (sometimes referred to just as arrays). 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 or a map or a dictionary. In an associative array, indexes aren’t integers, but strings. So in a numerical array of US presidents, “Abraham Lincoln” might have index 16; in the associative-array version, the index might be “Honest.” However, whereas 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.

When a language has both numerical and associative arrays, 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';

As of PHP 5.4, you can also use the short array syntax, inspired by JavaScript:

$fruits = ['Apples', 'Bananas', 'Cantaloupes', '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 nonarray 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 = mysqli_fetch_assoc($r)) {
    $fruits[] = $row;
}

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

The first while statement creates an array of arrays; the second creates an array of objects. See Storing Multiple Elements per Key in an Array 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';

The short syntax works here, too:

$fruits = [
    'red' => 'Apples',
    'yellow' => 'Bananas',
    'beige' => 'Cantaloupes',
    '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";
}

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;

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');
// alternatively,
$reconstruction_presidents = [16 => 'Lincoln', 'Johnson', 'Grant'];

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

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

PHP even allows you to use negative numbers in the array() call. (In fact, this method works for noninteger 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');
// alternatively,
$us_leaders = [-1 => 'George II', 'George III', 'Washington'];

If Washington is the first US 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');
// alternatively,
$presidents = [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().

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 = mysqli_fetch_assoc($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 = array();
$fruits['red'][] = 'strawberry';
$fruits['red'][] = 'apple';
$fruits['yellow'][] = 'banana';

print_r($fruits);

This prints:

Array
(
    [red] => Array
        (
            [0] => strawberry
            [1] => apple
        )

    [yellow] => Array
        (
            [0] => banana
        )

)

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

while (list($color,$fruit) = mysqli_fetch_assoc($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 array_to_comma_string() function from Printing an Array with Commas:

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

See Also

Printing an Array with Commas for how to print arrays with commas.

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, pass an increment to range() as a third argument.

So for odd numbers:

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

And for even numbers:

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

See Also

Operating on a Series of Integers for how to operate on a series of integers; documentation on range().

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 way 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:

foreach ($items as $item => $cost) {
    if (! in_stock($item)) {
        unset($items[$item]);           // address the array directly
    }
}

The variables returned by foreach() 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 $cost.

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. Therefore, this syntax is rarely used, and is included only to help you understand older PHP code.

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 nonarray. 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

Iterating Efficiently over Large or Expensive Datasets for how to use a generator to iterate efficiently overly large or expensive datasets; documentation on for, foreach, while, each(), reset(), and array_map().

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,” and 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 nothing 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 nothing, still throws an E_NOTICE error
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 Iterating Through an Array that don’t involve using a for loop.

See Also

Iterating Through an Array for iteration techniques; documentation on unset(), array_splice(), and array_values().

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';
print_r($array);

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);

See Also

Documentation on array_pad() and array_splice().

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] => Perl
    [1] => PHP
    [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] => Perl
    [1] => PHP
    [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'); // lowercase letters as values
$uc = array('A', 'b' => 'B'); // uppercase 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. For any identically named keys found in both arrays, the value from the left will be used. 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
)

Because 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().

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. Although 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

Printing an Array with Commas for printing an array with commas; documentation on join() and substr().

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 array_to_comma_string() function shown in Example 4-1, which returns the correct string.

Example 4-1. array_to_comma_string( )
function 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) . '.';

This implementation of this function isn’t completely straightforward because we want 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

Turning an Array into a String for turning an array into a string; documentation on join(), array_pop(), and reset().

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() and on array_key_exists().

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. Because 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

Finding the Position of a Value in an Array for determining the position of a value in an array; documentation on in_array() and array_flip().

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

Checking if an Element Is in an Array for checking whether an element is in an array; documentation on array_search(); for more sophisticated searching of arrays using regular expressions, see preg_replace(), which you can find at the PHP website and in Chapter 23.

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(/* ... */);

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

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:

$movies = array(/*...*/);
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 an anonymous 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() and anonymous functions.

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().

Both max() and min() issue a warning if you provide them with an empty array.

See Also

Sorting an Array for sorting an array; documentation on max(), min(), arsort(), and asort().

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 in which 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().

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.

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

Sorting an Array by a Computable Field for sorting with a custom comparison function and Sorting Multiple Arrays for sorting multiple arrays; documentation on sort(), asort(), natsort(), natcasesort(), rsort(), and arsort().

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:

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

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

Discussion

The comparison function must return a value greater than 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 comparison function doesn’t need to be a wrapper for an existing sort or an anonymous function. For instance, the date_sort() function, shown in Example 4-2, shows how to sort dates.

Example 4-2. date_sort( )
// expects dates in the form of "MM/DD/YYYY"
function 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, 'date_sort');

While sorting, usort() frequently recomputes the comparison 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 array_sort() in Example 4-3.

Example 4-3. array_sort( )
function 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, 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 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, array_sort() surpasses usort(). The following example sorts elements by their string lengths, a relatively quick custom sort:

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

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

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

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

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

// faster for >= 5 elements using map_length()
$tests = array_sort($tests, 'map_length');

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

See Also

Sorting an Array for basic sorting and Sorting Multiple Arrays for sorting multiple arrays; documentation on usort(), asort(), array_map(), and anonymous functions.

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

Sorting an Array for simple sorting and Sorting an Array by a Computable Field for sorting with a custom function; documentation on array_multisort().

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 on whether the first parameter is larger than, equal to, or less than the second:

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

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

It must also be declared as static. Alternatively, you can use an instantiated object:

class Dates {
    public function compare($a, $b) { /* compare here */ }
}

$dates = new Dates;

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

See Also

Chapter 7 for more on classes and objects; Sorting an Array by a Computable Field for more on custom sorting of arrays.

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 surprisingly 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().

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 ($_GET['fruits'] as $fruit) {
    if (!in_array($fruit, $array)) { $array[] = $fruit; }
}

Here’s one for associative arrays:

foreach ($_GET['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().

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():

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

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

foreach ($names as $name) {
    print "$name\n";
}
Baba
O'Riley

For nested data, use array_walk_recursive():

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

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

foreach ($names as $nametypes) {
    foreach ($nametypes as $name) {
        print "$name\n";
    }
}
Baba
Bill
O'Riley
O'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 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 anonymous callback function takes the array values, passes them to htmlentities() to encode the key HTML entities, and assigns the result back to $value:

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

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

foreach ($names as $name) {
    print "$name\n";
}
Baba
O'Riley

Because 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:

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

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

foreach ($names as $nametypes) {
    foreach ($nametypes as $name) {
        print "$name\n";
    }
}
Baba
Bill
O'Riley
O'Reilly

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

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, because 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);
print_r($difference);

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);
print_r($reverse_diff);

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() because you’ve intentionally constructed these arrays to have nothing in common.

Iterating Efficiently over Large or Expensive Datasets

Problem

You want to iterate through a list of items, but the entire list takes up a lot of memory or is very slow to generate.

Solution

Use a generator:

function FileLineGenerator($file) {
    if (!$fh = fopen($file, 'r')) {
        return;
    }

    while (false !== ($line = fgets($fh))) {
        yield $line;
    }

    fclose($fh);
}

$file = FileLineGenerator('log.txt');
foreach ($file as $line) {
    if (preg_match('/^rasmus: /', $line)) { print $line; }
}

Discussion

Generators provide a simple way to efficiently loop over items without the overhead and expense of loading all the data into an array. They are available in PHP 5.5.

A generator is a function that returns an iterable object. As you loop through the object, PHP repeatedly calls the generator to get the next value, which is returned by the generator function using the yield keyword.

Unlike normal functions where you start fresh every time, PHP preserves the current function state between calls to a generator. This allows you to keep any necessary information to provide the next value.

If there’s no more data, exit the function without a return or with an empty return statement. (Trying to return data from a generator is illegal.)

A perfect use of a generator is processing all the lines in a file. The simplest way is to use the file() function. This open the file, loads each line into an element of an array, and closes it. However, then you store the entire file in memory.

$file = file('log.txt');
foreach ($file as $line) {
    if (preg_match('/^rasmus: /', $line)) { print $line; }
}

Another option is to use the standard file reading functions, but then your code for reading from the file and acting on each line gets intertwined. This doesn’t make for reusable or easy-to-read code:

function print_matching_lines($file, $regex) {
    if (!$fh = fopen('log.txt','r')) {
        return;
    }
    while(false !== ($line = fgets($fh))) {
        if (preg_match($regex, $line)) { print $line; }
    }
    fclose($fh);
}

print_matching_lines('log.txt', '/^rasmus: /');

However, if you wrap the code to process the file into a generator, you get the best of both options—a general function to efficiently iterate through lines of a file and then clean syntax as if all the data is stored in an array:

function FileLineGenerator($file) {
    if (!$fh = fopen($file, 'r')) {
        return;
    }

    while (false !== ($line = fgets($fh))) {
        yield $line;
    }

    fclose($fh);
}

$file = FileLineGenerator('log.txt');
foreach ($file as $line) {
    if (preg_match('/^rasmus: /', $line)) { print $line; }
}

In a generator, control passes back and forth between the loop and the function via the yield statement. The first time the generator is called, control begins at the top of the function and pauses when it reaches a yield statement, returning the value.

In this example, the FileLineGenerator() generator function loops through lines of a file. After the file is opened, fgets() is called in a loop. As long as there are more lines, the loop yields $line back to the iterator. At the end of the file, the loop terminates, the file is closed, and the function terminates. Because nothing is yielded back, the foreach() exits.

Now, FileLineGenerator() can be used any time you want to loop through a file. The previous example prints lines beginning with rasmus: . The following one prints a random line from the file:

$line_number = 0;
foreach (FileLineGenerator('sayings.txt') as $line) {
    $line_number++;
    if (mt_rand(0, $line_number - 1) == 0) {
        $selected = $line;
    }
}

print $selected . "\n";

Despite a completely different use case, FileLineGenerator() is reusable without modifications. In this example, the generator is invoked from within the foreach loop instead of storing it in a variable.

You cannot rewind a generator. They only iterate forward.

See Also

Iterating Through an Array for iteration techniques and Chapter 24 for reading from files; documentation on generators.

Accessing an Object Using Array Syntax

Problem

You have an object, but you want to be able to read and write data to 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";
}

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. Because 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";
}

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; and What’s Opera, Doc?



[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, 3rd Edition now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.