We had an easy one in the previous video where we created a simple Detail view. In this video we will push the limits of our hardcoded "image paths as strings" approach, rounding off the basic site design (from a code point of view) in the process.

To make this part more interesting I strong suggest you download a bunch of images from a selection of categories. Three categories would work best. However, don't do too many at this point as the emphasis here is on code, not on the pictures / wallpapers.

With all that said, if you don't want to muck about finding your own images then feel free to [clone my GitHub repo][1] where I have a bunch of images ready for you to use.

Home Page Design

The idea for our home page will be to have a very visual overview of what's on offer inside the site.

For each category we will show the top two images, and have a link to "more...". We've already coded up both the detail view, and the gallery view, and although they are both hardcoded, they will be good enough to demonstrate the real functionality we will ultimately offer.

We will have three categories to begin with, so we will display six images in three sections. For a better understanding of this, please watch the video.

As ever, we don't want to repeat ourselves too heavily here, so we will use some Twig 'tricks' to reduce repetition.

Below these three category views we will have a 'random' selection of images. Actually, as we are still working with arrays at this point, we can do this quite easily (shuffle is our friend). However, when we move to Doctrine we will see this isn't quite as easy. There's no ORDER BY RAND() in Doctrine.

We won't need to worry about pagination on the home / front page. Although, if you wish to do so, you could add in pagination here as an extra challenge.

Random Images

We're going to start by displaying 8 random images.

As mentioned, this is harder to do with Doctrine than it is when using an array. However, for the moment we are using an array so let's ignore this fact and do it the easy way.

I'm going to assume you have uploaded a bunch of images into your web/images directory, and again, if not, [please use mine][1].

To start with, we need to create our arrays of categories.

You could type each of the image names out if you are that way inclined, but I'm pretty lazy so I'm going to do it using ls and then doctor as appropriate:

cd web/images
ls -b1Qm web/images

abstract-background-pink.jpg, abstract-black-and-white-wave.jpg, abstract-black-multi-color-wave.jpg,
abstract-blue-green.jpg, abstract-blue-line-background.jpg, abstract-red-background-pattern.jpg,
abstract-shards.jpeg, abstract-swirls.jpeg, landscape-summer-beach.jpg, landscape-summer-field.jpg,
landscape-summer-flowers.jpg, landscape-summer-hill.jpg, landscape-summer-mountain.png, landscape-summer-sea.jpg,
landscape-summer-sky.jpg, landscape-winter-canada-lake.jpg, landscape-winter-high-tatras.jpg,
landscape-winter-snow-lake.jpg, landscape-winter-snow-mountain.jpeg, landscape-winter-snow-trees.jpg,
landscape-winter-snowboard-jump.jpg, landscape-winter-snowy-fisheye.png, uploadable-sunny-trees.jpg

Ok, so not the nicest looking thing you will ever see. But with a tiny amount of effort inside PhpStorm we can munge this data set into some sane shape. Again, watch the video for a better demo of how to do this if unsure.

Here's what I ended up with:

<?php

// /src/AppBundle/Controller/HomeController.php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class HomeController extends Controller
{
    /**
     * @Route("/", name="home")
     */
    public function indexAction(Request $request)
    {
        $abstract = [
            "abstract-background-pink.jpg",
            "abstract-black-and-white-wave.jpg",
            "abstract-black-multi-color-wave.jpg",
            "abstract-blue-green.jpg",
            "abstract-blue-line-background.jpg",
            "abstract-red-background-pattern.jpg",
            "abstract-shards.jpeg",
            "abstract-swirls.jpeg",
        ];

        $summer = [
            "landscape-summer-beach.jpg",
            "landscape-summer-field.jpg",
            "landscape-summer-flowers.jpg",
            "landscape-summer-hill.jpg",
            "landscape-summer-mountain.png",
            "landscape-summer-sea.jpg",
            "landscape-summer-sky.jpg",
        ];

        $winter = [
            "landscape-winter-canada-lake.jpg",
            "landscape-winter-high-tatras.jpg",
            "landscape-winter-snow-lake.jpg",
            "landscape-winter-snow-mountain.jpeg",
            "landscape-winter-snow-trees.jpg",
            "landscape-winter-snowboard-jump.jpg",
            "landscape-winter-snowy-fisheye.png"
        ];
    }
}

