imsus/laravel-imgproxy
imsus/laravel-imgproxy provides a Laravel wrapper for imgproxy, letting you generate secure signed image URLs with configurable presets, resizing, and optimization options for serving responsive, optimized images in your app.
Architecture fit
cover(), fit(), toWebp()/toJpg()/toPng()/toAvif(), optimize(), orient(), flips, and storePublicly() are ergonomic compilers down to the same processing-option segments already emitted by the typed option layer. No new wire format, no new service dependency, no config surface.withOption() as the wire-level escape hatch) is the right shape for a shared internal package: product code reads in domain terms, while platform work retains an exact imgproxy escape hatch. It is explicitly recorded (ADR 0002, CONTEXT.md, API reference), which is the main thing I want to see for a package several teams will depend on.contain(), scale(), coordinate crop(), grayscale()) are defensible and documented, but they mean the intent surface is deliberately incomplete. Expect the README's intent-first framing to generate "how do I contain / grayscale?" questions; the mitigation is that advanced-usage now documents the contain composition.resize() vs cover()/fit(), and optimize() vs explicit toWebp() + quality option. This is a governance question more than a code question — the ADR/naming policy exists, which reduces the chance of a third dialect appearing.Integration feasibility
composer update inside the existing ^2.x constraint. No BC break declared, no config publish, no migration step, no runtime flag.Imgproxy::fromStorage(path, disk, ?instance) and fromPath() / fromUrl() reduce caller-side boilerplate and remove the "which of the five entry points do I use?" ambiguity that usually surrounds image URL builders. fromStorage reuses the same public-disk → url() / private-disk → temporaryUrl() resolution as the existing Storage macro, which is consistent — and is also a coupling point to verify.resize() still runs identically. This is the cheapest possible upgrade shape and should not require a scheduled migration window.Technical risk
Conditionable on an immutable builder. when() / unless() invoke the callback and fall back to $this when the callback returns nothing useful. Arrow functions (fn ($b) => $b->cover(...)) are fine; a statement-style closure that forgets to return the new instance will silently discard the change. Same concern applies to the higher-order proxy form (->when($cond)->cover(...)). I want a test asserting these forms actually produce the expected segment, plus a docs line stating the return-the-new-instance contract. This is the single most likely source of "my image isn't resized" bug reports.optimize() has implicit compound defaults. Defaulting to f:webp and q:70 means one call changes both format and quality. Teams expecting "optimize the JPEG I already have" will get WebP and a fixed quality instead. That is a content-type/Accept negotiation and cache-key concern, not just an aesthetic one.fromStorage() public/private classification is a security-relevant branch. A file on a disk treated as public resolves to a durable url(); a misclassification turns a private asset into a guessable, non-expiring URL. The behaviour mirrors the Storage macro, so this is inherited risk — but a new, easier entry point broadens the blast radius. Similarly, storePublicly() writes with public visibility and should be gated by a data-classification conversation, not just a convenience method.fromStorage.resize() gives no runtime signal, no IDE/static-analysis error, and no usage telemetry. We cannot measure remaining resize() usage to forecast a 3.0 rename. That is a planning blind spot, not a correctness bug.fit never enlarges"). If upstream imgproxy changes behaviour or we pin a different instance version, the promise breaks silently and the name lies. Needs a compatibility note tied to the imgproxy version range we actually run.Conditionable adds when/unless to the builder's public surface. Low collision risk, but confirm nothing in our own subclassing/decoration of the builder already defines those names.Key questions
illuminate/support versions, imgproxy versions) and is it tested in CI, or inferred? Conditionable availability is version-bound.resize() → cover()/fit() targeted at a 3.0 with a real removal, and will there be a runtime deprecation notice or usage telemetry before then?fromStorage() handle non-temporaryUrl-capable disks (local/local driver) gracefully, or does it throw? What is the documented behaviour?when()/unless() on the immutable builder documented and tested in all three call styles?optimize()'s WebP-by-default intentional for consumers who have not opted into format negotiation, and what is the recommended pattern for quality?advanced-usage compositions if imgproxy semantics shift?Stack fit
fromStorage() removing hand-rolled disk/visibility logic.Migration path
^2.2, upgrade, ship.resize() call sites. Adopt cover()/fit() for new code and for files touched for other reasons.resize() usage, since the deprecation is docblock-only and tooling may surface it only as an annotation.fromStorage() after confirming private-disk temporaryUrl behaviour in each consuming service's disk configuration; do not let it become the default for asset types we have not classified.Compatibility
Conditionable floor, it stays on the prior 2.x patch and that must be a recorded decision, not an accident.fit non-enlargement and format handling).Sequencing
resize() regressions.when() call styles and for fromStorage() on both a public and a private disk.resize → cover/fit, explicit format options → toWebp() etc.), plus the optimize() defaults warning.resize() sweep only once a removal or runtime deprecation is announced.Maintenance
Support
advanced-usage and withOption(), framed as "intent methods cover the common cases; the typed layer covers the rest."when() callbacks. One clear docs sentence plus a support runbook entry closes most of it.optimize() surprises (WebP + q=70) will present as "wrong file type" or "quality regression" tickets. Document the defaults prominently.Scaling
optimize()'s WebP default will shift cache keys and content types across consuming surfaces if adopted broadly. Roll it out deliberately per surface rather than uniformly.Failure modes
when()/unless() (highest likelihood, lowest severity).optimize() changing format where the consumer assumed format preservation (client-visible content-type / quality regressions).Ramp-up
cover(), toWebp(), storePublicly() are self-describing.withOption() rather than inventing a new intent method, and must know the when() return contract. A one-page internal convention note plus the ADR reference is sufficient.How can I help you explore Laravel packages today?