2

I've spent most of the day trying to wrap my head around multidimensional arrays, in order to build a PHP function that I can call to populate an image gallery that could potentially hold 100s of elements. At this point it would have been easier to just copy and paste the HTML portion over and over again, but I'd like to figure out how to make this work, for future projects.

I have found solutions to other questions about foreach loops with multidimensinoal arrays, but I haven't been able to adapt them to my case. In one instance, the closest to my own situation, the solution no longer works--but I don't have the rep to make a comment about it. I used the code from that example as the basis for my own attempt. I welcome any assistance! My comments may help explain what I'm trying to achieve, if it isn't already clear.

Here is my PHP code so far:

<?php 
// Use PHP array to populate the image path, title (for alt tag), and longer description (for image caption)
function build_html ($imgsrc, $title, $desc) {
    echo
        '<div class="responsive">
        <div class="gallery">
            <img src="$imgsrc" alt="$title" width="300" height="200">
            <div class="desc">$desc</div>
        </div>
        </div>';
}
// List of images; filenames will be numerical, so later I may switch to using $i++ shortcut in place of imgsrc
$gallery = array (
    array (
    "imgsrc"    =>  "i/Alice0.jpg", 
    "title"     =>  "Front of House", 
    "desc"      =>  "View of the house from the front sidewalk"
    ),
    array ( 
    "imgsrc"    =>  "i/Alice1.jpg", 
    "title"     =>  "Front door", 
    "desc"      =>  "Close-up of front door"
    )
);
// This is just for debugging, to confirm that I really have a multidimensional array
print_r($gallery); 

// BROKEN: this should use foreach loop(s) to gather the elements and pass them to the build_html function defined above.  
function display_gallery () { 
    foreach ($gallery as $img) { 
        $imgsrc = $img['imgsrc'];
        $title = $img['title'];
        $desc = $img['desc'];
        build_html($imgsrc, $title, $desc);

    }
}
// Calling the function in body of page
display_gallery();
?>

Edit: The error message I'm currently getting is:

Warning: Invalid argument supplied for foreach() in /home/varlokkur/house.anthropo.org/index.php on line 121

Edit: In case anyone stumbles across this question in the future, here is my modified code that works as expected. Note that the solution offered by aendeerei is a better example to follow, but this solution is closest to the original code. 2 changes to make it work:

  1. Fixed built_html function to properly use the variables that are passed
  2. Fixed display_gallery function to use the $gallery variable

Fixed code:

<?php 
// Use PHP array to populate the image path, title (for alt tag), and longer description (for image caption)
function build_html ($imgsrc, $title, $desc) {
    echo
        '
        <div class="responsive">
        <div class="gallery">
            <img src="' . $imgsrc . '" alt="' . $title . '" width="300" height="200">
            <div class="desc">'. $desc . '</div>
        </div>
        </div>
        ';
}

// List of images; filenames will be numerical, so later I may switch to using $i++ shortcut in place of imgsrc

$gallery = array (
    array (
    "imgsrc"    =>  "i/Alice0.JPG", 
    "title"     =>  "Front of House", 
    "desc"      =>  "View of the house from the front sidewalk"
    ),
    array ( 
    "imgsrc"    =>  "i/Alice1.JPG", 
    "title"     =>  "Front door", 
    "desc"      =>  "Close-up of front door"
    )
);
// This is just for debugging, to confirm that I really have a multidimensional array
//print_r($gallery); 

// BROKEN: this should use foreach loop(s) to gather the elements and pass them to the build_html function defined above.  
function display_gallery ($gallery) { 
//  global $gallery;
    foreach ($gallery as $img) { 
        $imgsrc = $img['imgsrc'];
        $title = $img['title'];
        $desc = $img['desc'];
        build_html($imgsrc, $title, $desc);
    //  var_dump($gallery);
    }
}
//var_dump($gallery);
// Calling the function in body of page
display_gallery($gallery);
?>
Doc
  • 156
  • 11
  • 1
    how is it broken? what doesn't work as expected? can you please update your question with that info? Thanks. – Gordon Jul 19 '17 at 05:35
  • I don't think $gallery is a flat array? I get this error message: Warning: Invalid argument supplied for foreach() in .... on line 121 – Doc Jul 19 '17 at 05:36

3 Answers3

2

Unlike in JavaScript, there is no scope bubbling in PHP. A function scope in PHP is isolated from the outside scope. Your $gallery is defined outside the function scope. Inside the function, it is undefined. That is why you get the Invalid argument supplied for foreach() message, because foreach expects an array, but it's not there. When you var_dump($gallery) in the function, you will see it is undefined.

The solution is to pass $gallery to display_gallery(), e.g.

function display_gallery(array $gallery) {
   // the code as is
}
display_gallery($gallery);

An alternative would be to utilize the global keyword:

function display_gallery() {
    global $gallery;       
    // the code as is
}
display_gallery();

