mmo/faker-images
mmo/faker-images adds fake image generation to Faker, letting Laravel and PHP developers create placeholder image URLs from popular services. Perfect for seeders, factories, and tests, it fills your demo data with realistic visuals without storing files. Simple to install and configure.
mmo/faker-images is a thin Faker provider package: it doesn't generate or store images, it builds URLs pointing at third-party placeholder services (LoremFlickr, Picsum, Placeholder-style endpoints). That single fact drives every decision below — treat it as a URL factory, not an image factory.
Install as a dev dependency (it should never be needed in production):
composer require mmo/faker-images --dev
It builds on fakerphp/faker (the package Laravel has shipped since v9), so no conflict with the framework's Faker singleton. Providers live under the Mmo\Faker\ namespace; each one registers one or more formatters on the generator — check the README for the exact class and method names for the provider you want, since they differ per service.
First use case — avatar/thumbnail URLs in model factories:
// database/factories/UserFactory.php
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'avatar_url' => fake()->imageUrl(200, 200), // overridden by the registered provider
];
}
Where to look first:
README.md for the provider list and the method signatures (imageUrl(), plus provider-specific parameters such as tags, grayscale, blur, or seed).src/ — the whole package is a handful of Base subclasses, each a few lines of string interpolation. Reading it takes five minutes and removes all guesswork.composer.lock to confirm which Faker version is resolved, because the provider relies on Faker\Provider\Base.Register the provider once, app-wide, for Laravel. Don't sprinkle addProvider() through seeders. Laravel binds Faker\Generator as a singleton, so extend it in a service provider:
// app/Providers/AppServiceProvider.php
use Faker\Generator as FakerGenerator;
public function register(): void
{
$this->app->extend(FakerGenerator::class, function (FakerGenerator $faker) {
$faker->addProvider(new \Mmo\Faker\LoremFlickrProvider($faker));
return $faker;
});
}
After this, fake(), $this->faker inside factories, and $this->faker inside seeders all use the same generator instance with the provider attached.
Override Faker's default imageUrl() deliberately. Faker's built-in imageUrl() points at a single placeholder host that has a history of moving and rate-limiting. Because Generator::addProvider() replaces formatters with the same name, registering a provider after the generator is created means the package's imageUrl() wins. That's usually what you want — but be explicit in a comment, because it surprises the next developer.
Download in seeders, serve locally in demos. The highest-value pattern for anything you show to stakeholders is to resolve the URL once at seed time and persist the bytes:
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
$url = fake()->imageUrl(800, 600, 'cats');
$path = 'avatars/' . fake()->uuid() . '.jpg';
Storage::disk('public')->put($path, Http::get($url)->body());
$user->update(['avatar_path' => $path]);
Now php artisan storage:link gives you an offline-safe demo, and your .env-driven APP_URL is the only host involved at render time.
Wrap it in one domain service. Because the package is a string builder, isolate it:
final class PlaceholderImages
{
public function avatar(): string { return fake()->imageUrl(200, 200); }
public function hero(int $w = 1600, int $h = 900): string { return fake()->imageUrl($w, $h, 'nature'); }
}
When the upstream domain changes (and it will), you edit one class instead of grepping 40 factories. It also gives you a seam to swap in locally generated images later.
Deterministic fixture data. $faker->seed(1234) makes the URL strings reproducible, which is useful for snapshot tests of API responses and for stable seeded databases. It does not make the remote image content reproducible — don't build visual regression tests on top of this.
Testing. Any test that touches a factory using these URLs should fence the network:
Http::preventStrayRequests();
Http::fake(['*' => Http::response('fake-bytes', 200)]);
For assertions, never compare full URLs — domains and query strings change. Use assertStringStartsWith('https://', $user->avatar_url) or a regex on the path.
via.placeholder.com alone has burned countless projects by changing ownership and behaviour. Pin expectations loosely, and never let a presentation depend on the service being up.Storage::fake() does nothing here. It fakes disks, not URL generation. If you assumed your media tests were hermetic because you faked the disk, they aren't — the URL still points outward if anything dereferences it.http:// URLs. On HTTPS pages the image silently fails. Check the returned scheme in a quick tinker session, and normalise (str_replace('http://', 'https://', $url)) if the provider doesn't expose a secure flag.image() needs ext-gd and writes real files to disk; imageUrl() (and this package) needs neither. That's a genuine advantage in slim CI containers — but it also means nothing validates that the URL resolves to an actual image.unique() interacts badly with small parameter spaces. fake()->unique()->imageUrl(100, 100) will exhaust after a few hundred calls if the provider's output is deterministic for fixed arguments. Vary a dimension or a tag, or drop unique().array_key_exists('imageUrl', fake()->getProviders() ? ... ) — or simply dd(fake()->imageUrl(10, 10)) after booting.app()->environment('local').Faker\Provider\Base and add an imageUrl()/<service>ImageUrl() formatter; it's the same mechanism the package itself uses, and your providers register identically via addProvider().How can I help you explore Laravel packages today?