Wallpaper Setup Command - Part 3 - Doing It With Style


At this stage we have a working Symfony console command which allows us to pull in any wallpaper images we have, and use those images to populate entities which we then save off to our database.

However, as covered in the previous video, the data that is used to populate our table is a little ... poor.

Before we go any further, if you have been following along I would ask that you either truncate your table now, or drop the database entirely, recreate and re-run the migration to get back to a known good state:

php bin/console doctrine:database:drop --force
php bin/console doctrine:database:create
php bin/console doctrine:migrations:migrate -n

Now, let's make this whole process a little smoother.

<?php

namespace AppBundle\Command;

use AppBundle\Entity\Wallpaper;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class WallpaperSetupCommand extends Command
{
    /**
     * @var string
     */
    private $rootDir;
    /**
     * @var EntityManager
     */
    private $entityManager;

    public function __construct(string $rootDir, EntityManager $entityManager)
    {
        parent::__construct();

        $this->rootDir = $rootDir;
        $this->entityManager = $entityManager;
    }

    protected function configure()
    {
        $this
            // the name of the command (the part after "bin/console")
            ->setFilename('app:setup-wallpapers')

            // the short description shown while running "php bin/console list"
            ->setDescription('Grabs all local wallpapers and creates an entity for each one')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $wallpapers = glob($this->rootDir . '/../web/images/*.*');
        $wallpaperCount = count($wallpapers);

        foreach ($wallpapers as $wallpaper) {

            [
                'basename' => $filename,
                'filename' => $slug,
            ] = pathinfo($wallpaper);

            [
                0 => $width,
                1 => $height
            ] = getimagesize($wallpaper);

            $wp = (new Wallpaper())
                ->setFilename($filename)
                ->setSlug($slug)
                ->setWidth($width)
                ->setHeight($height)
            ;

            $this->entityManager->persist($wp);
        }

        $this->entityManager->flush();
    }
}

Here we are using PHP 7.1's shorthand array destructuring to extract data from returned arrays, and push that data into variables in 'one line'.

Both pathinfo and getimagesize take the full path of the given image and return interesting information that we care about. Watch the video for further info on this.

If we run the command again now, we should see a much nicer looking set of database table entries.

Getting SymfonyStyleish

It would be super nice to spruce up our Symfony console command output. Fortunately Symfony offers a SymfonyStyle class, along with some helpers such as the progress bar, and a nice Table to help us do so.

We're going to add in a progress bar to show how many images have been processed, and then a table output at the end to show each image by name.

Symfony makes it super easy to add these helpers to our console command. The downside is, our commands do get a little bit 'busier' (or messier, depending on your point of view) as a result.

Adding in the progress bar is the easier of the two, so let's do just that:

use Symfony\Component\Console\Style\SymfonyStyle;

// * snip *

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);

        $wallpapers = glob($this->rootDir . '/../web/images/*.*');
        $wallpaperCount = count($wallpapers);

        $io->progressStart($wallpaperCount);

        foreach ($wallpapers as $wallpaper) {

            [
                'basename' => $filename,
                'filename' => $slug,
            ] = pathinfo($wallpaper);

            [
                0 => $width,
                1 => $height
            ] = getimagesize($wallpaper);

            $wp = (new Wallpaper())
                ->setFilename($filename)
                ->setSlug($slug)
                ->setWidth($width)
                ->setHeight($height)
            ;

            $this->entityManager->persist($wp);

            $io->progressAdvance();
        }

        $this->entityManager->flush();

        $io->progressFinish();
    }

Easy enough.

We need to create a new instance of the SymfonyStyle class. Calling this instance $io is just something I lifted directly from the docs. This class takes both the $input and $output variables as arguments, which come with any execute function, so we are all good there.

progressStart needs to know how many entries will be counted over before we are done. If you don't add this, expect some very funky output, especially on very large result sets.

In each iteration of the foreach loop we must call progressAdvance to indicate we are incrementing the progress bar by 1. You can pass in an integer here to increment by a different value if needed.

Finally, outside the loop we finish up.

All of this results in a nice looking output:

php bin/console app:setup
 22/22 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

Just be careful with the progress bar and any entries such as writeLn which interrupt the progress bar output and make things look messy.

One thing to note here is that because we have a unique constraint on our table, we cannot keep re-running the console command without first drop / create / migrate our database, or truncating the table.

Adding a Table of results to the output involves a little further effort:

use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Helper\Table;

// * snip *

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);

        $wallpapers = glob($this->rootDir . '/../web/images/*.*');
        $wallpaperCount = count($wallpapers);

        if ($wallpaperCount === 0) {
            $io->warning('No wallpapers found');

            return false;
        }

        $io->title('Importing Wallpapers');
        $io->progressStart($wallpaperCount);

        $fileNames = [];

        foreach ($wallpapers as $wallpaper) {

            [
                'basename' => $filename,
                'filename' => $slug,
            ] = pathinfo($wallpaper);

            [
                0 => $width,
                1 => $height
            ] = getimagesize($wallpaper);

            $wp = (new Wallpaper())
                ->setFilename($filename)
                ->setSlug($slug)
                ->setWidth($width)
                ->setHeight($height)
            ;

            $this->em->persist($wp);

            $fileNames[] = [$filename];

            $io->progressAdvance();
        }

        $io->progressFinish();

        $table = new Table($output);
        $table
            ->setHeaders(['Filename'])
            ->setRows($fileNames)
        ;
        $table->render();

        $io->success(sprintf('Added %d wallpapers, nice one.', $wallpaperCount));

        $this->em->flush();
    }

The gotcha here is to make sure you use an array of arrays for your $filenames or your output will be really messed up. Also, don't forget to call $table->render(), or all your hard work is for naught.

And with that, we should have our finished output:

php bin/console app:setup

Importing Wallpapers
====================

 22/22 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

+-------------------------------------+
| Filename                            |
+-------------------------------------+
| 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  |
+-------------------------------------+

 [OK] Added 22 wallpapers, nice one.

As mentioned, this is for learning purposes only. This isn't meant to be a proper solution. We really need to switch over to using fixtures to go much further. And that's exactly what we will do in the very next video.

Code For This Course

Get the code for this course.

Episodes

# 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:57
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:33
19 No Tests - Part 1 - Uploading Files in EasyAdminBundle 11:01
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:51
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:41
36 Test Driven Wallpaper Delete - Part 1 11:06
37 Test Driven Wallpaper Delete - Part 2 11:57
38 EasyAdminBundle Login Form Tutorial 08:01