mluex/gravatar-bundle
A Symfony bundle that integrates Gravatar avatars into your application. It provides Twig helpers and services to generate Gravatar URLs from email addresses, with support for default images, size, rating, and secure HTTPS URLs, making it easy to display user avatars consistently.
First, the framing: mluex/gravatar-bundle is a Symfony 5 bundle, not a Laravel package. There is no service provider, no facade, no publishable config, no auto-discovery. Two stars, one release (March 2020), zero dependents. Treat this as a reference implementation you read, not a dependency you install.
Minimal steps
composer require it into a Laravel app. It declares Symfony framework dependencies that will drag symfony/framework-bundle and friends into your vendor/ for what amounts to a 30-line URL builder.src/Gravatar.php — the entire public surface (getUrl() and helpers)src/Twig/GravatarExtension.php — how it's exposed to templatessrc/DependencyInjection/Configuration.php — the config keys (default, size, rating)// app/Support/Gravatar.php
namespace App\Support;
class Gravatar
{
public static function url(string $email, int $size = 80, string $default = 'mp', string $rating = 'g'): string
{
$hash = md5(strtolower(trim($email)));
return sprintf(
'https://www.gravatar.com/avatar/%s?s=%d&d=%s&r=%s',
$hash,
min($size, 2048),
urlencode($default),
$rating
);
}
}
First use case: replace a hardcoded placeholder avatar in a Blade view (<img src="{{ App\Support\Gravatar::url($user->email) }}">). That is the entire day-one value proposition, and it takes about ten minutes.
Bind it as a singleton, config-driven. Mirror what the bundle's DI extension does, using Laravel's config system:
// config/gravatar.php
return [
'size' => env('GRAVATAR_SIZE', 80),
'default' => env('GRAVATAR_DEFAULT', 'mp'), // mp|identicon|monsterid|wavatar|retro|robohash|404|<url>
'rating' => env('GRAVATAR_RATING', 'g'), // g|pg|r|x
];
// app/Providers/AppServiceProvider.php
$this->app->singleton(Gravatar::class, fn () => new Gravatar(
config('gravatar.size'),
config('gravatar.default'),
config('gravatar.rating'),
));
Per-call overrides beat global config. The bundle's global size setting is nearly useless in practice — you need 24px in a comment list, 40px in a navbar, 128px on a profile page. Expose size as the first argument and let the config supply the fallback.
Blade component for consistency. This is where the Symfony Twig gravatar filter maps onto Laravel:
{{-- resources/views/components/avatar.blade.php --}}
@props(['user', 'size' => 40])
<img
src="{{ app(App\Support\Gravatar::class)->url($user->email, $size) }}"
width="{{ $size }}" height="{{ $size }}"
alt="{{ $user->name }}"
loading="lazy"
class="rounded-full"
>
Persist the existence check, don't re-check per request. Use d=404 as a one-time probe when a user registers or changes email, and store the result:
$exists = Http::get(Gravatar::url($email, 80, '404'))->successful();
$user->update(['has_gravatar' => $exists]);
Then render from the local avatar/initials fallback when has_gravatar is false — no third-party round trip on page load.
Proxy through your own route. Gravatar is a third-party request on every page view: it leaks your users' IPs to Automattic and adds a DNS/TLS hop. Cache the bytes and serve them yourself:
Route::get('/avatar/{user}', fn (User $user) => Cache::remember(
"avatar:{$user->id}", now()->addDay(),
fn () => Http::get(Gravatar::url($user->email, 128))->body()
))->middleware('throttle:60,1');
Default-image strategy matters more than the URL code. mp (mystery person) is the safe corporate choice; identicon gives deterministic per-user variety; retro is the 8-bit set. A custom d= URL lets you point at your own branded placeholder — remember it must be URL-encoded.
The Symfony-bundle mismatch is the headline gotcha. No extra.laravel block means no discovery. If you install it anyway and try to resolve Mluex\GravatarBundle\Gravatar from the container, you'll get a BindingResolutionException — the service is registered by GravatarExtension::load(), which only runs inside a Symfony kernel. The Twig extension is likewise inert in Blade.
Last release 2020-03-21. It was a Symfony 5 fork of ornicar/gravatar-bundle, and both are effectively unmaintained. There will be no PHP 8.3/8.4 fixes, no Symfony 6/7 support, no security patches. Since it's ~200 lines of md5() and sprintf(), there is nothing to patch — which is exactly why copying beats depending.
Email normalization is the #1 bug source. Gravatar hashes md5(strtolower(trim($email))). If your registration form doesn't trim, or you hash the raw $request->email, you get a silently different hash and a mystery-person avatar that looks like "Gravatar is broken." Normalize once at the model level (a mutator) and never think about it again.
The s parameter caps at 2048. Larger values are silently clamped server-side, so a retina s=512 is fine but s=4096 is wasted bytes in your markup.
d=404 returns an actual 404. If you're proxying with Http::get(), ->body() on a 404 gives you Gravatar's error page, not an image. Always check ->successful() before caching, and make sure the cached entry has a short TTL so a user who just signed up for Gravatar isn't stuck with a placeholder.
HTTPS is the only scheme now. Gravatar dropped plain HTTP; older tutorials (and possibly this bundle's defaults) mention a secure flag. Don't add one — hardcode https://.
GDPR/privacy. An MD5 of an email address is pseudonymized PII, not anonymized, and Gravatar's terms make the hash effectively enumerable against known email lists. If you have EU users, the proxy pattern above isn't optional — it's the difference between "we send a hash" and "we send a hash and the user's IP to a US processor on every page view."
Debugging checklist when an avatar is wrong:
&d=404 — 404 means no Gravatar exists for that hash.md5(strtolower(trim($email))) in php artisan tinker and diff it against the hash in the URL. Nine times out of ten it's whitespace or casing.Extension points are essentially nonexistent. There are no events, no DI tags, no compiler passes worth using. The only seam is the Twig extension — and in Laravel the equivalent seam is the Blade component, which you own.
If you want a maintained Laravel-native option, creativeorange/gravatar is the conventional choice and gives you a facade and config publishing for roughly the same amount of code you'd write by hand. But honestly: the 30-line App\Support\Gravatar class above has no dependencies, no upgrade path, and no surprises — which is more than can be said for a two-star Symfony fork from 2020.
How can I help you explore Laravel packages today?