We could get creative here and extract each of these arrays into their own private methods. To be honest, it's not worth the effort at this point. As I mentioned at the start of this write up, we are stretching the limits of our hardcoded approach and we're going to replace this setup with a database-backed approach starting in the very next video.

Anyway, we have three arrays.

We want one array from which we can get 8 images at random.

Let's merge these arrays into one array first:

    /**
     * @Route("/", name="home")
     */
    public function indexAction(Request $request)
    {
        $abstract = [...];

        $summer = [...];

        $winter = [...];

        $images = array_merge($summer, $winter, $abstract);
    }
}

Ok, now we have a single array, we can randomise it by shuffle'ing it, and then we just need to take the first 8 entries:

    /**
     * @Route("/", name="home")
     */
    public function indexAction(Request $request)
    {
        $abstract = [...];

        $summer = [...];

        $winter = [...];

        $images = array_merge($summer, $winter, $abstract);

        shuffle($images);

        $randomisedImages = array_slice($images, 0, 8);
    }
}

shuffle is a built in PHP function the randomises the order of the elements in our array. It mutates the original $images array, which is pretty nasty in my opinion. A quick Google followed by checking out a bunch of StackOverflow articles will likely lead to an implementation less likely to introduce bugs into our code. But for the sake of it, this does the job we need with the minimum of fuss.

We only want the first 8 images from the resulting shuffle'ed output, so we slice off eight entries starting from the first element - zero indexed.

We're not likely to win any awards for this code. But it works. Let's carry on.

    /**
     * @Route("/", name="home")
     */
    public function indexAction(Request $request)
    {
        $abstract = [...];

        $summer = [...];

        $winter = [...];

        $images = array_merge($summer, $winter, $abstract);

        shuffle($images);

        $randomisedImages = array_slice($images, 0, 8);

        return $this->render('home/index.html.twig', [
            'randomised_images' => $randomisedImages,
        ]);
    }
}

Much like we've done already many times before, we take this array of output and pass it into our Twig template to be displayed to the end user.

Again, this template does not yet exist. Let's create it:

<!-- /app/Resources/views/home/index.html.twig -->

{% extends 'base.html.twig' %}

{% block body %}

    <div class="row">

        <h2>Random Wallpapers</h2>

        {% for image in randomised_images %}
            <div class="col-sm-6 col-md-3">
                <div class="thumbnail">
                    <img src="images/{{ image }}" alt="some text here">
                </div>
            </div>
        {% endfor %}
    </div>

{% endblock %}

There's nothing new here. If you are unsure on any of this, please watch the previous videos where we have covered all of this already.

If you browse to the home page now you should see 8 random images displayed. Again, these are full size images that have been smushed down to thumbnail size. This is fairly awful, but again, good enough for the moment.

Refresh the page and, assuming you have more than 8 images available, you should see a different set of images. Or the same 8 images in different places. Or, if you get extremely unlucky (or lucky, depending on your point of view), exactly the same images in exactly the same place.

Category Views

We've got our random images displaying. Now we want to show the top two images from each category.

As our site grows we will collect some stats on views, and maybe on downloads, but for now, we don't have these figures so the "top 2 images" is really the first two images in the array. Feel free to randomise and slice to make it a little less... ahem, repetitive.

The challenge here is not really one of code. We have already seen the way we will do this. The challenge here is not repeating our Twig template logic a bunch of times for each category.

