Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Respimg Laravel Package

nwtn/php-respimg

nwtn/php-respimg is a PHP library for responsive image handling: generate resized image variants, build srcset and sizes markup, and serve appropriately sized images across devices with simple configuration for PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Check your environment first. This package is an orchestration layer over native binaries; it does not resize or compress in pure PHP. Before composer require, confirm on the box that will run it:

convert -version           # ImageMagick
identify -list format | grep -i webp
jpegoptim --version
pngquant --version
node --version && npm --version   # respimg is a Node CLI wrapper

Missing binaries are the #1 reason a first run "succeeds" but silently skips WebP/AVIF output.

Install and publish config:

composer require nwtn/php-respimg
php artisan vendor:publish --tag=respimg

Read these three files, in this order:

  1. config/respimg.php (or whatever the publish tag drops) — this is where breakpoints, quality, output formats, and cache path live. Everything you tune day-to-day is here.
  2. The package's service provider — to confirm whether it auto-registers, what it binds into the container, and whether it needs manual registration on modern Laravel.
  3. The main workflow entry point (likely something like Respimg::process($path) returning markup). That is the only public API surface you should rely on.

First use case — the "upload one image, get a srcset" loop:

use Nwtn\Respimg\Facades\Respimg;

$markup = Respimg::process(
    storage_path('app/public/uploads/hero.jpg')
);

// $markup is an <img srcset="..." sizes="..." src="...">

Drop it into a Blade view with {!! $markup !!} (it is HTML, not escaped). If your first run returns markup but the derivative files aren't on disk where you expect, jump straight to Gotchas.

Do not stop here. Before you ship a single <img> using Respimg::process() directly from a controller or Blade, read the Implementation Patterns section — calling the package inline is a trap you cannot easily unwind.

Implementation Patterns

Wrap it. Always wrap it. The package's public API is the wrong dependency boundary for your app. Define a contract once and implement it with this package:

namespace App\Contracts;

interface ImageTransformer
{
    /**
     * @return array{src: string, srcset: string, sizes: string, width: int, height: int}
     */
    public function variants(string $sourcePath, array $widths, array $formats = ['jpg','webp']): array;
}
namespace App\Services\Images;

use App\Contracts\ImageTransformer;
use Nwtn\Respimg\Facades\Respimg;

class RespimgTransformer implements ImageTransformer
{
    public function variants(string $sourcePath, array $widths, array $formats = ['jpg','webp']): array
    {
        $result = Respimg::process($sourcePath, [
            'widths'  => $widths,
            'formats' => $formats,
        ]);

        return [
            'src'    => $result->src,
            'srcset' => $result->srcset,
            'sizes'  => $result->sizes,
            'width'  => $result->width,
            'height' => $result->height,
        ];
    }
}

Bind it in AppServiceProvider, and call ImageTransformer everywhere else. This is the entire risk-mitigation strategy for an unmaintained package — if you later swap to spatie/laravel-medialibrary or imgproxy, it is a one-file change.

Never call it in the request path. Image transforms are CPU-bound, spawn a subprocess, and write to disk. Every one of those is a latency landmine. Do this instead:

final class GenerateImageVariants implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $timeout = 300;
    public int $tries   = 3;

    public function __construct(
        public MediaAsset $asset,
        public array $widths = [320, 640, 960, 1280, 1600, 1920],
    ) {}

    public function handle(ImageTransformer $transformer): void
    {
        $result = $transformer->variants(
            $this->asset->sourcePath(),
            $this->widths,
            ['jpg', 'webp'],
        );

        $this->asset->update(['variants' => $result, 'variants_generated_at' => now()]);
    }

    public function failed(\Throwable $e): void
    {
        Log::error('Image transform failed', ['asset' => $this->asset->id, 'error' => $e->getMessage()]);
    }
}

Dispatch from an observer on your upload model, and put the job on a dedicated worker queue/image connection so heavy transforms don't starve normal jobs.

Render through a Blade component, not raw markup. This gives you a single place to add loading="lazy", decoding="async", aspect-ratio, and <picture>/WebP <source> fallback logic — and a single place to add placeholder markup for images whose variants haven't generated yet:

{{-- resources/views/components/responsive-image.blade.php --}}
@props(['asset', 'alt' => '', 'sizes' => '100vw'])

@if ($asset->variants_generated_at)
    <img
        src="{{ $asset->variants['src'] }}"
        srcset="{{ $asset->variants['srcset'] }}"
        sizes="{{ $sizes }}"
        width="{{ $asset->variants['width'] }}"
        height="{{ $asset->variants['height'] }}"
        alt="{{ $alt }}"
        loading="lazy"
        decoding="async">
@else
    <img src="{{ $asset->placeholderUrl() }}" alt="{{ $alt }}" width="800" height="600">
@endif

Pre-generate at upload time, and backfill in batches. Two workflows:

  1. Forward path: upload → observer dispatches GenerateImageVariants → job writes derivatives + rows. User sees a placeholder until done.
  2. Backfill path: an artisan command loops MediaAsset::whereNull('variants_generated_at')->chunkById(100, ...) and dispatches jobs. Run it once on adoption, feature-flagged behind a config toggle.

