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

Technical Evaluation

Architecture fit. behzadsp/eloquent-dynamic-photos is a model-layer concern package: it attaches photo fields to Eloquent models, writes primaries plus derived conversions to a Laravel disk, and exposes path/URL accessors. v2 replaces v1's seven per-model override methods with a declarative photoDefinitions(): array<string, PhotoDefinition> map, which is a materially better fit for models with more than one image column (independent disk, directory, format, and conversion sets per field). No new architectural surface is introduced — it remains a thin layer over Illuminate\Filesystem and intervention/image, with one queued job (ProcessConversionsJob). Teams already using Storage facades and standard Eloquent lifecycle hooks will find nothing surprising.

Integration feasibility. Hard gates are narrow and explicit: PHP ^8.3, Laravel ^12.0 || ^13.0, intervention/image ^4.2. The Laravel 11 exclusion is deliberate and well-argued (past security window, seven unfixed advisories, and Composer 2.10+ blocking the 11.x line by default) — this is a support-matrix honesty decision, not an oversight, and it removes a class of "supported on paper, uninstallable in practice" tickets. The ext-gd hard dependency is gone: the driver is now chosen at runtime via a driver config key resolved through Intervention v4's ImageManager::usingDriver(), which helps on imagick-only hosts. auto_orient requires ext-exif but degrades to a documented no-op, which is the right failure posture.

Technical risk. Three items dominate:

  1. Packagist tag poisoning. v2.0.0 is pinned to a commit from before the Laravel 11 drop and cannot be corrected because Packagist will not move an ingested tag. Anyone resolving ^2.0 without a floor gets a broken install. Every consumer must pin 2.0.1 or later, and internal templates/skeletons referencing v2 need auditing. This is a supply-chain hygiene issue that will resurface in support tickets.
  2. Total model-facing API break. The v1 → v2 surface changed completely; UPGRADE.md is the only bridge. Any codebase with v1 calls in application code (not just the model) needs a mechanical pass.
  3. v1 defect history. The fixed-defects list is a catalog of serious production bugs: a storage path traversal via unsanitized name interpolation (security), old-file deletion before new-file write (data loss), double-encode on every upload (2× CPU and peak memory), and a fatal call to a nonexistent method for anyone following the README. That history is a real signal about the package's prior QA, and it should be weighed against the countervailing signal: v2 ships a genuine suite (167 tests, real encoded fixtures, Storage::fake() assertions), larastan level max with zero suppressions in src/, and a five-cell CI matrix. The engineering posture has clearly changed; the track record has not yet.

Key questions.

  • Is Laravel ^13.0 actually released and stable at adoption time, or is that clause aspirational? Confirm the CI matrix reflects real framework versions rather than dev branches.
  • What is the remainder of the truncated release note describing photoFullPath()'s remote-disk behavior? The fix direction (inspecting the disk rather than blindly calling Storage::path()) matters for S3/Spaces users, but the final semantics are cut off.
  • Is intervention/image ^4.2 GA, and what is its own PHP/Laravel floor? An immature image library is a transitive risk we don't control.
  • Who maintains this package? Single-maintainer projects carry bus-factor risk, and v2 is a rewrite — the version most likely to surface new defects.
  • What is the actual storage/bandwidth delta from the quality default moving 50 → 75 at our upload volumes?

Integration Approach

Stack fit. Adopt only on Laravel 12 (or 13 if confirmed GA) with PHP 8.3+. This is a forcing function: services still on Laravel 11 or PHP 8.2 cannot take v2 at all, and there is no supported middle path — v1 is not a viable fallback given its defect list. Treat the framework upgrade as the prerequisite workstream, not a parallel one. Confirm the runtime image driver on each target environment: gd remains the low-friction default, imagick is required for some format/dimension edge cases and is what the memory-control measurement was run against.

Migration path. Sequence strictly:

  1. Framework and PHP floor first (Laravel 12, PHP 8.3), validated in a lower environment.
  2. Pin behzadsp/eloquent-dynamic-photos:2.0.1 explicitly — never ^2.0, never 2.0.0.
  3. Republish config: the file moved to config/eloquent_photo.php under tag eloquent-photo-config. Rename keys root_directory → directory and slug_limit → name_limit.
  4. Rewrite model declarations to photoDefinitions(); delete all seven v1 override methods (they no longer exist and one of them was fatal anyway).
  5. Update application-level call sites: photoFullPath(), photoPath(), photoUrl() now take a field name, not a raw path. Audit any code that passed a computed path — that pattern is now a silent-wrong-answer risk, not a type error.
  6. Relocate existing conversion files. The layout changed to {directory}/{model-dir}/conversions/{primary-basename}/{conversion}.{format}. Run regeneratePhotoConversions($field) to backfill — it is idempotent and retry-safe, so it can be run per-field, re-run after partial failure, and scripted as a batch job. Budget this as a real data-migration step with disk-write time proportional to conversion count × row count.

