setono/tiny-png-bundle
A Symfony bundle that integrates the TinyPNG API to automatically compress and optimize PNG and JPEG images, helping reduce file sizes and improve page load performance with simple configuration.
First, a framing correction that still holds after v1.1.0: setono/tiny-png-bundle is a Symfony bundle, not a Laravel package. It ships no ServiceProvider, no Facade, no config/tiny-png.php, and is not registered via Laravel's package auto-discovery. composer require setono/tiny-png-bundle in a Laravel app will install code you cannot boot through the framework. v1.1.0 changed nothing about that — the release is a dependency bump ("updated Symfony and PHP"), not a feature or integration release. So your three realistic first moves are unchanged:
src/DependencyInjection/Configuration.php and .../SetonoTinyPngExtension.php — the actual config surface (almost certainly a single api_key under setono_tiny_png).src/Service/TinyPng.php (or similarly named) — the thin wrapper around Tinify\Tinify::fromFile() / fromBuffer() / toFile(). This is the only logic you actually care about.Resources/config/services.xml — constructor wiring and which interface to bind.tinify/tinify directly and build a small App\Services\ImageOptimizer (or a TinyPngManager) mirroring the bundle's service. ~40 lines, full framework integration.symfony/messenger worker) and call it over HTTP/queue. Still not worth it for this scope.Worth checking before you decide: v1.1.0 bumped the Symfony and PHP constraints. That means the package may now install on a modern runtime where v1.0.4 would have failed composer update — but also that it may have dropped older Symfony/PHP support. Run composer why-not setono/tiny-png-bundle (or read composer.json directly) against your actual app before assuming either way.
Your first end-to-end use case: pick up a user avatar or product image after it is stored, compress it through Tinify, and write the result back to a public/s3 disk. Get a free API key from tiny.png (500 compressions/month) and put it in .env as TINIFY_API_KEY.
The Laravel translation of the bundle's pattern. The bundle's job is: read config, construct a TinyPng service with the API key, expose compress($source, $destination). In Laravel:
// config/services.php
'tinify' => ['key' => env('TINIFY_API_KEY')],
// app/Services/ImageOptimizer.php
final class ImageOptimizer
{
public function __construct(private readonly string $apiKey) {}
public function compress(string $contents): string
{
\Tinify\Tinify::setKey($this->apiKey);
return \Tinify\Tinify::fromBuffer($contents)->toBuffer();
}
}
Bind it as a singleton in AppServiceProvider so Tinify::setKey() is set once per process rather than per call — mirrors what the Symfony DI container does. Nothing in v1.1.0 changes this contract: no new methods, no config keys, no interfaces.
Queue the work, always. Compression is a network round-trip to Tinify's servers. Never inline in a request or inside a store() call. Pattern: controller stores the original → dispatch OptimizeImageJob on a media queue → job reads from disk, compresses, writes the optimized variant, records path, bytes_saved, compression_count on the model. This is the Laravel equivalent of the bundle being consumed by a Symfony message handler.
Storage-shaped interface. Many Setono bundles take a Flysystem FilesystemOperator. If this one does, that maps 1:1 onto Laravel disks — Storage::disk('s3')->get($path) / ->put($outPath, $buffer). Keep originals and optimized copies on separate paths (originals/ vs optimized/) so you can re-run compression after a key rotation without destroying source data.
Compress from a buffer, not a URL. Tinify::fromBuffer() / fromFile() avoids an extra public URL or signed-link round trip and keeps private images private. Only use fromUrl() if the asset is already publicly addressable.
Idempotency keys. Store a hash of the original (sha256) plus a compressed_at timestamp on the model. The job should early-return if already compressed or if the stored original hasn't changed — Tinify's free tier is 500/month and you will burn through it on re-saves.
artisan vendor:publish, no facades, no Laravel support commit in the changelog. Anything in the README about config/packages/setono_tiny_png.yaml or services.yaml is Symfony-only. Treat the docs as design notes, not instructions.composer.json. That is a maintenance release, not a feature release: no bug fixes to the Tinify wrapper, no new config, no security advisory. Two practical consequences: (a) if you're pulling this transitively through a Symfony app, re-run your test suite, because a raised PHP floor is a silent breaking change on older runtimes; (b) if you previously bounced off the version ceiling on PHP 8.2/8.3, try the constraint again — it may resolve now. In a Laravel app it still changes nothing, because you can't boot it regardless.tinify/tinify constraint yourself rather than trusting docs. If the constraint conflicts with your PHP runtime, use tinify/tinify standalone — that's not affected by this release at all.Tinify\Exception\ClientException (4xx), AccountException (key invalid / verification pending / quota exceeded), ServerException (5xx, retryable), and ConnectionException (network, retryable). The bundle will not classify these for you. Map them to job $tries/backoff — retry only 5xx and connection errors; fail fast on AccountException.Tinify\Tinify::getCompressionCount() returns this month's usage. Log it in the job and alert (or flip a feature flag off) as you approach 500. Free tier is per-key, per-calendar-month, and resets — a scheduled check on the 1st is cheap insurance.optimized/{hash}/{filename}) so an algorithm change or a bad key doesn't strand you.spatie/image-optimizer with local pngquant/jpegoptim/cwebp. Treat Tinify as the "public marketing assets" path and local optimization as the default.fromBuffer() holds the whole file in PHP memory; a 30 MB PNG can blow past memory_limit. Prefer fromFile() on a temp path and cap upload sizes before queuing.setKey() is global static state on the SDK. In Octane or long-running workers, if you ever rotate keys or use multiple accounts, set it explicitly at the top of every job — never assume it persists correctly across requests.TinyPng service is an interface-backed DI service, which is exactly the seam that lets you swap in a local optimizer later. In Laravel, define App\Contracts\ImageOptimizer and bind either TinyPngOptimizer or LocalOptimizer — one config flag, no call-site changes. This remains the single most valuable idea the bundle has to offer a Laravel codebase, and v1.1.0 doesn't add anything to it.How can I help you explore Laravel packages today?