jeffersongoncalves/laravel-report-url
A lightweight Laravel package for adding URL reporting to your application. It provides configuration, routes, and controller scaffolding to collect, store, and manage reports associated with URLs, making it easy to integrate into existing Laravel projects.
Architecture fit — The package is a narrow, single-purpose capability: persist a generated report file on a private disk and expose it through a time-boxed, signed URL instead of an email attachment. That maps cleanly onto Laravel primitives (service provider, publishable config, filesystem disks, signed routes, migration-backed records, artisan command, scheduler, events), so it slots into an existing Laravel app without imposing a new architectural layer. The coupling to laravel-saved-filter is optional and via a container binding (HostsReportFiles), which is the right shape — it extends an existing reporting workflow rather than replacing it.
Integration feasibility — Straightforward for a team already on Laravel: composer require, publish config and migrations, configure a private disk and the route middleware, schedule the prune command. The feature set implies a persisted record per link (expiry, counter, last_downloaded_at), so a migration and a table are part of the install footprint. The main integration unknowns are environmental rather than code-level: signed URL generation depends on correct APP_URL/scheme/host resolution behind proxies and load balancers, and the download route must not collide with existing route names or catch-all auth middleware.
Technical risk
restrict_to_owner is described as optional. If it defaults to off, the signed URL is effectively a bearer token — anyone who receives or forwards the link can download until expiry. The 7-day default TTL is long for anything sensitive. Both defaults need explicit confirmation and likely a documented recommendation to shorten TTL and enable owner checks.report-url:prune is scheduled "on one server," which is correct only if all app nodes share the same disk (S3 or a shared volume). On per-node local disks, files written by one node are invisible to the pruning node and leak indefinitely. This is the single most likely production misconfiguration.response()->download() and in-memory reads will hit PHP memory limits. Needs verification that downloads stream and that range requests / content-type are handled.store() is idempotent, whether counter increments are atomic under concurrency, or whether the download event is dispatched inline (listener failures could break the download).Key questions — What are the shipped defaults for ttl, restrict_to_owner, and the route middleware stack? Does it support S3 presigned URLs, or only local temporaryUrl? Is the download response streamed? What is the migration/table shape and expected row growth? Does prune clean orphaned files as well as expired records? Is ReportUrlDownloaded queue-aware? What is the minimum Laravel/PHP version, and is laravel-saved-filter 1.9+ truly optional at runtime?
Stack fit — Assumes Laravel with a configured filesystem abstraction, database migrations, and the scheduler running. No queue, cache, or external service is required for the core path, which keeps the blast radius small. The laravel-saved-filter binding is additive: teams not using that package are unaffected.
Migration path — Nothing to migrate from; this is the first release. The migration is from whatever the app does today — typically Storage::temporaryUrl() calls scattered in controllers, or attachments in scheduled-report emails. Replacing those call sites with ReportUrl::storeFile() is a contained, per-endpoint change and can be done incrementally.
Compatibility — Verify the framework/PHP constraints against the host app before committing. The laravel-saved-filter integration requires 1.9+; confirm the constraint is expressed as a soft/optional dependency rather than a hard requirement so non-users aren't forced into an upgrade. Route registration should be disableable via config for apps that want to own the endpoint.
Sequencing
ttl, restrict_to_owner, and middleware defaults explicitly rather than accepting them.report-url:prune --dry-run against staging data, then enable the daily schedule with onOneServer().Maintenance — Small surface area: one route, one command, one table, one config file. Ongoing cost is mainly version pinning and periodic review of TTL/retention defaults. The package is young, so expect API refinement in early 1.x and budget for reading changelogs.
Support — The dominant ticket driver will be "my download link doesn't work." Support needs a short runbook covering the three causes: expired (410 by design), signature mismatch after a host/scheme change or proxy reconfiguration, and owner-check rejection for a user who isn't the link's owner. Expiry should be surfaced in the UI, not left to the error page.
Scaling — Two pressure points. Storage must be shared (S3 or shared volume) for pruning and downloads to behave consistently across nodes. And the link table grows one row per generated report — with a 7-day default TTL and daily pruning this stays bounded, but a long TTL or a failed prune job turns it into unbounded growth on a hot table that receives counter updates on every download.
Failure modes
store() time — should fail loudly, not silently return a broken link.Ramp-up — Low for any Laravel developer: the API is two static methods plus a fluent url()/expires_at accessor. The real ramp-up is operational, not syntactic — whoever owns the scheduler, the disk configuration, and the signed-URL/proxy interaction needs to understand all three. Plan for one named owner, a documented TTL/ownership policy, and a staging check of expiry and prune before this carries production report delivery.
How can I help you explore Laravel packages today?