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

Eloquent Dynamic Photos Laravel Package

behzadsp/eloquent-dynamic-photos

A Laravel package that attaches dynamic photos to Eloquent models, generating and serving image variants on the fly. Define custom sizes and conversions, cache results, and keep model media handling simple and configurable.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

Build vs. buy: For models that own one or more image columns, this is a "buy" that removes a recurring class of self-inflicted bugs (orphaned files, path traversal, stale conversions, EXIF-rotated mobile uploads). v2.0.1 ships a real test suite (167 tests), a five-cell PHP 8.3–8.5 × Laravel 12/13 CI matrix, phpstan/larastan at max with zero suppressions, and honest support metadata — that is the maintenance evidence you want before adopting a small-vendor package, and it's stronger than most comparable packages offer.

Feature unlocks you can now put on a roadmap:

  • Multi-image models. photoDefinitions() replaces seven per-model override methods with a per-field declaration (PhotoDefinition, Conversion, Fit). Two photo columns on one model can have independent disks, directories, formats, and conversions — avatar-on-S3 + hero-on-local is now a config line, not a fork.
  • Derivative strategy as a product lever. Named conversions (thumbnails, etc.) are declared per field and generated from the stored primary. regeneratePhotoConversions($field) backfills rows stored before a conversion existed, idempotently — so "add a 400px card thumbnail" becomes a shippable ticket instead of a data-migration project.
  • Perceived-performance work. Optional queued conversion generation (ProcessConversionsJob, with queue_conversions / queue.connection / queue.name) keeps the primary write synchronous and defers the expensive derivatives. This is a direct lever on upload latency and on request-timeout incidents.
  • Retention and cost policy. Automatic cleanup on model delete via an Eloquent deleted hook, soft-delete aware (files survive deleted_at, removed on forceDelete()), gated on transaction commit before touching disk, and globally disablable via delete_files_on_model_delete. That turns "we have X TB of orphaned files" from an unbounded liability into a config decision.
  • Upload hardening as a security posture. MIME allowlist checked twice (container sniffed from the raw header before decoding, then against the decoded image rather than a client-supplied header), plus max_file_size, max_width/max_height (downscale, never reject, never upscale), and a max_pixels pre-decode budget that accounts for frame count in GIF/WebP. The measured result — a 1.4 MB 100-page LZW TIFF going from ~3 GB allocated to 0 MB under imagick — is a DoS/memory-ceiling control, not a nicety.
  • Mobile-first image correctness. auto_orient bakes EXIF rotation into pixels before encoding (ext-exif; no-op without it). Sideways user avatars are a support-ticket generator you can delete from the backlog.
  • API ergonomics that prevent data loss. deletePhoto($field) clears the column and the files; deletePhotoFiles($field) deletes files only. Distinct intent, distinct method — no more "we deleted the file and left the column dangling."
  • Failure observability. StorageFailedException on Storage::put() returning false (Laravel's default filesystem config does this instead of throwing) for both primary and every conversion, so silent storage failures surface in your error tracker instead of as 404s in a week.

Upgrade urgency if you are on v1: treat this as a security and correctness mandate, not a nice-to-have. v1 defects fixed in v2, each with a named regression test:

  • The name attribute was interpolated into the storage path unsanitized — a user-controlled value containing / or .. could write outside the intended directory.
  • updatePhoto() deleted the old file before writing the new one, so any later failure left the DB pointing at a file that no longer existed.
  • The _url accessor stripped every occurrence of the _url substring, and fired for any attribute pair ending in _url — a model with real website / website_url columns silently returned Storage::url($website).
  • getPhotoFullPath() used Storage::path(), which on S3 silently returns a locally-prefixed key that is not a filesystem path.
  • getEloquentPhotoTimestampFormat() called a method that did not exist — fatal for anyone who followed the README.
  • Every upload encoded the image twice, doubling CPU and peak memory.
  • Dependency floor was dishonest (str() requires Laravel 9 while composer.json claimed ^7.0|^8.0), and intervention/image ^2.7 was EOL.

Migration cost to budget: v2 is a rewrite of the model-facing API. The seven override methods are gone, config keys are renamed (root_directory → directory, slug_limit → name_limit), the config file moved to config/eloquent_photo.php under the eloquent-photo-config tag, and photoFullPath() / photoPath() / photoUrl() now take a field name rather than a raw path. Follow UPGRADE.md; scope this as a discrete ticket with call-site greps, not a drive-by.

Release hygiene flag for engineering: install v2.0.1, not v2.0.0. Packagist pinned v2.0.0 to the commit before Laravel 11 was dropped and cannot be moved off an already-ingested tag, so v2.0.0 is not installable against the intended matrix. Require ^2.0.1.

When to Consider This Package

Adopt when:

  • You are on PHP ^8.3 and Laravel ^12.0 || ^13.0. This is a hard gate, not a preference.
  • One or more Eloquent models own image columns and you want disk/path/format/conversion behaviour declared next to the model rather than smeared across controllers, observers, and jobs.
  • You need per-field storage divergence (different disks, directories, formats for different columns on the same model).
  • You are currently on v1 and have user-supplied filenames, S3/remote disks, or queued upload paths — the fixed path-traversal, ordering, and accessor defects make upgrading the cheaper option.
  • You accept untrusted uploads and want pre-decode memory controls without standing up an image-processing service.
  • You want derivative backfill to be an operational command (regeneratePhotoConversions) rather than a bespoke script.
  • You want automatic cleanup on delete with soft-delete and transaction semantics that match your data model.

Look elsewhere when:

  • You are on Laravel ≤11 or PHP <8.3. Laravel 11 is deliberately excluded — it is past its security window, seven laravel/framework advisories have no 11.x fix (landing in 12.60.0/12.61.1/13.10.0), and Composer 2.10+ blocks the whole 11.x line by default. If you cannot move Laravel, this package cannot help you, and a "supported on paper" ^11.0 would not have installed anyway.
  • You are pinned to intervention/image v2 or v3 by another package. v2 jumps straight to ^4.2; v3 is not in the matrix. Resolve that conflict before you plan the upgrade.
  • You need a media library, not a column helper: polymorphic multi-model galleries, collections, custom properties, ordering, per-attachment metadata → spatie/laravel-medialibrary is the right shape.
  • You need transformation features beyond fit/resize conversions — focal-point cropping, art-directed crops, responsive srcset generation, or on-the-fly URL transforms. Pair a CDN/transform service instead; this package generates derivatives at write time only.
  • You need non-image attachments (PDFs, video). Out of scope by design.
  • You have exactly one image column with one size and no untrusted input. The package still works, but the value concentrates in validation, cleanup, and conversion regeneration — justify it on those or skip it.
  • You require a vendor with a support contract or an SLA. This is single-maintainer; the CI matrix and static analysis are good signals, not a guarantee.

Cost note to carry into the decision: default quality rose from 50 to 75. New uploads are larger and visibly better; existing stored files are unaffected. If storage or CDN egress is a watched metric, model the delta before you flip it, or override the config deliberately.

How to Pitch It (Stakeholders)

To executives — one paragraph: We are standardising image handling on a package that was just rewritten, and the rewrite is primarily a risk-reduction event. It closes a path-traversal defect where a user-supplied filename could write outside the intended directory, fixes an upload sequence that could leave records pointing at deleted files, and caps memory exposure from malicious or malformed uploads — a crafted 1.4 MB TIFF that previously allocated ~3 GB now allocates effectively nothing. It also eliminates orphaned files on delete (a direct storage-cost line), makes adding a new thumbnail size a config change plus an idempotent backfill rather than a migration project, and lets us move derivative generation to the queue so upload latency and request timeouts improve. The support matrix is now honest — PHP 8.3+, Laravel 12/13 — and deliberately excludes Laravel 11 because it is past its security window with unfixed framework advisories. Cost: a scoped migration ticket, since the model-facing API changed completely, and slightly larger files from a better default quality setting.

To engineering — the short version:

  • v2 replaces seven per-model override methods with a typed photoDefinitions(): array<string, PhotoDefinition> declaration using PhotoDefinition / Conversion / Fit. Per-field disks, directories, formats, and conversions; two photo columns on one model can differ freely.
  • Install ^2.0.1 — v2.0.0 is uninstallable (Packagist pinned it to a pre-Laravel-11-drop commit and won't move an ingested tag). Read UPGRADE.md; config keys renamed (root_directory→directory, slug_limit→name_limit), config moved to config/eloquent_photo.php (tag eloquent-photo-config), and photoFullPath()/photoPath()/photoUrl() now take a field name.
  • Correctness wins you get for free: write-then-delete ordering with conversions generated before the old file is removed, single encode per upload (halves CPU and peak memory), Str::slug() on all filename construction, suffix-only _url accessor gated on declared fields, StorageFailedException when a disk rejects a write, and no pointless transaction widening a DB lock around I/O.
  • Ops-relevant behaviour: cleanup honours soft deletes, waits for the enclosing transaction to commit before touching disk, and can be disabled via delete_files_on_model_delete. Conversions can be deferred via queue_conversions / queue.connection / queue.name (ProcessConversionsJob); the primary is always synchronous.
  • Validation: double MIME check (raw container pre-decode, decoded image post-decode), max_file_size, downscale-only max_width/max_height, and a frame-count-aware max_pixels pre-decode budget. auto_orient needs ext-exif and no-ops without it. Driver (gd/imagick) is runtime-selected via the driver key through Intervention v4's ImageManager::usingDriver() — no hard ext-gd dependency.
  • Bar to hold us to: larastan level max with zero suppressions in src/, 167 tests, composer test / test:coverage / lint / analyse, CI across PHP 8.3–8.5 × Laravel 12–13. Match that in anything we write alongside it.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console