Let's see the code first:

    /**
     * @Route("/", name="home")
     */
    public function indexAction(Request $request)
    {
        $abstract = [...];

        $summer = [...];

        $winter = [...];

        $images = array_merge($summer, $winter, $abstract);

        shuffle($images);

        $randomisedImages = array_slice($images, 0, 8);

        return $this->render('home/index.html.twig', [
            'randomised_images' => $randomisedImages,
            'summer'            => array_slice($summer, 0, 2),
            'winter'            => array_slice($winter, 0, 2),
            'abstract'          => array_slice($abstract, 0, 2),
        ]);
    }
}

Now, one thing to note: We are randomising the $images array that we created as a result of merging three existing arrays ($summer, $winter, and $abstract). Calling shuffle will not have had any impact on these three original arrays.

For each category all we have done is take the first two entries in the respective arrays. These images will always be the same, unless you change the order manually inside your arrays.

Let's take a stab at displaying one of these categories in our index.html.twig template:

<!-- /app/Resources/views/home/index.html.twig -->

{% extends 'base.html.twig' %}

{% block body %}

    <div class="row">

        <div class="row">
            <h2>Top Wallpapers For Summer</h2>
            <div class="row">
                {% for image in summer %}
                    <div class="col-sm-12 col-md-6">
                        <div class="thumbnail">
                            <img src="images/{{ image }}" alt="some text here">
                        </div>
                    </div>
                {% endfor %}
            </div>
        </div>

        <h2>Random Wallpapers</h2>

        {% for image in randomised_images %}
            <div class="col-sm-6 col-md-3">
                <div class="thumbnail">
                    <img src="images/{{ image }}" alt="some text here">
                </div>
            </div>
        {% endfor %}
    </div>

{% endblock %}

Ok, seems alright, I guess.

What happens if we add in the other two categories?

<!-- /app/Resources/views/home/index.html.twig -->

{% extends 'base.html.twig' %}

{% block body %}

    <div class="row">

        <div class="row">
            <h2>Top Wallpapers For Summer</h2>
            <div class="row">
                {% for image in summer %}
                    <div class="col-sm-12 col-md-6">
                        <div class="thumbnail">
                            <img src="images/{{ image }}" alt="some text here">
                        </div>
                    </div>
                {% endfor %}
            </div>
        </div>

        <div class="row">
            <h2>Top Wallpapers For Winter</h2>
            <div class="row">
                {% for image in winter %}
                    <div class="col-sm-12 col-md-6">
                        <div class="thumbnail">
                            <img src="images/{{ image }}" alt="some text here">
                        </div>
                    </div>
                {% endfor %}
            </div>
        </div>

        <div class="row">
            <h2>Top Wallpapers For Abstract</h2>
            <div class="row">
                {% for image in abstract %}
                    <div class="col-sm-12 col-md-6">
                        <div class="thumbnail">
                            <img src="images/{{ image }}" alt="some text here">
                        </div>
                    </div>
                {% endfor %}
            </div>
        </div>

        <h2>Random Wallpapers</h2>

        {% for image in randomised_images %}
            <div class="col-sm-6 col-md-3">
                <div class="thumbnail">
                    <img src="images/{{ image }}" alt="some text here">
                </div>
            </div>
        {% endfor %}
    </div>

{% endblock %}

Oh.

Oh my.

Oh my, no.

Each of these sections is a 95% repeat of the previous section. The only thing that differs is the h2 tag contents and the array of images we pass in to the for loop.

We can do better.

Let's extract out the section that gets repeated and just pass in the array we want repeating:

<!-- /app/Resources/views/home/best-in-category.html.twig -->

<div class="row">
    {% for image in images %}
        <div class="col-sm-12 col-md-6">
            <div class="thumbnail">
                <img src="images/{{ image }}" alt="some text here">
            </div>
        </div>
    {% endfor %}
</div>

All this snippet needs is an array of images. What those images are is not really of any concern.

We will need to doctor this when we switch to using image objects, but for now, its going to be good enough.