However, while the latter looks like a convenient and simple fix, reaching from an inner scope to an outer scope is usually harder to maintain and more error prone in the long run. I am including it for the sake of completeness, but would advice to use the first approach, e.g. passing in the variable.

Gordon
  • 312,688
  • 75
  • 539
  • 559
  • Thanks for your response. I've tried updating the code as you suggested, but it produces broken image links with the title and desc fields set as their variable names, $title and $desc. I will keep experimenting... – Doc Jul 19 '17 at 16:19
  • Okay, I fixed it. The problem was my initial html block in the build_html function was improperly set up to actually use variables. I've verified it works with your solution of passing the $gallery variable to the function, and now I understand (better) how variable scopes work in PHP. Thanks for that lesson! I haven't been able to make it work with the global keyword, however. It just tells me "Invalid argument for foreach()..." – Doc Jul 19 '17 at 16:46
1

Notes:

  • I've separated the file name in images list, so it can also be used properly if it's maybe going to become an integer in the future.
  • I added additional infos, like "alt", "width", "height". "Alt" means alternative text, showed when image can not be displayed. But "title" is the attribute used for the tooltips on hover.
  • I changed the function names to intention-revealing names. And I wanted to show you that the function/method names in PHP should be defined in "camelCase" format (see PHP recommendation PSR-1: Basic Coding Standard).
  • I used the function sprintf() in displayImageCard(), to show you how to achieve a better readability and separation of PHP variables from the output string.
  • You should "inject" the $gallery array as argument to the displayImageGallery() function. The lack of this function argument was the source of the error you received.

Good luck!

<?php

/*
 * -----------------
 * Gallery functions
 * -----------------
 */

/**
 * Display image card.
 * 
 * @param string $file File name.
 * @param string $extension File extension (jpg|png, etc).
 * @param string $path File path.
 * @param string $alt Alternative text.
 * @param string $title Title used in tooltips.
 * @param string $description Description.
 * @param integer $width Width.
 * @param integer $height Height.
 * @return string Output string.
 */
function displayImageCard($file, $extension, $path, $alt, $title, $description, $width = 300, $height = 200) {
    echo sprintf('<div class="responsive">
                            <div class="gallery">
                                <img src="%s" alt="%s" title="%s" width="%s" height="%s">
                                <div class="desc">%s</div>
                            </div>
                        </div>'
            , $path . $file . '.' . $extension
            , $alt
            , $title
            , $width
            , $height
            , $description
    );
}

/**
 * Display image gallery.
 * 
 * @param array $gallery [optional] Images list.
 * @return void
 */
function displayImageGallery(array $gallery = array()) {
    echo '<pre>' . print_r($gallery, true) . '</pre>';

    foreach ($gallery as $key => $image) {
        $file = $image['file'];
        $extension = $image['extension'];
        $path = $image['path'];
        $alt = $image['alt'];
        $title = $image['title'];
        $description = $image['description'];

        displayImageCard($file, $extension, $path, $alt, $title, $description);
    }
}

/*
 * ---------------
 * Display gallery
 * ---------------
 */

// Images list.
$gallery = array(
    array(
        'file' => 'i/Alice0',
        'extension' => 'jpg',
        'path' => 'abc/',
        'alt' => 'Front of House',
        'title' => 'Front of House',
        'description' => 'View of the house from the front sidewalk',
    ),
    array(
        'file' => 'i/Alice1',
        'extension' => 'jpg',
        'path' => 'def/',
        'alt' => 'Front door',
        'title' => 'Front door',
        'description' => 'Close-up of front door',
    )
);

// Display image gallery.
displayImageGallery($gallery);
1

This will solve it:

function display_gallery ($gallery) {     
foreach ($gallery as $img) { 
    $imgsrc = $img['imgsrc'];
    $title = $img['title'];
    $desc = $img['desc'];
    build_html($imgsrc, $title, $desc);
}
}
// Calling the function in body of page
display_gallery($gallery);

As well you could put the part where you define your array in function:

function returnArray() {
    // $gallery = ...
    return ($gallery);
}

And then in foreach loop:

foreach (returnArray() as $img) { ... }
NoOorZ24
  • 2,914
  • 1
  • 14
  • 33
  • 2
    Also I'd suggest moving to OOP as it would make easier define accessibility of functions and variables – NoOorZ24 Jul 19 '17 at 06:22
  • Thanks for your response. I've started reading up on OOP and will continue studying. I definitely learned more in trying to hammer out a solution to this little project than I get from books/codecademy tutorials. Do you have any suggestions about why the second approach (returning the array in a function) would potentially be better than the first? – Doc Jul 19 '17 at 16:50
  • 1
    I'm not really sure if second one is better as I'm used to Laravel framework and everything there uses OOP. It's kinda like different world. – NoOorZ24 Jul 20 '17 at 06:00