As we discussed in Chapter 2, PHP supports both scalar and compound data types. In this chapter, we’ll discuss one of the compound types: arrays. An array is a collection of data values organized as an ordered collection of key-value pairs. It may help to think of an array, in loose terms, like an egg carton. Each compartment of an egg carton can hold an egg, but it travels around as one overall container. And, just as an egg carton doesn’t have to contain only eggs (you can put anything in there, like rocks, snowballs, four-leaf clovers, or nuts and bolts), so too an array is not limited to one type of data. It can hold strings, integers, Booleans, and so on. Plus, array compartments can also contain other arrays—but more on that later.
This chapter talks about creating an array, adding and removing elements from an array, and looping over the contents of an array. Because arrays are very common and useful, there are many built-in functions that work with them in PHP. For example, if you want to send email to more than one email address, you’ll store the email addresses in an array and then loop through the array, sending the message to the current email address. Also, if you have a form that permits multiple selections, the items the user selected are returned in an array.
There are two kinds of arrays in PHP: indexed and associative. The keys of an indexed array are integers, beginning at 0. Indexed arrays are used when you identify things by their position. Associative arrays have strings as keys and behave more like two-column tables. The first column is the key, which is used to access the value.
PHP internally stores all arrays as associative arrays; the only difference between associative and indexed arrays is what the keys happen to be. Some array features are provided mainly for use with indexed arrays because they assume that you have or want keys that are consecutive integers beginning at 0. In both cases, the keys are unique. In other words, you can’t have two elements with the same key, regardless of whether the key is a string or an integer.
PHP arrays have an internal order to their elements that is independent of the keys and values, and there are functions that you can use to traverse the arrays based on this internal order. The order is normally that in which values were inserted into the array, but the sorting functions described later in this chapter let you change the order to one based on keys, values, or anything else you choose.
Before we look at creating an array, let’s look at the structure of an existing array. You can access specific values from an existing array using the array variable’s name, followed by the element’s key, or index, within square brackets:
$age
[
'fred'
]
$shows
[
2
]
The key can be either a string or an integer. String values that are equivalent to integer numbers (without leading zeros) are treated as integers. Thus, $array[3]
and $array['3']
reference the same element, but $array['03']
references a different element. Negative numbers are valid keys, but they don’t specify positions from the end of the array as they do in Perl.
You don’t have to quote single-word strings. For instance, $age['fred']
is the same as $age[fred]
. However, it’s considered good PHP style to always use quotes, because quoteless keys are indistinguishable from constants. When you use a constant as an unquoted index, PHP uses the value of the constant as the index and emits a warning. This will throw an error in future versions of PHP:
$person
=
array
(
"
name
"
=>
'Peter'
);
"
Hello,
{
$person
[
name
]
}
"
;
// output: Hello, Peter
// this 'works' but emits this warning as well:
Warning
:
Use
of
undefined
constant
name
-
assumed
'name'
(
this
will
throw
an
Error
in
a
future
version
of
PHP
)
You must use quotes if you’re using interpolation to build the array index:
$person
=
array
(
"
name
"
=>
'Peter'
);
"
Hello,
{
$person
[
"
name
"
]
}
"
;
// output: Hello, Peter (with no warning)
Although it’s technically optional, you should also quote the key if you’re interpolating an array lookup to ensure that you get the value you expect. Consider this example:
define
(
'NAME'
,
'bob'
);
$person
=
array
(
"
name
"
=>
'Peter'
);
echo
"
Hello,
{
$person
[
'name'
]
}
"
;
echo
"
<br/>
"
;
echo
"
Hello, NAME
"
;
echo
"
<br/>
"
;
echo
NAME
;
// output:
Hello
,
Peter
Hello
,
NAME
bob
Storing a value in an array will create the array if it doesn’t already exist, but trying to retrieve a value from an array that hasn’t been defined won’t create the array. For example:
// $addresses not defined before this point
echo
$addresses
[
0
];
// prints nothing
echo
$addresses
;
// prints nothing
$addresses
[
0
]
=
"spam@cyberpromo.net"
;
echo
$addresses
;
// prints "Array"
Using simple assignment to initialize an array in your program can lead to code like this:
$addresses
[
0
]
=
"spam@cyberpromo.net"
;
$addresses
[
1
]
=
"abuse@example.com"
;
$addresses
[
2
]
=
"root@example.com"
;
That’s an indexed array, with integer indices beginning at 0. Here’s an associative array:
$price
[
'gasket'
]
=
15.29
;
$price
[
'wheel'
]
=
75.25
;
$price
[
'tire'
]
=
50.00
;
An easier way to initialize an array is to use the array()
construct, which builds an array from its arguments. This builds an indexed array, and the index values (starting at 0) are created automatically:
$addresses
=
array
(
"spam@cyberpromo.net"
,
"abuse@example.com"
,
"root@example.com"
);
To create an associative array with array()
, use the =>
symbol to separate indices (keys) from values:
$price
=
array
(
'gasket'
=>
15.29
,
'wheel'
=>
75.25
,
'tire'
=>
50.00
);
Notice the use of whitespace and alignment. We could have bunched up the code, but it wouldn’t have been as easy to read (this is equivalent to the previous code sample), or as easy to add or remove values:
$price
=
array
(
'gasket'
=>
15.29
,
'wheel'
=>
75.25
,
'tire'
=>
50.00
);
You can also specify an array using a shorter, alternate syntax:
$price
=
[
'gasket'
=>
15.29
,
'wheel'
=>
75.25
,
'tire'
=>
50.0
];
To construct an empty array, pass no arguments to array()
:
$addresses
=
array
();
You can specify an initial key with =>
and then a list of values. The values are inserted into the array starting with that key, with subsequent values having sequential keys:
$days
=
array
(
1
=>
"Mon"
,
"Tue"
,
"Wed"
,
"Thu"
,
"Fri"
,
"Sat"
,
"Sun"
);
// 2 is Tue, 3 is Wed, etc.
If the initial index is a non-numeric string, subsequent indices are integers beginning at 0. Thus, the following code is probably a mistake:
$whoops
=
array
(
'Fri'
=>
"Black"
,
"Brown"
,
"Green"
);
// same as
$whoops
=
array
(
'Fri'
=>
"Black"
,
0
=>
"Brown"
,
1
=>
"Green"
);
To add more values to the end of an existing indexed array, use the []
syntax:
$family
=
array
(
"Fred"
,
"Wilma"
);
$family
[]
=
"Pebbles"
;
// $family[2] is "Pebbles"
This construct assumes the array’s indices are numbers and assigns elements into the next available numeric index, starting from 0. Attempting to append to an associative array without appropriate keys is almost always a programmer mistake, but PHP will give the new elements numeric indices without issuing a warning:
$person
=
array
(
'name'
=>
"Fred"
);
$person
[]
=
"Wilma"
;
// $person[0] is now "Wilma"
The range()
function creates an array of consecutive integer or character values between and including the two values you pass to it as arguments. For example:
$numbers
=
range
(
2
,
5
);
// $numbers = array(2, 3, 4, 5);
$letters
=
range
(
'a'
,
'z'
);
// $letters holds the alphabet
$reversedNumbers
=
range
(
5
,
2
);
// $reversedNumbers = array(5, 4, 3, 2);
Only the first letter of a string argument is used to build the range:
range
(
"aaa"
,
"zzz"
);
// same as range('a','z')
The count()
and sizeof()
functions are identical in use and effect. They return the number of elements in the array. There is no stylistic preference about which function you use. Here’s an example:
$family
=
array
(
"Fred"
,
"Wilma"
,
"Pebbles"
);
$size
=
count
(
$family
);
// $size is 3
This function counts only array values that are actually set:
$confusion
=
array
(
10
=>
"ten"
,
11
=>
"eleven"
,
12
=>
"twelve"
);
$size
=
count
(
$confusion
);
// $size is 3
To create an array with values initialized to the same content, use array_pad()
. The first argument to array_pad()
is the array, the second argument is the minimum number of elements you want the array to have, and the third argument is the value to give any elements that are created. The array_pad()
function returns a new padded array, leaving its argument (source) array alone.
Here’s array_pad()
in action:
$scores
=
array
(
5
,
10
);
$padded
=
array_pad
(
$scores
,
5
,
0
);
// $padded is now array(5, 10, 0, 0, 0)
Notice how the new values are appended to the array. If you want the new values added to the start of the array, use a negative second argument:
$padded
=
array_pad
(
$scores
,
−5
,
0
);
// $padded is now array(0, 0, 0, 5, 10);
If you pad an associative array, existing keys will be preserved. New elements will have numeric keys starting at 0.
The values in an array can themselves be arrays. This lets you easily create multidimensional arrays:
$row0
=
array
(
1
,
2
,
3
);
$row1
=
array
(
4
,
5
,
6
);
$row2
=
array
(
7
,
8
,
9
);
$multi
=
array
(
$row0
,
$row1
,
$row2
);
You can refer to elements of multidimensional arrays by appending more square brackets, []
:
$value
=
$multi
[
2
][
0
];
// row 2, column 0. $value = 7
To interpolate a lookup of a multidimensional array, you must enclose the entire array lookup in curly braces:
echo
(
"The value at row 2, column 0 is
{
$multi
[
2
][
0
]
}
\n
"
);
Failing to use the curly braces results in output like this:
The
value
at
row
2
,
column
0
is
Array
[
0
]
To copy all of an array’s values into variables, use the list()
construct:
list
(
$variable
,
...
)
=
$array
;
The array’s values are copied into the listed variables in the array’s internal order. By default that’s the order in which they were inserted, but the sort functions described later let you change that. Here’s an example:
$person
=
array
(
"Fred"
,
35
,
"Betty"
);
list
(
$name
,
$age
,
$wife
)
=
$person
;
// $name is "Fred", $age is 35, $wife is "Betty"
The use of the list()
function is a common practice for picking up values from a database selection where only one row is returned. This automatically loads the data from the simple query into a series of local variables. Here is an example of selecting two opposing teams from a sports scheduling database:
$sql
=
"SELECT HomeTeam, AwayTeam FROM schedule WHERE
Ident = 7"
;
$result
=
mysql_query
(
$sql
);
list
(
$hometeam
,
$awayteam
)
=
mysql_fetch_assoc
(
$result
);
There is more coverage on databases in Chapter 9.
If you have more values in the array than in the list()
, the extra values are ignored:
$person
=
array
(
"Fred"
,
35
,
"Betty"
);
list
(
$name
,
$age
)
=
$person
;
// $name is "Fred", $age is 35
If you have more values in the list()
than in the array, the extra values are set to NULL
:
$values
=
array
(
"hello"
,
"world"
);
list
(
$a
,
$b
,
$c
)
=
$values
;
// $a is "hello", $b is "world", $c is NULL
Two or more consecutive commas in the list()
skip values in the array:
$values
=
range
(
'a'
,
'e'
);
// use range to populate the array
list
(
$m
,
,
$n
,
,
$o
)
=
$values
;
// $m is "a", $n is "c", $o is "e"
To extract only a subset of the array, use the array_slice()
function:
$subset
=
array_slice
(
array
,
offset
,
length
);
The array_slice()
function returns a new array consisting of a consecutive series of values from the original array. The offset parameter identifies the initial element to copy (0
represents the first element in the array), and the length parameter identifies the number of values to copy. The new array has consecutive numeric keys starting at 0. For example:
$people
=
array
(
"Tom"
,
"Dick"
,
"Harriet"
,
"Brenda"
,
"Jo"
);
$middle
=
array_slice
(
$people
,
2
,
2
);
// $middle is array("Harriet", "Brenda")
It is generally only meaningful to use array_slice()
on indexed arrays (i.e., those with consecutive integer indices starting at 0):
// this use of array_slice() makes no sense
$person
=
array
(
'name'
=>
"Fred"
,
'age'
=>
35
,
'wife'
=>
"Betty"
);
$subset
=
array_slice
(
$person
,
1
,
2
);
// $subset is array(0 => 35, 1 => "Betty")
Combine array_slice()
with list()
to extract only some values to variables:
$order
=
array
(
"Tom"
,
"Dick"
,
"Harriet"
,
"Brenda"
,
"Jo"
);
list
(
$second
,
$third
)
=
array_slice
(
$order
,
1
,
2
);
// $second is "Dick", $third is "Harriet"
To divide an array into smaller, evenly sized arrays, use the array_chunk()
function:
$chunks
=
array_chunk
(
array
,
size
[,
preserve_keys
]);
The function returns an array of the smaller arrays. The third argument, preserve_keys, is a Boolean value that determines whether the elements of the new arrays have the same keys as in the original (useful for associative arrays) or new numeric keys starting from 0 (useful for indexed arrays). The default is to assign new keys, as shown here:
$nums
=
range
(
1
,
7
);
$rows
=
array_chunk
(
$nums
,
3
);
print_r
(
$rows
);
Array
(
[
0
]
=>
Array
(
[
0
]
=>
1
[
1
]
=>
2
[
2
]
=>
3
)
[
1
]
=>
Array
(
[
0
]
=>
4
[
1
]
=>
5
[
2
]
=>
6
)
[
2
]
=>
Array
(
[
0
]
=>
7
)
)
The array_keys()
function returns an array consisting of only the keys in the array in internal order:
$arrayOfKeys
=
array_keys
(
array
);
Here’s an example:
$person
=
array
(
'name'
=>
"Fred"
,
'age'
=>
35
,
'wife'
=>
"Wilma"
);
$keys
=
array_keys
(
$person
);
// $keys is array("name", "age", "wife")
PHP also provides a (generally less useful) function to retrieve an array of just the values in an array, array_values()
:
$arrayOfValues
=
array_values
(
array
);
As with array_keys()
, the values are returned in the array’s internal order:
$values
=
array_values
(
$person
);
// $values is array("Fred", 35, "Wilma");
To see if an element exists in the array, use the array_key_exists()
function:
if
(
array_key_exists
(
key
,
array
))
{
...
}
The function returns a Boolean value that indicates whether the first argument is a valid key in the array given as the second argument.
It’s not sufficient to simply say:
if
(
$person
[
'name'
])
{
...
}
// this can be misleading
Even if there is an element in the array with the key name
, its corresponding value might be false (i.e., 0
, NULL
, or the empty string). Instead, use array_key_exists()
, as follows:
$person
[
'age'
]
=
0
;
// unborn?
if
(
$person
[
'age'
])
{
echo
"
true!
\n
"
;
}
if
(
array_key_exists
(
'age'
,
$person
))
{
echo
"
exists!
\n
"
;
}
exists
!
Many people use the isset()
function instead, which returns true
if the element exists and is not NULL
:
$a
=
array
(
0
,
NULL
,
''
);
function
tf
(
$v
)
{
return
$v
?
'T'
:
'F'
;
}
for
(
$i
=
0
;
$i
<
4
;
$i
++
)
{
printf
(
"
%d: %s %s
\n
"
,
$i
,
tf
(
isset
(
$a
[
$i
])),
tf
(
array_key_exists
(
$i
,
$a
)));
}
0
:
T
T
1
:
F
T
2
:
T
T
3
:
F
F
The array_splice()
function can remove or insert elements in an array and optionally create another array from the removed elements:
$removed
=
array_splice
(
array
,
start
[,
length
[,
replacement
]
]);
We’ll look at array_splice()
using this array:
$subjects
=
array
(
"physics"
,
"chem"
,
"math"
,
"bio"
,
"cs"
,
"drama"
,
"classics"
);
We can remove the "math"
, "bio"
, and "cs"
elements by telling array_splice()
to start at position 2 and remove 3 elements:
$removed
=
array_splice
(
$subjects
,
2
,
3
);
// $removed is array("math", "bio", "cs")
// $subjects is array("physics", "chem", "drama", "classics")
If you omit the length, array_splice()
removes to the end of the array:
$removed
=
array_splice
(
$subjects
,
2
);
// $removed is array("math", "bio", "cs", "drama", "classics")
// $subjects is array("physics", "chem")
If you simply want to delete elements from the source array and you don’t care about retaining their values, you don’t need to store the results of array_splice()
:
array_splice
(
$subjects
,
2
);
// $subjects is array("physics", "chem");
To insert elements where others were removed, use the fourth argument:
$new
=
array
(
"law"
,
"business"
,
"IS"
);
array_splice
(
$subjects
,
4
,
3
,
$new
);
// $subjects is array("physics", "chem", "math", "bio", "law", "business", "IS")
The size of the replacement array doesn’t have to be the same as the number of elements you delete. The array grows or shrinks as needed:
$new
=
array
(
"law"
,
"business"
,
"IS"
);
array_splice
(
$subjects
,
3
,
4
,
$new
);
// $subjects is array("physics", "chem", "math", "law", "business", "IS")
To insert new elements into the array while pushing existing elements to the right, delete zero elements:
$subjects
=
array
(
"physics"
,
"chem"
,
"math');
$new
= array("
law
", "
business
");
array_splice(
$subjects
, 2, 0,
$new
);
//
$subjects
is array("
physics
", "
chem
", "
law
", "
business
", "
math
")
Although the examples so far have used an indexed array, array_splice()
also works on associative arrays:
$capitals
=
array
(
'USA'
=>
"Washington"
,
'Great Britain'
=>
"London"
,
'New Zealand'
=>
"Wellington"
,
'Australia'
=>
"Canberra"
,
'Italy'
=>
"Rome"
,
'Canada'
=>
"Ottawa"
);
$downUnder
=
array_splice
(
$capitals
,
2
,
2
);
// remove New Zealand and Australia
$france
=
array
(
'France'
=>
"Paris"
);
array_splice
(
$capitals
,
1
,
0
,
$france
);
// insert France between USA and GB
PHP provides two functions, extract()
and compact()
, that convert between arrays and variables. The names of the variables correspond to keys in the array, and the values of the variables become the values in the array. For instance, this array
$person
=
array
(
'name'
=>
"Fred"
,
'age'
=>
35
,
'wife'
=>
"Betty"
);
can be converted to, or built from, these variables:
$name
=
"Fred"
;
$age
=
35
;
$wife
=
"Betty"
;
The extract()
function automatically creates local variables from an array. The indices of the array elements become the variable names:
extract
(
$person
);
// $name, $age, and $wife are now set
If a variable created by the extraction has the same name as an existing one, the existing variable’s value is overwritten with the one from the array.
You can modify extract()
’s behavior by passing a second argument. The Appendix describes the possible values for this second argument. The most useful value is EXTR_PREFIX_ALL
, which indicates that the third argument to extract()
is a prefix for the variable names that are created. This helps ensure that you create unique variable names when you use extract()
. It is good PHP style to always use EXTR_
PREFIX_ALL
, as shown here:
$shape
=
"
round
"
;
$array
=
array
(
'cover'
=>
"
bird
"
,
'shape'
=>
"
rectangular
"
);
extract
(
$array
,
EXTR_PREFIX_ALL
,
"
book
"
);
echo
"
Cover:
{
$book_cover
}
, Book Shape:
{
$book_shape
}
, Shape:
{
$shape
}
"
;
Cover
:
bird
,
Book
Shape
:
rectangular
,
Shape
:
round
The compact()
function is the reverse of extract()
; you pass it the variable names to compact either as separate parameters or in an array. The compact()
function creates an associative array whose keys are the variable names and whose values are the variable’s values. Any names in the array that do not correspond to actual variables are skipped. Here’s an example of compact()
in action:
$color
=
"indigo"
;
$shape
=
"curvy"
;
$floppy
=
"none"
;
$a
=
compact
(
"color"
,
"shape"
,
"floppy"
);
// or
$names
=
array
(
"color"
,
"shape"
,
"floppy"
);
$a
=
compact
(
$names
);
The most common task with arrays is to do something with every element—for instance, sending mail to each element of an array of addresses, updating each file in an array of filenames, or adding up each element of an array of prices. There are several ways to traverse arrays in PHP, and the one you choose will depend on your data and the task you’re performing.
The most common way to loop over elements of an array is to use the foreach
construct:
$addresses
=
array
(
"
spam@cyberpromo.net
"
,
"
abuse@example.com
"
);
foreach
(
$addresses
as
$value
)
{
echo
"
Processing
{
$value
}
\n
"
;
}
Processing
spam
@
cyberpromo
.
net
Processing
abuse
@
example
.
com
PHP executes the body of the loop (the echo
statement) once for each element of $addresses
in turn, with $value
set to the current element. Elements are processed by their internal order.
An alternative form of foreach
gives you access to the current key:
$person
=
array
(
'name'
=>
"
Fred
"
,
'age'
=>
35
,
'wife'
=>
"
Wilma
"
);
foreach
(
$person
as
$key
=>
$value
)
{
echo
"
Fred's
{
$key
}
is
{
$value
}
\n
"
;
}
Fred
's name is Fred
Fred'
s
age
is
35
Fred
'
s
wife
is
Wilma
In this case, the key for each element is placed in $key
and the corresponding value is placed in $value
.
The foreach
construct does not operate on the array itself, but rather on a copy of it. You can insert or delete elements in the body of a foreach
loop, safe in the knowledge that the loop won’t attempt to process the deleted or inserted elements.
Every PHP array keeps track of the current element you’re working with; the pointer to the current element is known as the iterator. PHP has functions to set, move, and reset this iterator. The iterator functions are:
current()
reset()
next()
prev()
end()
each()
key()
The each()
function is used to loop over the elements of an array. It processes elements according to their internal order:
reset
(
$addresses
);
while
(
list
(
$key
,
$value
)
=
each
(
$addresses
))
{
echo
"
{
$key
}
is
{
$value
}
<br />
\n
"
;
}
0
is
spam
@
cyberpromo
.
net
1
is
abuse
@
example
.
com
This approach does not make a copy of the array, as foreach
does. This is useful for very large arrays when you want to conserve memory.
The iterator functions are useful when you need to consider some parts of the array separately from others. Example 5-1 shows code that builds a table, treating the first index and value in an associative array as table column headings.
$ages
=
array
(
'Person'
=>
"Age"
,
'Fred'
=>
35
,
'Barney'
=>
30
,
'Tigger'
=>
8
,
'Pooh'
=>
40
);
// start table and print heading
reset
(
$ages
);
list
(
$c1
,
$c2
)
=
each
(
$ages
);
echo
(
"<table>
\n
<tr><th>
{
$c1
}
</th><th>
{
$c2
}
</th></tr>
\n
"
);
// print the rest of the values
while
(
list
(
$c1
,
$c2
)
=
each
(
$ages
))
{
echo
(
"<tr><td>
{
$c1
}
</td><td>
{
$c2
}
</td></tr>
\n
"
);
}
// end the table
echo
(
"</table>"
);
If you know that you are dealing with an indexed array, where the keys are consecutive integers beginning at 0, you can use a for
loop to count through the indices. The for
loop operates on the array itself, not on a copy of the array, and processes elements in key order regardless of their internal order.
Here’s how to print an array using for
:
$addresses
=
array
(
"
spam@cyberpromo.net
"
,
"
abuse@example.com
"
);
$addressCount
=
count
(
$addresses
);
for
(
$i
=
0
;
$i
<
$addressCount
;
$i
++
)
{
$value
=
$addresses
[
$i
];
echo
"
{
$value
}
\n
"
;
}
spam
@
cyberpromo
.
net
abuse
@
example
.
com
PHP provides a mechanism, array_walk()
, for calling a user-defined function once per element in an array:
array_walk
(
array
,
callable
);
The function you define takes in two or, optionally, three arguments: the first is the element’s value, the second is the element’s key, and the third is a value supplied to array_walk()
when it is called. For instance, here’s another way to print table columns made of the values from an array:
$printRow
=
function
(
$value
,
$key
)
{
(
"<tr><td>
{
$key
}
</td><td>
{
$value
}
</td></tr>
\n
"
);
};
$person
=
array
(
'name'
=>
"Fred"
,
'age'
=>
35
,
'wife'
=>
"Wilma"
);
echo
"<table border=1>"
;
array_walk
(
$person
,
$printRow
);
echo
"</table>"
;
A variation of this example specifies a background color using the optional third argument to array_walk()
. This parameter gives us the flexibility we need to print many tables, with many background colors:
function
printRow
(
$value
,
$key
,
$color
)
{
echo
"<tr>
\n
<td bgcolor=
\"
{
$color
}
\"
>
{
$value
}
</td>"
;
echo
"<td bgcolor=
\"
{
$color
}
\"
>
{
$key
}
</td>
\n
</tr>
\n
"
;
}
$person
=
array
(
'name'
=>
"Fred"
,
'age'
=>
35
,
'wife'
=>
"Wilma"
);
echo
"<table border=
\"
1
\"
>"
;
array_walk
(
$person
,
"printRow"
,
"lightblue"
);
echo
"</table>"
;
If you have multiple options you want to pass into the called function, simply pass an array in as a third parameter:
$extraData
=
array
(
'border'
=>
2
,
'color'
=>
"
red
"
);
$baseArray
=
array
(
"
Ford
"
,
"
Chrysler
"
,
"
Volkswagen
"
,
"
Honda
"
,
"
Toyota
"
);
array_walk
(
$baseArray
,
"
walkFunction
"
,
$extraData
);
function
walkFunction
(
$item
,
$index
,
$data
)
{
echo
"
{
$item
}
<- item, then border:
{
$data
[
'border'
]
}
"
;
echo
"
color->
{
$data
[
'color'
]
}
<br />
"
;
}
Ford
<-
item
,
then
border
:
2
color
->
red
Crysler
<-
item
,
then
border
:
2
color
->
red
VW
<-
item
,
then
border
:
2
color
->
red
Honda
<-
item
,
then
border
:
2
color
->
red
Toyota
<-
item
,
then
border
:
2
color
->
red
The array_walk()
function processes elements in their internal order.
A cousin of array_walk()
, array_reduce()
applies a function to each element of the array in turn, to build a single value:
$result
=
array_reduce
(
array
,
callable
[,
default
]);
The function takes two arguments: the running total, and the current value being processed. It should return the new running total. For instance, to add up the squares of the values of an array, use:
$addItUp
=
function
(
$runningTotal
,
$currentValue
)
{
$runningTotal
+=
$currentValue
*
$currentValue
;
return
$runningTotal
;
};
$numbers
=
array
(
2
,
3
,
5
,
7
);
$total
=
array_reduce
(
$numbers
,
$addItUp
);
echo
$total
;
87
The array_reduce()
line makes these function calls:
addItUp
(
0
,
2
);
addItUp
(
4
,
3
);
addItUp
(
13
,
5
);
addItUp
(
38
,
7
);
The default argument, if provided, is a seed value. For instance, if we change the call to array_reduce()
in the previous example to:
$total
=
array_reduce
(
$numbers
,
"addItUp"
,
11
);
The resulting function calls are:
addItUp
(
11
,
2
);
addItUp
(
15
,
3
);
addItUp
(
24
,
5
);
addItUp
(
49
,
7
);
If the array is empty, array_reduce()
returns the default value. If no default value is given and the array is empty, array_reduce()
returns NULL
.
The in_array()
function returns true
or false
, depending on whether the first argument is an element in the array given as the second argument:
if
(
in_array
(
to_find
,
array
[,
strict
]))
{
...
}
If the optional third argument is true
, the types of to_find and the value in the array must match. The default is to not check the data types.
Here’s a simple example:
$addresses
=
array
(
"spam@cyberpromo.net"
,
"abuse@example.com"
,
"root@example.com"
);
$gotSpam
=
in_array
(
"spam@cyberpromo.net"
,
$addresses
);
// $gotSpam is true
$gotMilk
=
in_array
(
"milk@tucows.com"
,
$addresses
);
// $gotMilk is false
PHP automatically indexes the values in arrays, so in_array()
is generally much faster than a loop checking every value in the array to find the one you want.
Example 5-2 checks whether the user has entered information in all the required fields in a form.
<?php
function
hasRequired
(
$array
,
$requiredFields
)
{
$array
=
$keys
=
array_keys
(
$array
);
foreach
(
$requiredFields
as
$fieldName
)
{
if
(
!
in_array
(
$fieldName
,
$keys
))
{
return
false
;
}
}
return
true
;
}
if
(
$_POST
[
'submitted'
])
{
$testArray
=
array_filter
(
$_POST
);
echo
"<p>You "
;
echo
hasRequired
(
$testArray
,
array
(
'name'
,
'email_address'
)
)
?
"did"
:
"did not"
;
echo
" have all the required fields.</p>"
;
}
?>
<form
action=
"
<?php
echo
$_SERVER
[
'PHP_SELF'
];
?>
"
method=
"POST"
>
<p>
Name:<input
type=
"text"
name=
"name"
/><br
/>
Email address:<input
type=
"text"
name=
"email_address"
/><br
/>
Age (optional):<input
type=
"text"
name=
"age"
/>
</p>
<p
align=
"center"
>
<input
type=
"submit"
value=
"submit"
name=
"submitted"
/>
</p>
</form>
A variation on in_array()
is the array_search()
function. While in_array()
returns true
if the value is found, array_search()
returns the key of the element, if found:
$person
=
array
(
'name'
=>
"
Fred
"
,
'age'
=>
35
,
'wife'
=>
"
Wilma
"
);
$k
=
array_search
(
"
Wilma
"
,
$person
);
echo
(
"
Fred's
{
$k
}
is Wilma
\n
"
);
Fred
'
s
wife
is
Wilma
The array_search()
function also takes the optional third strict argument, which requires that the types of the value being searched for and the value in the array match.
Sorting changes the internal order of elements in an array and optionally rewrites the keys to reflect this new order. For example, you might use sorting to arrange a list of scores from biggest to smallest, to alphabetize a list of names, or to order a set of users based on how many messages they posted.
PHP provides three ways to sort arrays—sorting by keys, sorting by values without changing the keys, or sorting by values and then changing the keys. Each kind of sort can be done in ascending order, descending order, or an order determined by a user-defined function.
The functions provided by PHP to sort an array are shown in Table 5-1.
Effect | Ascending | Descending | User-defined order |
---|---|---|---|
Sort array by values, then reassign indices starting with 0 | sort() |
rsort() |
usort() |
Sort array by values | asort() |
arsort() |
uasort() |
Sort array by keys | ksort() |
krsort() |
uksort() |
The sort()
, rsort()
, and usort()
functions are designed to work on indexed arrays because they assign new numeric keys to represent the ordering. They’re useful when you need to answer questions such as “What are the top 10 scores?” and “Who’s the third person in alphabetical order?” The other sort functions can be used on indexed arrays, but you’ll only be able to access the sorted ordering by using traversal constructs such as foreach
and next()
.
To sort names into ascending alphabetical order, do something like this:
$names
=
array
(
"Cath"
,
"Angela"
,
"Brad"
,
"Mira"
);
sort
(
$names
);
// $names is now "Angela", "Brad", "Cath", "Mira"
To get them in reverse alphabetical order, simply call rsort()
instead of sort()
.
If you have an associative array that maps usernames to minutes of login time, you can use arsort()
to display a table of the top three, as shown here:
$logins
=
array
(
'njt'
=>
415
,
'kt'
=>
492
,
'rl'
=>
652
,
'jht'
=>
441
,
'jj'
=>
441
,
'wt'
=>
402
,
'hut'
=>
309
,
);
arsort
(
$logins
);
$numPrinted
=
0
;
echo
"<table>
\n
"
;
foreach
(
$logins
as
$user
=>
$time
)
{
echo
(
"<tr><td>
{
$user
}
</td><td>
{
$time
}
</td></tr>
\n
"
);
if
(
++
$numPrinted
==
3
)
{
break
;
// stop after three
}
}
echo
"</table>"
;
If you want that table displayed in ascending order by username, use ksort()
instead.
User-defined ordering requires that you provide a function that takes two values and returns a value that specifies the order of the two values in the sorted array. The function should return 1
if the first value is greater than the second, −1
if the first value is less than the second, and 0
if the values are the same for the purposes of your custom sort order.
The program in Example 5-3 applies the various sorting functions to the same data.
<?php
function
userSort
(
$a
,
$b
)
{
// smarts is all-important, so sort it first
if
(
$b
==
"smarts"
)
{
return
1
;
}
else
if
(
$a
==
"smarts"
)
{
return
−1
;
}
return
(
$a
==
$b
)
?
0
:
((
$a
<
$b
)
?
−1
:
1
);
}
$values
=
array
(
'name'
=>
"Buzz Lightyear"
,
'email_address'
=>
"buzz@starcommand.gal"
,
'age'
=>
32
,
'smarts'
=>
"some"
);
if
(
$_POST
[
'submitted'
])
{
$sortType
=
$_POST
[
'sort_type'
];
if
(
$sortType
==
"usort"
||
$sortType
==
"uksort"
||
$sortType
==
"uasort"
)
{
$sortType
(
$values
,
"userSort"
);
}
else
{
$sortType
(
$values
);
}
}
?>
<form
action=
"
<?php
echo
$_SERVER
[
'PHP_SELF'
];
?>
"
method=
"post"
>
<p>
<input
type=
"radio"
name=
"sort_type"
value=
"sort"
checked=
"checked"
/>
Standard<br
/>
<input
type=
"radio"
name=
"sort_type"
value=
"rsort"
/>
Reverse<br
/>
<input
type=
"radio"
name=
"sort_type"
value=
"usort"
/>
User-defined<br
/>
<input
type=
"radio"
name=
"sort_type"
value=
"ksort"
/>
Key<br
/>
<input
type=
"radio"
name=
"sort_type"
value=
"krsort"
/>
Reverse key<br
/>
<input
type=
"radio"
name=
"sort_type"
value=
"uksort"
/>
User-defined key<br
/>
<input
type=
"radio"
name=
"sort_type"
value=
"asort"
/>
Value<br
/>
<input
type=
"radio"
name=
"sort_type"
value=
"arsort"
/>
Reverse value<br
/>
<input
type=
"radio"
name=
"sort_type"
value=
"uasort"
/>
User-defined value<br
/>
</p>
<p
align=
"center"
><input
type=
"submit"
value=
"Sort"
name=
"submitted"
/></p>
<p>
Values<?php
echo
$_POST
[
'submitted'
]
?
"sorted by
{
$sortType
}
"
:
"unsorted"
;
?>
:</p>
<ul>
<?php
foreach
(
$values
as
$key
=>
$value
)
{
echo
"<li><b>
{
$key
}
</b>:
{
$value
}
</li>"
;
}
?>
</ul>
</form>
PHP’s built-in sort functions correctly sort strings and numbers, but they don’t correctly sort strings that contain numbers. For example, if you have the filenames ex10.php, ex5.php, and ex1.php, the normal sort functions will rearrange them in this order: ex1.php, ex10.php, ex5.php. To correctly sort strings that contain numbers, use the natsort()
and natcasesort()
functions:
$output
=
natsort
(
input
);
$output
=
natcasesort
(
input
);
The array_multisort()
function sorts multiple indexed arrays at once:
array_multisort
(
array1
[,
array2
,
...
]);
Pass it a series of arrays and sorting orders (identified by the SORT_ASC
or SORT_DESC
constants), and it reorders the elements of all the arrays, assigning new indices. It is similar to a join operation on a relational database.
Imagine that you have a lot of people, and several pieces of data on each person:
$names
=
array
(
"Tom"
,
"Dick"
,
"Harriet"
,
"Brenda"
,
"Joe"
);
$ages
=
array
(
25
,
35
,
29
,
35
,
35
);
$zips
=
array
(
80522
,
'02140'
,
90210
,
64141
,
80522
);
The first element of each array represents a single record—all the information known about Tom. Similarly, the second element constitutes another record—all the information known about Dick. The array_multisort()
function reorders the elements of the arrays, preserving the records. That is, if "Dick"
ends up first in the $names
array after the sort, the rest of Dick’s information will be first in the other arrays too. (Note that we needed to quote Dick’s zip code to prevent it from being interpreted as an octal constant.)
Here’s how to sort the records first ascending by age, then descending by zip code:
array_multisort
(
$ages
,
SORT_ASC
,
$zips
,
SORT_DESC
,
$names
,
SORT_ASC
);
We need to include $names
in the function call to ensure that Dick’s name stays with his age and zip code. Printing out the data shows the result of the sort:
for
(
$i
=
0
;
$i
<
count
(
$names
);
$i
++
)
{
echo
"
{
$names
[
$i
]
}
,
{
$ages
[
$i
]
}
,
{
$zips
[
$i
]
}
\n
"
;
}
Tom
,
25
,
80522
Harriet
,
29
,
90210
Joe
,
35
,
80522
Brenda
,
35
,
64141
Dick
,
35
,
02140
The array_reverse()
function reverses the internal order of elements in an array:
$reversed
=
array_reverse
(
array
);
Numeric keys are renumbered starting at 0, while string indices are unaffected. In general, it’s better to use the reverse-order sorting functions instead of sorting and then reversing the order of an array.
The array_flip()
function returns an array that reverses the order of each original element’s key-value pair:
$flipped
=
array_flip
(
array
);
That is, for each element of the array whose value is a valid key, the element’s value becomes its key and the element’s key becomes its value. For example, if you have an array that maps usernames to home directories, you can use array_flip()
to create an array that maps home directories to usernames:
$u2h
=
array
(
'gnat'
=>
"/home/staff/nathan"
,
'frank'
=>
"/home/action/frank"
,
'petermac'
=>
"/home/staff/petermac"
,
'ktatroe'
=>
"/home/staff/kevin"
);
$h2u
=
array_flip
(
$u2h
);
$user
=
$h2u
[
"/home/staff/kevin"
];
// $user is now 'ktatroe'
Elements whose original values are neither strings nor integers are left alone in the resulting array. The new array lets you discover the key in the original array given its value, but this technique works effectively only when the original array has unique values.
To traverse the elements in an array in random order, use the shuffle()
function. It replaces all existing keys—string or numeric—with consecutive integers starting at 0.
Here’s how to randomize the order of the days of the week:
$weekdays
=
array
(
"
Monday
"
,
"
Tuesday
"
,
"
Wednesday
"
,
"
Thursday
"
,
"
Friday
"
);
shuffle
(
$weekdays
);
print_r
(
$weekdays
);
Array
(
[
0
]
=>
Tuesday
[
1
]
=>
Thursday
[
2
]
=>
Monday
[
3
]
=>
Friday
[
4
]
=>
Wednesday
)
Obviously, the order after you shuffle()
may not be the same as the sample output here due to the random nature of the function. Unless you are interested in getting multiple random elements from an array without repeating any specific item, using the rand()
function to pick an index is more efficient.
PHP has several useful built-in functions for modifying or applying an operation to all elements of an array. You can calculate the sum of an array, merge multiple arrays, find the difference between two arrays, and more.
The array_merge()
function intelligently merges two or more arrays:
$merged
=
array_merge
(
array1
,
array2
[,
array
...
])
If a numeric key from an earlier array is repeated, the value from the later array is assigned a new numeric key:
$first
=
array
(
"hello"
,
"world"
);
// 0 => "hello", 1 => "world"
$second
=
array
(
"exit"
,
"here"
);
// 0 => "exit", 1 => "here"
$merged
=
array_merge
(
$first
,
$second
);
// $merged = array("hello", "world", "exit", "here")
If a string key from an earlier array is repeated, the earlier value is replaced by the later value:
$first
=
array
(
'bill'
=>
"clinton"
,
'tony'
=>
"danza"
);
$second
=
array
(
'bill'
=>
"gates"
,
'adam'
=>
"west"
);
$merged
=
array_merge
(
$first
,
$second
);
// $merged = array('bill' => "gates", 'tony' => "danza", 'adam' => "west")
The array_diff()
function calculates the difference between two or more arrays, returning an array with values from the first array that are not present in the others:
$diff
=
array_diff
(
array1
,
array2
[,
array
...
]);
For example:
$a1
=
array
(
"
bill
"
,
"
claire
"
,
"
ella
"
,
"
simon
"
,
"
judy
"
);
$a2
=
array
(
"
jack
"
,
"
claire
"
,
"
toni
"
);
$a3
=
array
(
"
ella
"
,
"
simon
"
,
"
garfunkel
"
);
// find values of $a1 not in $a2 or $a3
$difference
=
array_diff
(
$a1
,
$a2
,
$a3
);
print_r
(
$difference
);
Array
(
[
0
]
=>
"
bill
"
,
[
4
]
=>
"
judy
"
);
Values are compared using the strict comparison operator ===
, so 1
and "1"
are considered different. The keys of the first array are preserved, so in $diff
the key of "bill"
is 0
and the key of "judy"
is 4
.
In another example, the following code returns the difference of two arrays:
$first
=
array
(
1
,
"
two
"
,
3
);
$second
=
array
(
"
two
"
,
"
three
"
,
"
four
"
);
$difference
=
array_diff
(
$first
,
$second
);
print_r
(
$difference
);
Array
(
[
0
]
=>
1
[
2
]
=>
3
)
To identify a subset of an array based on its values, use the array_filter()
function:
$filtered
=
array_filter
(
array
,
callback
);
Each value of array is passed to the function named in callback. The returned array contains only those elements of the original array for which the function returns a true
value. For example:
function
isOdd
(
$element
)
{
return
$element
%
2
;
}
$numbers
=
array
(
9
,
23
,
24
,
27
);
$odds
=
array_filter
(
$numbers
,
"
isOdd
"
);
// $odds is array(0 => 9, 1 => 23, 3 => 27)
As you can see, the keys are preserved. This function is most useful with associative arrays.
Arrays crop up in almost every PHP program. In addition to their obvious purpose of storing collections of values, they’re also used to implement various abstract data types. In this section, we show how to use arrays to implement sets and stacks.
Arrays enable you to implement the basic operations of set theory: union, intersection, and difference. Each set is represented by an array, and various PHP functions implement the set operations. The values in the set are the values in the array—the keys are not used, but they are generally preserved by the operations.
The union of two sets is all the elements from both sets with duplicates removed. The array_merge()
and array_unique()
functions let you calculate the union. Here’s how to find the union of two arrays:
function
arrayUnion
(
$a
,
$b
)
{
$union
=
array_merge
(
$a
,
$b
);
// duplicates may still exist
$union
=
array_unique
(
$union
);
return
$union
;
}
$first
=
array
(
1
,
"
two
"
,
3
);
$second
=
array
(
"
two
"
,
"
three
"
,
"
four
"
);
$union
=
arrayUnion
(
$first
,
$second
);
print_r
(
$union
);
Array
(
[
0
]
=>
1
[
1
]
=>
two
[
2
]
=>
3
[
4
]
=>
three
[
5
]
=>
four
)
The intersection of two sets is the set of elements they have in common. PHP’s built-in array_intersect()
function takes any number of arrays as arguments and returns an array of those values that exist in each. If multiple keys have the same value, the first key with that value is preserved.
Although not as common in PHP programs as in other programs, one fairly common data type is the last-in first-out (LIFO) stack. We can create stacks using a pair of PHP functions, array_push()
and array_pop()
. The array_push()
function is identical to an assignment to $array[]
. We use array_push()
because it accentuates the fact that we’re working with stacks, and the parallelism with array_pop()
makes our code easier to read. There are also array_shift()
and array_unshift()
functions for treating an array like a queue.
Stacks are particularly useful for maintaining state. Example 5-4 provides a simple state debugger that allows you to print out a list of which functions have been called up to this point (i.e., the stack trace).
$callTrace
=
array
();
function
enterFunction
(
$name
)
{
global
$callTrace
;
$callTrace
[]
=
$name
;
echo
"Entering
{
$name
}
(stack is now: "
.
join
(
' -> '
,
$callTrace
)
.
")<br />"
;
}
function
exitFunction
()
{
echo
"Exiting<br />"
;
global
$callTrace
;
array_pop
(
$callTrace
);
}
function
first
()
{
enterFunction
(
"first"
);
exitFunction
();
}
function
second
()
{
enterFunction
(
"second"
);
first
();
exitFunction
();
}
function
third
()
{
enterFunction
(
"third"
);
second
();
first
();
exitFunction
();
}
first
();
third
();
Here’s the output from Example 5-4:
Entering
first
(
stack
is
now
:
first
)
Exiting
Entering
third
(
stack
is
now
:
third
)
Entering
second
(
stack
is
now
:
third
->
second
)
Entering
first
(
stack
is
now
:
third
->
second
->
first
)
Exiting
Exiting
Entering
first
(
stack
is
now
:
third
->
first
)
Exiting
Exiting
Using the foreach
construct, you can iterate not only over arrays, but also over instances of classes that implement the Iterator
interface (see Chapter 6 for more information on objects and interfaces). To implement the Iterator
interface, you must implement five methods on your class:
current()
key()
next()
rewind()
valid()
true
if the iterator currently points at a valid element, and false
otherwise.Example 5-5 reimplements a simple iterator class containing a static array of data.
class
BasicArray
implements
Iterator
{
private
$position
=
0
;
private
$array
=
[
"
first
"
,
"
second
"
,
"
third
"
];
public
function
__construct
()
{
$this
->
position
=
0
;
}
public
function
rewind
()
{
$this
->
position
=
0
;
}
public
function
current
()
{
return
$this
->
array
[
$this
->
position
];
}
public
function
key
()
{
return
$this
->
position
;
}
public
function
next
()
{
$this
->
position
+=
1
;
}
public
function
valid
()
{
return
isset
(
$this
->
array
[
$this
->
position
]);
}
}
$basicArray
=
new
BasicArray
;
foreach
(
$basicArray
as
$value
)
{
echo
"
{
$value
}
\n
"
;
}
foreach
(
$basicArray
as
$key
=>
$value
)
{
echo
"
{
$key
}
=>
{
$value
}
\n
"
;
}
first
second
third
0
=>
first
1
=>
second
2
=>
third
When you implement the Iterator
interface on a class, it allows you only to traverse elements in instances of that class using the foreach
construct; it does not allow you to treat those instances as arrays or parameters to other methods. This, for example, rewinds the Iterator
pointing at $trie
’s properties using the built-in rewind()
function instead of calling the rewind()
method on $trie
:
class
Trie
implements
Iterator
{
const
POSITION_LEFT
=
"left"
;
const
POSITION_THIS
=
"this"
;
const
POSITION_RIGHT
=
"right"
;
var
$leftNode
;
var
$rightNode
;
var
$position
;
// implement Iterator methods here...
}
$trie
=
new
Trie
();
rewind
(
$trie
);
The optional SPL library provides a wide variety of useful iterators, including filesystem directory, tree, and regex matching iterators.
The last three chapters—on functions, strings, and arrays—have covered a lot of foundational ground. The next chapter builds on this foundation and takes you into the newish world of objects and object-oriented programming (OOP). Some argue that OOP is the better way to program, as it is more encapsulated and reusable than procedural programming. That debate continues, but once you get into the object-oriented approach to programming and understand its benefits, you can make an informed decision about how you’ll program in the future. That said, the overall trend in the programming world is to use OOP as much as possible.
One word of caution before you continue: there are many situations where a novice OOP programmer can get lost, so be sure you’re really comfortable with OOP before you do anything major or mission-critical with it.