Looping a multidimensional array in php
I have a multidimensional array like this:
array(2) {
[1]=>
array(3) {
["eventID"]=>
string(1) "1"
["eventTitle"]=>
string(7) "EVENT 1"
["artists"]=>
array(3) {
[4]=>
array(2) {
["name"]=>
string(8) "ARTIST 1"
["description"]=>
string(13) "artist 1 desc"
["links"]=>
array(2) {
[1]=>
array(2) {
["URL"]=>
string(22) "http://www.artist1.com"
}
[6]=>
array(2) {
["URL"]=>
string(24) "http://www.artist1-2.com"
}
}
}
[5]=>
array(2) {
["name"]=>
string(8) "ARTIST 8"
["description"]=>
string(13) "artist 8 desc"
["links"]=>
array(1) {
[8]=>
array(2) {
["URL"]=>
string(22) "http://www.artist8.com"
}
}
}
[2]=>
array(2) {
["ime"]=>
string(8) "ARTIST 5"
["opis"]=>
string(13) "artist 5 desc"
["links"]=>
array(1) {
[9]=>
array(2) {
["URL"]=>
string(22) "http://www.artist5.com"
}
}
}
}
}
[2]=>
array(3) {
["eventID"]=>
string(1) "2"
["eventTitle"]=>
string(7) "EVENT 2"
["artists"]=>
array(3) {
[76]=>
array(2) {
["name"]=>
string(9) "ARTIST 76"
["description"]=>
string(14) "artist 76 desc"
["links"]=>
array(1) {
[13]=>
array(2) {
["URL"]=>
string(23) "http://www.artist76.com"
}
}
}
[4]=>
array(2) {
["name"]=>
string(8) "ARTIST 4"
["description"]=>
string(13) "artist 4 desc"
["links"]=>
array(1) {
[11]=>
array(2) {
["URL"]=>
string(22) "http://www.artist4.com"
}
}
}
}
}
}
I would like to make html output like this:
--
EVENT 1
ARTIST 1
artist 1 desc
http://www.artist1.com, http://www.artist1-2.com
ARTIST 8
artist 8 desc
http://www.artist8.com
ARTIST 5
artist 5 desc
http://www.artist5.com
--
EVENT 2
ARTIST 76
artist 76 desc
http://www.artist76.com
ARTIST 4
artist 4 desc
http://www.artist4.com
--
etc.
I'm confused about digging deeper and deeper in arrays, especially when my array keys are not sequential numbers but IDs of artist/link/etc.
These arrays will kill me, honestly! =)
Thanks for any help in advance!!!
Solution 1:
You're best using the foreach
construct to loop over your array. The following is untested and is off the top of my head (and probably therefore not as thought through as it should be!) but should give you a good start:
foreach ($mainArray as $event)
{
print $event["eventTitle"];
foreach ($event["artists"] as $artist)
{
print $artist["name"];
print $artist["description"];
$links = array();
foreach ($artist["links"] as $link)
{
$links[] = $link["URL"];
}
print implode(",", $links);
}
}
Solution 2:
The foreach
statement will take care of all of this for you, including the associative hashes. Like this:
foreach($array as $value) {
foreach($value as $key => $val) {
if($key == "links") {
}
/* etc */
}
}
Solution 3:
I think a good way to approach this is "bottom up", ie. work out what to do with the inner-most values, then use those results to work out the next-level-up, and so on until we reach the top. It's also good practice to write our code in small, single-purpose, re-usable functions as much as possible, so that's what I'll be doing.
Note that I'll assume your data is safe (ie. it's not been provided by a potentially-malicious user). I'll also assume that the keys "ime" and "opi" are meant to match the "name" and "description" of the other arrays ;)
We can ignore the innermost strings themselves, since we don't need to modify them. In that case the inner-most structure I can see are the individual links, which are arrays containing a 'URL' value. Here's some code to render a single link:
function render_link($link) {
return "<a href='{$link['URL']}'>{$link['URL']}</a>";
}
This has reduced an array down to a string, so we can use it remove the inner-most layer. For example:
// Input
array('URL' => "http://www.artist1.com")
// Output
"<a href='http://www.artist1.com'>http://www.artist1.com</a>"
Now we move out a layer to the 'links' arrays. There are two things to do here: apply "render_link" to each element, which we can do using "array_map", then reduce the resulting array of strings down to a single comma-separated string, which we can do using the "implode" function:
function render_links($links_array) {
$rendered_links = array_map('render_link', $links_array);
return implode(', ', $rendered_links);
}
This has removed another layer, for example:
// Input
array(1 => array('URL' => "http://www.artist1.com"),
6 => array('URL' => "http://www.artist1-2.com"))
// Output
"<a href='http://www.artist1.com'>http://www.artist1.com</a>, <a href='http://www.artist1-2.com'>http://www.artist1-2.com</a>"
Now we can go out another level to an individual artist, which is an array containing 'name', 'description' and 'links'. We know how to render 'links', so we can reduce these down to a single string separated by linebreaks:
function render_artist($artist) {
// Replace the artist's links with a rendered version
$artist['links'] = render_links($artist['links']);
// Render this artist's details on separate lines
return implode("\n<br />\n", $artist);
}
This has removed another layer, for example:
// Input
array('name' => 'ARTIST 1',
'description' => 'artist 1 desc',
'links' => array(
1 => array(
'URL' => 'http://www.artist1.com')
6 => array(
'URL' => 'http://www.artist1-2.com')))
// Output
"ARTIST 1
<br />
artist 1 desc
<br />
<a href='http://www.artist1.com'>http://www.artist1.com</a>, <a href='http://www.artist1-2.com'>http://www.artist1-2.com</a>"
Now we can move out a layer to the 'artists' arrays. Just like when we went from a single link to the 'links' arrays, we can use array_map to handle the contents and implode to join them together:
function render_artists($artists) {
$rendered = array_map('render_artist', $artists);
return implode("\n<br /><br />\n", $rendered);
}
This has removed another layer (no example, because it's getting too long ;) )
Next we have an event, which we can tackle in the same way we did for the artist, although I'll also remove the ID number and format the title:
function render_event($event) {
unset($event['eventID']);
$event['eventTitle'] = "<strong>{$event['eventTitle']}</strong>";
$event['artists'] = render_artists($event['artists']);
return implode("\n<br />\n", $event);
}
Now we've reached the outer array, which is an array of events. We can handle this just like we did for the arrays of artists:
function render_events($events) {
$rendered = array_map('render_event', $events);
return implode("\n<br /><br />--<br /><br />", $rendered);
}
You might want to stop here, since passing your array to render_events will give you back the HTML you want:
echo render_events($my_data);
However, if we want more of a challenge we can try to refactor the code we've just written to be less redundant and more re-usable. One simple step is to get rid of render_links, render_artists and render_events since they're all variations on a more-general pattern:
function reduce_with($renderer, $separator, $array) {
return implode($separator, array_map($renderer, $array));
}
function render_artist($artist) {
$artist['links'] = reduce_with('render_link', ', ', $artist['links']);
return implode("\n<br />\n", $artist);
}
function render_event($event) {
unset($event['eventID']);
$event['eventTitle'] = "<strong>{$event['eventTitle']}</strong>";
$event['artists'] = reduce_with('render_artist',
"\n<br /><br />\n",
$event['artists']);
return implode("\n<br />\n", $event);
}
echo reduce_with('render_event', "\n<br /><br />--<br /><br />", $my_data);
If this is part of a larger application, we may want to tease out some more general patterns. This makes the code slightly more complex, but much more re-usable. Here are a few patterns I've spotted:
// Re-usable library code
// Partial application: apply some arguments now, the rest later
function papply() {
$args1 = func_get_args();
return function() use ($args1) {
return call_user_func_array(
'call_user_func',
array_merge($args1, func_get_args()));
};
}
// Function composition: chain functions together like a(b(c(...)))
function compose() {
$funcs = array_reverse(func_get_args());
$first = array_shift($funcs);
return function() use ($funcs, $first) {
return array_reduce($funcs,
function($x, $f) { return $f($x); },
call_user_func_array($first, func_get_args()));
};
}
// Transform or remove a particular element in an array
function change_elem($key, $func, $array) {
if is_null($func) unset($array[$key]);
else $array[$key] = $func($array[$key]);
return $array;
}
// Transform all elements then implode together
function reduce_with($renderer, $separator) {
return compose(papply('implode', $separator),
papply('array_map', $renderer));
}
// Wrap in HTML
function tag($tag, $text) {
return "<{$tag}>{$text}</{$tag}>";
}
// Problem-specific code
function render_link($link) {
return "<a href='{$link['URL']}'>{$link['URL']}</a>";
}
$render_artist = compose(
papply('implode', "\n<br />\n"),
papply('change_elem', 'links', papply('reduce_with',
'render_link',
', '));
$render_event = compose(
papply('implode', "\n<br />\n"),
papply('change_elem', null, 'eventID'),
papply('change_elem', 'eventTitle', papply('tag', 'strong')),
papply('change_elem', 'artists', papply('reduce_with',
$render_artist,
"\n<br /><br />\n")));
echo reduce_with($render_event, "\n<br /><br />--<br /><br />", $my_data);