Compatibility. Two behaviors change without erroring, which is the riskiest category:

  • quality 50 → 75 affects new uploads only. Existing files are untouched, so the impact is a gradual, permanent increase in storage and CDN egress. Model it before rollout.
  • Strict config typing now raises PhotoDefinitionException naming the offending key rather than silently casting. env()-shaped strings ("75", "true") remain accepted, so Laravel-idiomatic config is safe — but hand-edited config files with wrong types that "worked" in v1 will now hard-fail at first use. This is good behavior arriving as a latent boot-time landmine; validate config in staging.
  • The _url accessor is now gated on fields declared in photoDefinitions(). Models with a real website/website_url column pair stop returning garbage — but any application that came to depend on the old over-broad behavior breaks. Grep for _url attributes on photo-bearing models.

Sequencing. Do the conversion-path migration and the call-site rename in the same change window as the model declaration rewrite — splitting them leaves the application in a state where paths resolve but conversions 404. Enable queue_conversions only after the synchronous path is verified end-to-end; the primary is always written synchronously, so a queue misconfiguration degrades to missing thumbnails rather than missing originals, which is the correct failure ordering.

Operational Impact

Maintenance. The package now carries real maintenance signals — CI matrix across PHP 8.3/8.4/8.5 × Laravel 12/13, larastan level max with zero suppressions in src/, composer scripts for test/coverage/lint/analyse. That lowers our cost to evaluate future releases considerably. Against that: v2.0.0's tag is permanently broken on Packagist, which is an operational hazard we inherit and must defend against with pinned versions in every composer.json and every internal scaffold. Single-maintainer risk remains and should be recorded in the dependency register with a fallback plan (the package is small enough to fork and vendor if abandoned).

Support. Support load concentrates in the upgrade window: teams hitting the API rewrite, the config key renames, and the conversion-path move. The strict-config exceptions are self-describing (they name the key), which shortens triage. The larger support question is the framework floor — any team that cannot reach Laravel 12 becomes a blocked ticket with no supported resolution path, and that needs an explicit product decision rather than case-by-case escalation.

Scaling. v2 improves scaling characteristics in three concrete ways. First, queued conversions via ProcessConversionsJob (configurable queue_conversions, queue.connection, queue.name) let upload requests stay cheap — the primary write remains synchronous, so request latency is bounded by one encode rather than one encode plus N conversions. Second, encode-once instead of encode-twice removes the previous 2× CPU and peak-memory penalty on every upload — directly relevant to PHP-FPM worker sizing. Third, the pre-decode pixel budget (max_pixels, frame-count-aware for GIF and WebP) is a genuine memory control: a disallowed container never reaches the decoder, so it cannot allocate before being rejected. The measured 1.4 MB / 100-page LZW TIFF result (~3 GB allocated before, 0 MB after, on imagick) is the kind of number that prevents a whole class of worker-OOM incidents. Note the honest caveat in the notes: imagick's frames can't be counted by the pixel budget, so the container allowlist is doing the work there — meaning the allowlist's completeness is now a security-relevant config surface. Also note max_width/max_height downscale and never reject, never upscale — a permissive policy that shifts cost to storage rather than to user-facing errors.

Failure modes. v2's ordering is substantially safer than v1's: write new file → save → generate conversions → delete old file. The v1 behavior (delete old before writing new, then fail) left columns pointing at vanished files; that's fixed. Two residual gaps to plan for:

  • StorageFailedException is raised when the disk rejects a write (Storage::put() returning false — Laravel's default filesystem config does this instead of throwing), for the primary and every conversion. Catch it explicitly; uncaught, it surfaces as a 500 mid-upload. With queued conversions, a conversion write failure lands in the queue failure path, not the request path — so queue failure monitoring is now a hard requirement, not a nicety.
  • updatePhoto() no longer opens a transaction around the save. The reasoning is sound (the file write was already outside the transaction, so it bought nothing and widened the DB-lock window during I/O), but it means the DB write and the conversion generation are not atomic. regeneratePhotoConversions() is the designed recovery lever — idempotent and retry-safe — so the runbook should be "on partial failure, re-run backfill," not "roll back."

Also plan around: automatic file cleanup on model delete fires via an Eloquent deleted hook, respects soft deletes (files survive softDelete(), are removed on forceDelete()), waits for the enclosing transaction to commit before touching disk, and can be disabled globally with delete_files_on_model_delete. That transaction-aware, post-commit deletion is the correct design — it prevents the classic "files deleted, transaction rolled back" data loss — but it does mean deletion timing is tied to commit, which affects tests and any manual data-fix scripts that manipulate rows outside normal model lifecycle. Know which of deletePhoto($field) (files and clears/saves the column) and deletePhotoFiles($field) (files only) you want; picking the wrong one either orphans rows or orphans files.

Ramp-up. For a team already on Laravel 12, the learning curve is short and localized: one declaration API (PhotoDefinition, Conversion, Fit), one config file, two or three accessor signatures, and one queue job. Budget roughly a day for the model rewrite plus call-site audit on a medium codebase, and treat the conversion-file relocation and backfill as the long pole because it is I/O-bound and touches production data. Produce an internal upgrade note that front-loads the four silent-breakage items — quality default, strict config typing, _url accessor gating, field-name-not-path accessors — since each fails without an error.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource