### Install iminify-laravel via Composer Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Run this command to install the package. Requires PHP 8.2+. ```bash composer require mozex/iminify-laravel ``` -------------------------------- ### Use IminifyOptimizer::chain() with Spatie Image Source: https://github.com/mozex/iminify-laravel/blob/main/README.md IminifyOptimizer::chain() returns a Spatie OptimizerChain holding only this optimizer, which spatie/image and Browsershot use. This example loads an image, resizes it to width 1200, and optimizes it through Iminify. ```php use Mozex\Iminify\Laravel\IminifyOptimizer; use Spatie\Image\Image; Image::load($path)->width(1200)->optimize(IminifyOptimizer::chain())->save(); ``` -------------------------------- ### Configure Media Library image_optimizers with IminifyOptimizer Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Replace jpegoptim, pngquant, optipng, gifsicle, and cwebp with IminifyOptimizer in config/media-library.php, keeping svgo and avifenc for SVG and AVIF files. Keep at least one of Spatie's built-in optimizers in the list, otherwise the default binaries are used silently. An 'api_key' in options is ignored to avoid leaking into queued jobs. ```php 'image_optimizers' => [ Mozex\Iminify\Laravel\IminifyOptimizer::class => [], Spatie\ImageOptimizer\Optimizers\Svgo::class => [ '--disable=cleanupIDs', ], Spatie\ImageOptimizer\Optimizers\Avifenc::class => [ '-a cq-level=23', '-j all', '--min 0', '--max 63', '--minalpha 0', '--maxalpha 63', '-a end-usage=q', '-a tune=ssim', ], ], ``` -------------------------------- ### Publish iminify config with artisan command Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Run this command to publish config/iminify.php. ```bash php artisan iminify:install ``` -------------------------------- ### Enable responsive images with IMINIFY_RESPONSIVE_IMAGES Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Set IMINIFY_RESPONSIVE_IMAGES=true to run your image_optimizers list on each responsive width instead of the default binaries. When the list lacks Iminify, it replaces jpegoptim, pngquant, optipng, gifsicle, and cwebp there, so no width is compressed twice. ```dotenv IMINIFY_RESPONSIVE_IMAGES=true ``` -------------------------------- ### Configure IminifyOptimizer in image-optimizer.php Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Replace jpegoptim, pngquant, optipng, gifsicle and cwebp with IminifyOptimizer in the optimizers array. The optimizer deletes each image from your Iminify account when done and never asks for alt text. ```php 'optimizers' => [ Mozex\Iminify\Laravel\IminifyOptimizer::class => [], Spatie\ImageOptimizer\Optimizers\Svgo::class => [ '--disable=cleanupIDs', ], ], ``` -------------------------------- ### Upload, wait, and download an image using the Iminify facade Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Use the Iminify facade to upload an image, wait for processing, and download the result. The client is configured from your config and sends an 'Iminify-Client: laravel/' header. A single request may take IMINIFY_TIMEOUT seconds (default 30), and wait() has its own limit via its second argument. ```php use Mozex\Iminify\Laravel\Facades\Iminify; $image = Iminify::images()->upload(storage_path('app/photos/team.jpg'), ['format' => 'webp']); $image = Iminify::images()->wait($image); if ($image->hasResult()) { Iminify::images()->download($image)->saveTo(storage_path('app/photos/team.webp')); } ``` -------------------------------- ### Fake Iminify with queued fixtures and assert sent request Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Use Iminify::fake() to swap the client for one that answers queued fixtures in order and records calls for assertions. The optimizer uses the fake and waits never sleep. Fixtures::refusal() queues an exception to be thrown in its turn. Assert with Iminify::assertSent(). ```php use Mozex\Iminify\Laravel\Facades\Iminify; use Mozex\Iminify\Testing\Fixtures; Iminify::fake([ Fixtures::image(['id' => 'abc']), Fixtures::refusal('daily_limit_reached', 429), ]); $image = Iminify::images()->get('abc'); Iminify::assertSent('GET', 'images/abc'); ``` -------------------------------- ### Listen for OptimizationSkippedEvent and log the reason Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Use this listener to react when an image cannot be optimized. The event carries the file path and the exception reason. Logging is off by default in Spatie's chain, so this listener provides a way to capture the reason. ```php use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Log; use Mozex\Iminify\Laravel\Events\OptimizationSkippedEvent; Event::listen(function (OptimizationSkippedEvent $event) { Log::warning("Iminify skipped {$event->path}: {$event->reason->getMessage()}"); }); ``` -------------------------------- ### Use IminifyOptimizer::chain() for a specific media conversion Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Give a specific media conversion its own optimizer chain using IminifyOptimizer::chain(), while leaving the rest of the conversions on the default binaries. This is useful when you want Iminify for some conversions only. ```php use Mozex\Iminify\Laravel\IminifyOptimizer; public function registerMediaConversions(?Media $media = null): void { $this->addMediaConversion('thumb') ->width(400) ->optimize(IminifyOptimizer::chain()); } ``` -------------------------------- ### Set IMINIFY_API_KEY in .env Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Add your API key to the .env file. Create the key under Settings, API Tokens on www.iminify.com. ```dotenv IMINIFY_API_KEY=your-key ``` -------------------------------- ### Regenerate media conversions with media-library:regenerate Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Rebuild conversions for existing media or after config changes. Each regenerated conversion counts against your plan's daily images, so narrow the run with --only=thumb, a model name as the first argument, or --ids=. ```bash php artisan media-library:regenerate ``` -------------------------------- ### Upload, wait, and download an image via the Iminify facade Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Use the Iminify facade to upload an image, wait for processing, and download the result. The API key comes from IMINIFY_API_KEY. Each refusal throws its own exception from Mozex\Iminify\Exceptions\, all under ApiException with the API's $errorCode. IMINIFY_TIMEOUT (30 seconds) limits one request, not a whole wait. ```php use Mozex\Iminify\Laravel\Facades\Iminify; $image = Iminify::images()->upload(storage_path('app/photo.jpg'), ['format' => 'webp']); $image = Iminify::images()->wait($image); if ($image->hasResult()) { Iminify::images()->download($image)->saveTo(storage_path('app/photo.webp')); } ``` -------------------------------- ### Register Iminify webhook route in routes/web.php Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Add this route to handle incoming webhooks. It skips the CSRF check because webhooks cannot pass it. The route responds at /iminify/webhooks under the name iminify.webhooks; pass a different address and name as arguments if needed. ```php Route::iminifyWebhooks(); ``` -------------------------------- ### Configure image-optimizer.php with IminifyOptimizer and Svgo Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Use this configuration to route Laravel's ImageOptimizer through Iminify. The optimizers list includes IminifyOptimizer and Spatie's Svgo with the '--disable=cleanupIDs' option. Options in the config list override the 'iminify.optimizer' settings. ```php // config/image-optimizer.php 'optimizers' => [ Mozex\Iminify\Laravel\IminifyOptimizer::class => [], Spatie\ImageOptimizer\Optimizers\Svgo::class => ['--disable=cleanupIDs'], ], ``` -------------------------------- ### Run a single Pest test file or filtered test Source: https://github.com/mozex/iminify-laravel/blob/main/CLAUDE.md Commands to run one Pest test file or filter tests by name using `--filter`. ```bash vendor/bin/pest tests/SomeTest.php # one file vendor/bin/pest --filter="test name here" # one test ``` -------------------------------- ### Composer test and lint commands Source: https://github.com/mozex/iminify-laravel/blob/main/CLAUDE.md Development commands for testing, linting, static analysis, and type coverage. `composer test` is the gate before every commit; `composer lint` fixes code style with Pint; `composer test:lint` checks style without fixing; `composer test:types` runs PHPStan; `composer test:type-coverage` requires minimum 100% Pest type coverage; `composer test:unit` runs Pest. ```bash composer test # lint + static analysis + type coverage + tests, the gate before every commit composer lint # fix code style (Pint) composer test:lint # check code style without fixing composer test:types # PHPStan composer test:type-coverage # Pest type coverage (minimum 100%) composer test:unit # Pest ``` -------------------------------- ### Set Iminify webhook signing secret in .env Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Store the signing secret shown on www.iminify.com under Settings, Webhooks. The route checks the signature against the raw body and returns a 400 for any mismatch. ```dotenv IMINIFY_WEBHOOK_SECRET=whsec_... ``` -------------------------------- ### Listen for WebhookReceivedEvent and handle ImageCompleted Source: https://github.com/mozex/iminify-laravel/blob/main/README.md Use this listener to process webhook events. The event type is checked against WebhookEventType::ImageCompleted to access the image. The event ID stays the same on retries, so you can skip already-handled events. Implement ShouldQueue if your listener does slow work because Iminify counts a send as failed when the answer exceeds the timeout. ```php use Illuminate\Support\Facades\Event; use Mozex\Iminify\Enums\WebhookEventType; use Mozex\Iminify\Laravel\Events\WebhookReceivedEvent; Event::listen(function (WebhookReceivedEvent $received) { if ($received->event->type === WebhookEventType::ImageCompleted) { $image = $received->event->image(); } }); ``` -------------------------------- ### Iminify::fake() Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Fakes the Iminify client for testing. Accepts an array of fixtures that are answered in order through the real resources. ```APIDOC ## Iminify::fake() ### Description Fakes the Iminify client for testing. Accepts an array of fixtures that are answered in order through the real resources. ### Parameters - **fixtures** (array) - Required - An array of fixture objects, such as `Fixtures::image()` or `Fixtures::refusal()`. ### Usage Example ```php use Mozex\Iminify\Laravel\Facades\Iminify; use Mozex\Iminify\Testing\Fixtures; Iminify::fake([Fixtures::image(['id' => 'abc']), Fixtures::refusal('daily_limit_reached', 429)]); ``` ### Notes - The fake answers in order through the real resources, so refusals throw the real exceptions. - A call with no answer queued throws a `LogicException` naming it. ``` -------------------------------- ### WebhookVerifier::sign() Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Signs a webhook body for testing purposes. Returns the signature to be sent in the Iminify-Signature header. ```APIDOC ## WebhookVerifier::sign() ### Description Signs a webhook body for testing purposes. Returns the signature to be sent in the Iminify-Signature header. ### Parameters - **body** (string) - Required - The raw webhook body to sign. ### Returns - **string** - The signature value to be used in the `Iminify-Signature` header. ### Usage Example ```php $secret = 'whsec_...'; $body = '...'; $signature = (new Mozex\Iminify\Webhooks\WebhookVerifier($secret))->sign($body); // Send in the Iminify-Signature header ``` ``` -------------------------------- ### Listen to WebhookReceivedEvent Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Listens for the WebhookReceivedEvent. The event's type is checked against WebhookEventType::ImageCompleted, and the image is retrieved from the event. Slow listeners should implement ShouldQueue because Iminify counts slow answers as failed sends. ```php Event::listen(function (WebhookReceivedEvent $received) { if ($received->event->type === WebhookEventType::ImageCompleted) { $image = $received->event->image(); } }); ``` -------------------------------- ### Route::iminifyWebhooks() Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Registers the webhook route for Iminify. It sets up a POST endpoint at /iminify/webhooks, named iminify.webhooks, and disables CSRF checks for it. ```APIDOC ## [POST] /iminify/webhooks ### Description Registers the webhook route for Iminify. It sets up a POST endpoint at /iminify/webhooks, named iminify.webhooks, and disables CSRF checks for it. ### Method POST ### Endpoint /iminify/webhooks ### Parameters #### Request Body - **raw body** (string) - Required - The raw request body used for signature verification. ### Response #### Success Response (200) - **200** - The webhook is accepted and processed. #### Error Response (400) - **400** - Returned when the signature verification fails. ### Notes - The route is named `iminify.webhooks`. - CSRF checks are disabled for this route. - Set `IMINIFY_WEBHOOK_SECRET` to the `whsec_` secret from Settings, Webhooks. - The route checks the signature against the raw body and dispatches `Mozex\Iminify\Laravel\Events\WebhookReceivedEvent`. ``` -------------------------------- ### Fake Iminify and assert sent requests Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Uses Iminify::fake() with fixtures to simulate responses. The fake answers in order through real resources, so refusals throw real exceptions. Assertions check sent requests. A call with no queued answer throws a LogicException. Rate-limit waits use Laravel's Sleep, so Sleep::fake() skips them. ```php use Mozex\Iminify\Laravel\Facades\Iminify; use Mozex\Iminify\Testing\Fixtures; Iminify::fake([Fixtures::image(['id' => 'abc']), Fixtures::refusal('daily_limit_reached', 429)]); // ... code under test Iminify::assertSent('GET', 'images/abc'); Iminify::assertNotSent('DELETE', 'images/*'); ``` -------------------------------- ### Override optimizer level in config list Source: https://github.com/mozex/iminify-laravel/blob/main/README.md An option in a config list overrides the optimizer settings for that list. Here, the level is set to 'ultra' for the IminifyOptimizer entry. ```php Mozex\Iminify\Laravel\IminifyOptimizer::class => ['level' => 'ultra'], ``` -------------------------------- ### Register Iminify webhook route Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Adds the Iminify webhook route to routes/web.php. The route is POST /iminify/webhooks, named iminify.webhooks, and skips the CSRF check. ```php // routes/web.php Route::iminifyWebhooks(); // POST /iminify/webhooks, named iminify.webhooks, without the CSRF check ``` -------------------------------- ### Iminify::assertSent() Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Asserts that a request was sent to the Iminify API with the given method and path. ```APIDOC ## Iminify::assertSent() ### Description Asserts that a request was sent to the Iminify API with the given method and path. ### Parameters - **method** (string) - Required - The HTTP method of the request (e.g., 'GET', 'DELETE'). - **path** (string) - Required - The path of the request, which may include wildcards (e.g., 'images/abc', 'images/*'). ### Usage Example ```php Iminify::assertSent('GET', 'images/abc'); ``` ``` -------------------------------- ### WebhookReceivedEvent Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Event dispatched when a webhook is received. Provides access to the event data, including the event type and associated image. ```APIDOC ## WebhookReceivedEvent ### Description Event dispatched when a webhook is received. Provides access to the event data, including the event type and associated image. ### Properties - **event->id** (string) - The event ID. Repeats when a failed send is sent again. - **event->type** (WebhookEventType) - The type of the webhook event. ### Methods - **event->image()** - Returns the image associated with the event. Only available when the event type is `ImageCompleted`. ### Usage Example ```php Event::listen(function (WebhookReceivedEvent $received) { if ($received->event->type === WebhookEventType::ImageCompleted) { $image = $received->event->image(); } }); ``` ### Notes - Slow listeners should implement `ShouldQueue` because Iminify counts a slow answer as a failed send. ``` -------------------------------- ### Iminify::assertNotSent() Source: https://github.com/mozex/iminify-laravel/blob/main/resources/boost/skills/iminify-laravel/SKILL.md Asserts that a request was not sent to the Iminify API with the given method and path. ```APIDOC ## Iminify::assertNotSent() ### Description Asserts that a request was not sent to the Iminify API with the given method and path. ### Parameters - **method** (string) - Required - The HTTP method of the request (e.g., 'GET', 'DELETE'). - **path** (string) - Required - The path of the request, which may include wildcards (e.g., 'images/abc', 'images/*'). ### Usage Example ```php Iminify::assertNotSent('DELETE', 'images/*'); ``` ```