Store derivative names yourself, not the package's defaults. Use a deterministic, content-addressed path like images/{sha256(source)}/{width}.{format}. That gives you free cache invalidation (new source → new hash → new paths), safe concurrent generation, and a swap path to a different backend later.

Test the wrapper, not the package. Write a fake implementation of ImageTransformer for feature tests so your test suite never touches Node or ImageMagick. Reserve a single integration test for the real implementation, gated behind an env flag so CI can skip it on machines without the binaries:

#[Group('integration')]
class RespimgTransformerTest extends TestCase
{
    protected function setUp(): void
    {
        if (! config('services.images.real_backend_enabled')) {
            $this->markTestSkipped('Image binaries not available');
        }
    }
}

Gotchas and Tips

Silent no-ops are the failure mode you will most likely hit. If jpegoptim, pngquant, or cwebp is missing, expect the workflow to still exit 0 and produce JPEG-only derivatives. Log the resulting derivative list, not just "job succeeded" — alert on missing formats, not on job status:

if (! in_array('webp', collect($result['srcset'])->pluck('format')->all())) {
    Log::warning('WebP requested but not produced — check cwebp in worker image');
}

Filenames cross a shell boundary. Anything that lands in a path handed to a subprocess is suspect. Sanitise upload filename components (basename, strip non-[a-zA-Z0-9._-], cap length) before they reach the package. If a value is user-controlled (original filename, an S3 key constructed from request input), assume command-injection review is required, not optional.

Write-then-rename, or accept corrupt derivatives. The wrapper likely writes each output file directly. If a job is killed mid-write you get zero-byte or truncated derivatives that the web server happily serves with a 200. Either patch the wrapper to write to *.tmp and rename() on success (small enough change), or add a post-generation validation step that reads image dimensions back with getimagesize() and deletes anything that doesn't parse.

No manifest = duplication and orphans. The package has no idempotency layer. Your own media_asset_variants table (source hash, width, format, storage path, bytes) is not optional — it is what makes re-runs safe, lets you skip-if-exists, and lets you reap derivatives when the source is replaced. Without it you will accumulate orphan files forever.

PHP 8 / Laravel 10+ compatibility is unverified. This is likely Laravel 5.x-era code. Before adopting, spike a fresh Laravel app on your target PHP version and confirm:

  • composer require resolves without conflict
  • the service provider auto-registers
  • a real transform produces the expected files

If it fails on PHP 8, a vendored fork is typically faster than a shim (the wrapper is small), but set a hard timebox — if it takes more than a couple of days, switch to intervention/image v3 or a managed service.

Never trust it with request-path resizing. Not because it can't, but because it shouldn't. A /images/foo-800.jpg route that dynamically calls this package will convert image bytes directly into p99 latency and PHP-FPM starvation. Pre-generate.

Watch memory on large PNGs and multi-page inputs. ImageMagick will happily eat gigabytes on a 4000×4000 PNG or a TIFF with 20 pages. Set a -limit memory and -limit map on the IM invocation if the wrapper allows passing extra args, and cap source dimensions at upload validation with getimagesize(). On the queue side, set $timeout and $tries conservatively and route failed jobs to a dead-letter queue so retries don't hammer a bad source.

Different image sizes in dev vs. prod are almost always mismatched binaries or config. If width=960 derivatives come out different heights on your laptop vs. the staging worker, look at: ImageMagick version, the config file actually loaded (publish vs. package default), and whether environment variables like RESPIMG_QUALITY are set differently. Add the resolved config to your first-log line for every transform job.

Extension points worth using:

  • Config breakpoints: keep them in config/respimg.php but source them from the same design tokens as your CSS. A 960px breakpoint in <img sizes> that doesn't match your @media (min-width: 960px) is a wasted download.
  • sizes attribute: this is your lever, not the package's. The package emits some default — override it per call site. 100vw on a card grid is wrong and you will ship double-sized images everywhere.
  • Formats: add webp (and avif if avifenc exists in your worker image) but always emit a JPEG fallback in the src. The package may or may not do <picture> automatically; if not, build that in your Blade component.
  • Cache directory: treat the package's default cache directory as ephemeral and per-environment. If you're on a read-only container filesystem, point it at a writable path in storage/app/respimg — the package will not warn you, it will just fail to write and return paths to files that don't exist.

Debugging checklist, in order, when "it doesn't work":

# 1. Are the binaries on PATH *inside the container/worker*?
docker run --rm your-worker-image bash -c 'which convert jpegoptim pngquant cwebp node'

# 2. Did the transform actually run without an exit-0 silent skip?
#    Look for the derivative file on disk.
ls -la storage/app/respimg/

# 3. Is the resolved config what you expect?
php artisan tinker --execute="dump(config('respimg'));"

# 4. Is the subprocess failing but the wrapper swallowing the error?
#    Re-run the underlying command by hand on the same file.

# 5. Is the path crossing the disk boundary correctly?
#    If source is on S3, this package likely only handles local FS — download first.

Treat this as vendored code from day one. Add a README note in your repo tagging the version, an owner, and the native-binary prerequisites. That is the single cheapest thing you can do to survive the day when the worker image rebases and pngquant disappears.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource