• No results found

Array Iteration

In document ZEND PHP 5 Certification (Page 75-80)

Iteration is probably one of the most common operations you will perform with ar- rays—besides creating them, of course. Unlike what happens in other languages, where arrays are all enumerative and contiguous, PHP’s arrays require a set of func- tionality that matches their flexibility, because “normal” looping structures cannot cope with the fact that array keys do not need to be continuous—or, for that matter, enumerative. Consider, for example, this simple array:

$a = array (’a’ => 10, 10 => 20, ’c’ => 30);

It is clear that none of the looping structures we have examined so far will allow you to cycle through the elements of the array—unless, that is, you happen to know ex- actly what its keys are, which is, at best, a severe limitation on your ability to manip- ulate a generic array.

The Array Pointer

Each array has apointer that indicates the “current” element of an array in an it- eration. The pointer is used by a number of different constructs, but can only be manipulated through a set of functions and does not affect your ability to access in- dividual elements of an array, nor is it affected by most “normal” array operations. The pointer is, in fact, a handy way of maintaining the iterative state of an array with- out needing an external variable to do the job for us.

The most direct way of manipulating the pointer of an array is by using a series of functions designed specifically for this purpose. Upon starting an iteration over an array, the first step is usually to reset the pointer to its initial position using the

reset()function; after that, we can move forward or backwards by one position by usingprev()andnext()respectively. At any given point, we can access the value of the current element usingcurrent()and its key usingkey(). Here’s an example:

$array = array(’foo’ => ’bar’, ’baz’, ’bat’ => 2);

function displayArray($array) {

reset($array);

echo key($array) .": " .current($array) . PHP_EOL;

next($array); }

}

Here, we have created a function that will display all the values in an array. First, we call reset()to rewind the internal array pointer. Next, using a whileloop, we display the current key and value, using thekey()andcurrent()functions. Finally, we advance the array pointer, usingnext(). The loop continues until we no longer have a valid key.

i

It’s important to understand that there is no correlation between the array pointer and the keys of the array’s elements. Moving ahead or back by one position simply gives you access to the elements of the array based on their position inside it, and not on their keys.

Since you can iterate back-and-forth within an array by using its pointer, you could—in theory—start your iteration from the last element (using theend()func- tion to reset the pointer to the bottom of the array) and then making your way to back the beginning:

$a = array (1, 2, 3); end($a);

while (key ($array) !== null) {

echo key($array) .": " .current($array) . PHP_EOL;

prev($array); }

Note how, in the last two example, we check whether the iteration should continue by comparing the result of a call tokey()on the array toNULL. This only works be- cause we are using a non-identity operator—using the inequality operator could cause some significant issues if one of the array’s elements has a key that evaluates to integer zero.

58Arrays

An Easier Way to Iterate

As you can see, using this set of functions requires quite a bit of work; to be fair, there are some situations where they offer the only reasonable way of iterating through an array, particularly if you need to skip back-and-forth between its elements.

If, however, all you need to do is iterate through the entire array from start to finish, PHP provides a handy shortcut in the form of theforeach()construct:

$array = array(’foo’, ’bar’, ’baz’);

foreach ($array as $key => $value) {

echo "$key: $value"; }

The process that takes place here is rather simple, but has a few important gotchas. First of all, foreachoperates on acopy of the array itself; this means that changes made to the array inside the loop arenot reflected in the iteration—for example, removing an item from the array after the loop has begun will not causeforeachto skip over that element. The array pointer is also always reset to the beginning of the array prior to the beginning to the loop, so that you cannot manipulate it in such a way to causeforeachto start from a position other than the first element of the array. PHP 5 also introduces the possibility of modifying the contents of the array directly by assigning the value of each element to the iterated variable by reference rather than by value:

$a = array (1, 2, 3);

foreach ($a as $k => &$v) { $v += 1;

}

var_dump ($a); // $a will contain (2, 3, 4)

While this techniquecan be useful, it is so fraught with peril as to be something best left alone. Consider this code, for example:

foreach ($a as &$v) { }

foreach ($a as $v) { }

print_r ($a);

It would be natural to think that, since this little script does nothing to the array, it will not affect its contents... but that’s not the case! In fact, the script provides the following output: Array ( [0] => zero [1] => one [2] => one )

As you can see, the array has been changed, and the last key now contains the value

’one’. How is that possible? Unfortunately, there is a perfectly logical explana- tion—and this is not a bug. Here’s what going on. The firstforeachloop does not make any change to the array, just as we would expect. However, it does cause$vto be assigned a reference to each of$a’s elements, so that, by the time the loop is over,

$vis, in fact, a reference to$a[2].

As soon as the second loop starts, $vis now assigned the value of each element. However,$visalready a reference to$a[2]; therefore, any value assigned to it will be copied automatically into the last element of the arrays! Thus, during the first itera- tion,$a[2]will becomezero, thenone, and thenoneagain, being effectively copied on to itself. To solve this problem, you should always unset the variables you use in your by-referenceforeachloops—or, better yet, avoid using the former altogether.

Passive Iteration

Thearray_walk()function and its recursive cousinarray_walk_recursive()can be used to perform an iteration of an array in which a user-defined function is called. Here’s an example:

60Arrays

function setCase(&$value, &$key) {

$value = strtoupper($value); }

$type = array(’internal’, ’custom’);

$output_formats[] = array(’rss’, ’html’, ’xml’); $output_formats[] = array(’csv’, ’json’);

$map = array_combine($type, $output_formats);

array_walk_recursive($map, ’setCase’);

var_dump($map);

Using the customsetCase()function, a simple wrapper forstrtoupper(), we are able to convert each each of the array’s values to uppercase. One thing to note about

array_walk_recursive()is that it will not call the user-defined function on anything but scalar values; because of this, the first set of keys,internalandcustom, are never passed in.

The resulting array looks like this:

array(2) { ["internal"]=> &array(3) { [0]=> string(3) "RSS" [1]=> string(4) "HTML" [2]=> string(3) "XML" } ["custom"]=> &array(2) { [0]=> string(3) "CSV" [1]=> string(4) "JSON" } }

In document ZEND PHP 5 Certification (Page 75-80)