Array
Iteration
Iteration is probably one of the
most common operations you will perform with arrays—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 functionality 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 exactly what its keys are,
which is, at best, a severe limitation on your ability to manipulate a generic
array.
The
Array Pointer
Each array has a pointer that
indicates the “current” element of an array in an iteration. 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 individual 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 without 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 using prev() and next()
respectively. At any given point, we can access the value of the current
element using current() and its key u sing key(). Here’s an example:
$array = array(’foo’
=> ’bar’, ’baz’, ’bat’ => 2);
function
displayArray($array) {
reset($array);
while (key($array) !==
null) {
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 while loop, we display the current
key and value, using the key() and current() functions. Finally, we advance the
array pointer, using next(). The loop continues until we no longer have a valid
key.
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 the end() function 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 to key() on the array to NULL. This only works because 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.
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 the foreach() 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, foreach operates
on a copy of the array itself; this means that changes made to the array inside
the loop are not reflected in the iteration—for example, removing an item from
the array after the loop has begun wi ll not cause foreach to 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 cause foreach to 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 technique can be useful,
it is so fraught with peril as to be something best left alone. Consider this
code, for example:
$a = array
(’zero’,’one’,’two’);
Arrays ” 59
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 explanation—and this is not a bug.
Here’s what going on. The first foreach loop does not make any change to the
array, just as we would expect. However, it does cause $v to be assigned a
reference to each of $a’s elements, so that, by the time the loop is over, $v
is, in fact, a reference to $a[2]. As soon as the second loop starts, $v is now
assigned the value of each element. However, $v is already 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 iteration, $a[2] will become
zero, then one, and then one again, being effectively copied on to itself. To
solve this problem, you should always unset the variables you use in your
by-reference foreach loops—or, better yet, avoid using the former altogether.
Passive
Iteration
The array_walk() function and its
recursive cousin array_walk_recursive() can be used to perform an iteration of
an array in which a user-defined function is called.
Here’s an example:
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 custom setCase() function,
a simple w rapper for strtoupper(), 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, internal and custom, 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"
}
}
0 Comment to " PHP Array Iteration (The Array Pointer, An Easier Way to Iterate, Passive Iteration) "
Post a Comment