wp-media/imagify-php
Official PHP client for the Imagify API. Optimize images outside WordPress by compressing, resizing, and converting them to WebP through Imagify's service. Install via Composer and add image optimization to any PHP application.
Install the library through Composer and pin the version explicitly, because the last tagged release is from September 2017 and there is no active maintenance:
composer require wp-media/imagify-php
Where to look first:
src/ for the client class. The publicly documented entry point is the client object (typically instantiated as something like new Imagify($apiKey) or new Client($apiKey) depending on the tagged version). Open that file and read the public methods before anything else — the API surface is small and the docblocks are the most reliable "documentation" you have.The minimal first use case — optimize one image and read back the result:
use WPMedia\Imagify\Imagify; // confirm namespace/class in src/
$client = new Imagify(config('services.imagify.key'));
$result = $client->optimize(new SplFileInfo(storage_path('app/public/hero.jpg')));
if ($result->isSuccess()) {
// persist the optimized file / its URL, then serve from storage
}
Start by proving out the two calls you will call most: optimize (returns a compressed payload and/or a reference to a stored image) and quota (getQuota()-style call). If those two round-trip cleanly against a real key, everything else in the package is detail.
Wrap it behind a service, never call it from controllers. The package is a thin HTTP wrapper; give yourself a single ImagifyService that owns the client, key, and error translation. This isolates the day you replace the abandoned dependency.
final class ImagifyService
{
public function __construct(private Imagify $client) {}
public function optimize(string $path): OptimizedImage
{
try {
$response = $this->client->optimize(new SplFileInfo($path));
} catch (ImagifyException $e) {
throw new ImageOptimizationFailed($path, $e);
}
// map library DTO -> your own DTO
}
}
Bind it as a singleton in a service provider so the underlying HTTP client and connection are reused:
$this->app->singleton(Imagify::class, fn () => new Imagify(config('services.imagify.key')));
Keep the key in config/services.php and .env, not in the constructor call site. The library has no config layer of its own, so Laravel's config cache is your only guard against accidentally hardcoding it.
Treat the API as asynchronous and eventual. Image optimization is not instant. The reliable workflow for bulk media is:
media_assets table.Use queued jobs for anything beyond a single ad-hoc file. A OptimizeImageJob with $tries = 3, an exponential backoff(), and a unique job lock per asset ID prevents duplicate submissions when uploads and edits race. The library itself does not deduplicate.
Store optimized results yourself. The value of the package is the compression call; the durable artifact is your own file on disk or object storage. Never let a page render depend on a live Imagify call — that is a latency and availability dependency on a third party, and it will also silently burn quota on cache misses.
Check quota before batch runs, not inside the loop. If the client exposes a quota/limits method, call it once at the top of a batch command and abort with a clear message when the remaining allowance is below the batch size.
Testing pattern: because the client is constructor-injected, swap it for a fake in ImagifyService tests and reserve real HTTP calls for a small, opt-in integration suite (@group external) run against a test account. Do not rely on the library having its own mock client — verify before assuming.
This package is effectively unmaintained. A 2017 release predates PSR-18, modern Guzzle 7, and PHP 8 attribute/type changes. Expect to hit:
composer require with an explicit Guzzle constraint, or a replace/dependency bump after a careful read of the transport code.php:// stream handling or SplFileInfo assumptions that break for remote/storage-backed files. Convert S3/object-storage assets to a local temp file before calling optimize().Vendor or fork it. With 12 stars and no releases since 2017, do not depend on upstream bugfixes. Fork into your VCS, tag it 0.0.0+vendored, and reference it via repositories in composer.json. This is the single highest-leverage decision for a package of this maturity.
Timeouts and retries are on you. The wrapper is unlikely to set sensible cURL timeouts for large image uploads. Configure the underlying handler with a generous connect timeout and an overall timeout sized to your maximum allowed file — a 10 MB JPEG over a slow link will otherwise fail mid-upload with an opaque cURL error. Distinguish retryable (timeout, 5xx, 429) from terminal (401 invalid key, 413 too large, 422 unsupported format) in one place.
Errors surface as exceptions or raw response objects, depending on version. Read the client's error path before writing try/catch: some methods throw on non-2xx, others return a response you must inspect. Normalize both into one exception type at your service boundary so callers never branch on library internals.
API key leakage is easy. The key is a long-lived account secret with no per-scope permissions. Never log the client object, and redact Authorization/api_key headers in your exception handler — stack traces from Guzzle requests will otherwise carry the header into logs.
File-format and size guards belong in your code. The API will reject unsupported formats (animated GIFs, some CMYK JPEGs, SVG) and oversized files, but you pay a round trip to learn that. Validate mime type and dimensions locally first, and skip optimization for files already below a threshold where compression gains nothing.
Watch what "success" means. A 200 from the API may mean "accepted for processing", not "optimized". Always confirm the returned payload references a real, fetchable optimized asset before you overwrite the original — keep the original until the optimized file is verified on disk.
Extension points are thin. There is no event system, no pluggable transport interface, and no config object. The practical extension surface is: subclass/replace the client, inject a configured Guzzle client if the constructor accepts one, and wrap the whole thing in your own service layer. Plan accordingly rather than hoping for hooks.
Have an exit plan. Given the score and age, design ImagifyService as an interface with the package behind one implementation. If Imagify changes its API or you outgrow the wrapper, you can drop in a direct Guzzle implementation or a maintained alternative without touching call sites.
How can I help you explore Laravel packages today?