PHP: fix Faker image returns false

Thomas Dutrion
Darkmira FR
Published in
3 min readFeb 26, 2020

--

Faker image is broken, the solution is to change the image provider… Sure, but you can’t edit the vendor, can you? Using this technique, your code is safe!

When generating fixtures on an open source project (Community Event Manager), I encountered an error while generating random images with Faker.

Turns out, Faker uses Lorempixel as a backend to generate image, probably to be able to fetch nice images and avoid having GD or similar extensions to generate an image. In my specific case, Lorempixel was down or very slow for a few days, which makes the code unusable.

According to https://laracasts.com/discuss/channels/laravel/using-faker-to-fake-images-always-returns-false, you just need to edit the image provider and change it to another provider as you please. Problem is, this provider is part of your vendor… So you probably do not want to do that!

Lucky for us, Faker just loads providers on a last in first out basis, meaning the last image provider (i.e. class that extends base provider and has a image method) will be used. Therefore, we just need to override the existing provider with our own. This one is highly inspired from the existing one, and throws exception whenever the option is not available on the generation site.

Last step is to register this new provider.

$faker = \Faker\Factory::create();
$faker->addProvider(new \App\Faker\Provider\PicsumImage($faker));

If you’re using Symfony (and Doctrine Fixtures, but mostly Symfony), you can actually register Faker as a service to make it easier.

Faker\Generator:
factory: ['Faker\Factory', 'create']
arguments: ['en_US']
calls:
- method: addProvider
arguments:
- '@App\Faker\Provider\PicsumImage'

And now, because of the autowiring, your fixtures may look like this:

<?php

declare
(strict_types=1);

namespace App\DataFixtures;
use App\Entity\Address;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Generator;

final class AddressFixtures extends Fixture
{
private $faker;

public function __construct(Generator $faker)
{
$this->faker = $faker;
}

public function load(ObjectManager $manager): void
{
$address = new Address();

$address
->setName($this->faker->word)
->setStreetAddress($this->faker->streetAddress)
->setPostalCode($this->faker->postcode)
->setCity($this->faker->city)
;

$manager->persist($address);

$manager->flush();
}
}

If like me you like when things are extensible, you can even register an interface for that provider (even though the initial Image class won’t extend it), and then create an ImageProviderAggregate or a FallbackImageProvider that uses both the initial and your own providers.

Here’s the interface extracted from the provided PicsumImage provider:

<?php

namespace
App\Faker\Provider;

use InvalidArgumentException;

interface Image
{
/**
* Download a remote random image to disk and return its location.
*
* Requires curl, or allow_url_fopen to be on in php.ini.
*
*
@param null $dir
*
@param int $width
*
@param int $height
*
@param null $category
*
@param bool $fullPath
*
@param bool $randomize
*
@param null $word
*
*
@return bool|RuntimeException|string
*
*
@example '/path/to/dir/13b73edae8443990be1aa8f1a483bc27.jpg'
*/
public static function image(
$dir = null,
$width = 640,
$height = 480,
$category = null,
$fullPath = true,
$randomize = true,
$word = null
);
}

Let’s then create our fallback provider:

<?php

declare
(strict_types=1);

namespace App\Faker\Provider;

use Faker\Provider\Image as LoremPixelImage;
use Throwable;

final class FallbackImageProvider implements Image
{
public static function image(
$dir = null,
$width = 640,
$height = 480,
$category = null,
$fullPath = true,
$randomize = true,
$word = null
) {
$providers = [LoremPixelImage::class, PicsumImage::class];
while ($provider = array_shift($providers)) {
try {
$result = $provider::image($dir, $width, $height, $category, $fullPath, $randomize, $word);
} catch (Throwable $exception) {
$result = false;
}
if (false !== $result) {
return $result;
}
}
return false;
}
}

Obviously, I’m not good with static at all (I never really use anything static), hence the hardcoded array of providers. If you know a way to initialize this array in a better way using configuration, that would be perfect then, as it would allow us to dynamically find providers from their interface, and disable the one from Faker to avoid some http queries timeout that really slow down the fixtures…

--

--

Thomas Dutrion
Darkmira FR

Freelance PHP Developer / Web architect, @scotlandphp organiser | Zend Certified Architect