Note here that we do not use extends or block. It's just a snippet of re-usable markup.

So, how do we use this then?

Easy, we include it!

<!-- /app/Resources/views/home/index.html.twig -->

{% extends 'base.html.twig' %}

{% block body %}

    <div class="row">
        <h2>Top Wallpapers For Summer</h2>
        {% include "::home/best-in-category.html.twig" with { 'images': summer } %}
    </div>

    <div class="row">
        <h2>Top Wallpapers For Winter</h2>
        {% include "::home/best-in-category.html.twig" with { 'images': winter } %}
    </div>

    <div class="row">
        <h2>Top Wallpapers For Abstract</h2>
        {% include "::home/best-in-category.html.twig" with { 'images': abstract } %}
    </div>

    <div class="row">

        <h2>Random Wallpapers</h2>

        {% for image in pagination %}
            <div class="col-sm-6 col-md-3">
                <div class="thumbnail">
                    <img src="images/{{ image }}" alt="some text here">
                </div>
            </div>
        {% endfor %}
    </div>

{% endblock %}

It's not perfect, but it's a lot better.

We're going to consider this "good enough" for our home page.

We're at the limit of what we need to do to prove that this layout is viable. What we need to do now is start using the database. To do that, we will need a way of populating the database with all these images we've been using so far. And that's exactly what we will do in the very next video.

[1] https://github.com/codereviewvideos/symfony3-wallpaper-website-tutorial/


Code For This Course

Get the code for this course.

Code For This Episode

Get the code for this episode.

Share This Episode

If you have found this video helpful, please consider sharing. I really appreciate it.


Episodes in this series

# Title Duration
1 Introduction and Site Demo 02:14
2 Setup and a Basic Wallpaper Gallery 08:43
3 Pagination 08:24
4 Adding a Detail View 04:47
5 Creating a Home Page 11:14
6 Creating our Wallpaper Entity 07:50
7 Wallpaper Setup Command - Part 1 - Symfony Commands As a Service 05:56
8 Wallpaper Setup Command - Part 2 - Injection Is Easy 08:53
9 Wallpaper Setup Command - Part 3 - Doing It With Style 05:37
10 Doctrine Fixtures - Part 1 - Setup and Category Entity Creation 08:52
11 Doctrine Fixtures - Part 2 - Relating Wallpapers with Categories 05:56
12 EasyAdminBundle - Setup and Category Configuration 06:02
13 EasyAdminBundle - Wallpaper Setup and List View 07:46
14 EasyAdminBundle - Starting with Wallpaper Uploads 05:57
15 Testing with PhpSpec to Guide Our Implementation 03:39
16 Using PhpSpec to Test our FileMover 05:34
17 Symfony Dependency Testing with PhpSpec 08:47
18 Defensive Counter Measures 06:32
19 No Tests - Part 1 - Uploading Files in EasyAdminBundle 11:02
20 No Tests - Part 2 - Uploading Files in EasyAdminBundle 07:05
21 Don't Mock What You Don't Own 09:36
22 You've Got To Take The Power Back 07:36
23 Making Symfony Work For Us 08:56
24 Testing The Wallpaper File Path Helper 15:11
25 Finally, It Works! 14:56
26 Why I Prefer Not To Use Twig 16:50
27 Fixing The Fixtures 11:20
28 Untested Updates 14:30
29 Untested Updates Part Two - Now We Can Actually Update 06:33
30 Adding an Image Preview On Edit 12:31
31 Delete Should Remove The Wallpaper Image File 11:02
32 Getting Started Testing Wallpaper Updates 10:02
33 Doing The Little Before The Big 08:13
34 Tested Image Preview... Sort Of :) 07:36
35 Finishing Up With a Tested Wallpaper Update 10:44
36 Test Driven Wallpaper Delete - Part 1 11:11
37 Test Driven Wallpaper Delete - Part 2 11:57
38 EasyAdminBundle Login Form Tutorial 08:01