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 Viewable Laravel Package

cyrildewit/eloquent-viewable

Eloquent Viewable is a minimalistic analytics package for Laravel that adds seamless view tracking to Eloquent models, with cooldowns, queued recording, unique counts, and period-based queries.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • Pattern: in-process, database-backed analytics. Every view is its own row in a polymorphic views table (viewable_type/viewable_id, visitor, collection, viewed_at); aggregation happens at read time through a fluent views($model) API, Eloquent scopes, and a Period value object.
  • Fits: Laravel monoliths and modular monoliths where the viewable models already live in the same database. Strong for editorial/blog content, marketplace listings, "most read"/"trending" surfaces, and internal dashboards on modest-to-moderate traffic.
  • Doesn't fit: very high-QPS pages (millions of views/day per instance), funnel/attribution analysis, cross-device identity, or any metric with contractual/reporting weight. This is directional product analytics, not a BI system.
  • Sourcing advantage: no JS snippet, no external vendor, no additional data processor in the DPA. That is a genuine procurement and privacy-review accelerant — though IP and user agent still land in your own database, so the compliance work moves rather than disappears.
  • Positioning: occupies the "just enough, in my own DB" middle ground between an incrementing counter column (cheap, no history), Redis/ClickHouse (scale, meaningful ops cost), and GA4/Plausible (external dependency, consent overhead).

Integration feasibility

  • Effort: low, measured in hours for a first model. composer require, publish migrations, php artisan migrate, add Viewable interface + InteractsWithViews trait, call views($post)->record() in the controller show action.
  • Extensibility is the standout: custom View/Views models, custom visitor resolution, pluggable crawler detection, macros, view collections, per-call or global queueing, built-in caching, and removeViewsOnDelete. That covers most enterprise "but we need tenant scoping / custom attribution" objections without forking.
  • Idiomatic surface: Eloquent scopes, service provider, publishable config — no new mental model, no new runtime. Reviewers will recognize everything.
  • Hard constraints: no Lumen support. Version matrix is explicit — v8 requires Laravel 13.x + PHP 8.5+, v7 covers Laravel 6–13 + PHP 7.4+. The version line must be chosen against the current platform, not the latest tag.

Technical risk

  • Write amplification (medium): one INSERT per view, plus queue overhead. Pages rendering N viewable models issue N inserts. Needs a capacity model before commit — this is the single most common reason these rollouts get walked back.
  • Read cost (medium-high): count() on a detail page is fine with the right index; orderByViews on a paginated listing is the scaling cliff, typically appearing as the slowest query on the page once the table reaches tens of millions of rows. Caching helps, but the package does not ship rollup/pre-aggregation — that becomes custom work.
  • Uniqueness semantics (high, organizational): "unique view" is a hash of visitor signals and session-based cooldown, not identity. VPN/UA rotation inflates; shared corporate IPs and cookie-less clients deflate. If a stakeholder treats this number as decision-grade without a documented definition, you get a metrics-trust incident, not a bug.
  • Privacy (medium): IP and user agent are persisted. DNT and crawler filtering are built in (good), but retention, anonymization, and jurisdictional treatment of hashed IPs remain yours. Route to legal/DPO before production writes.
  • Queue footgun (medium): with queueing enabled, ViewRecorded listeners execute in the worker without session, cookies, request(), or auth()->user(). Listeners reading request context fail silently — the failure mode is wrong analytics discovered weeks later.
  • QA confusion (low-medium): automatic crawler filtering means Postman and some test clients never record a view. Expect a "the package is broken" ticket during acceptance unless it's called out in the test plan.
  • Maturity: MIT, active releases, changelog, CI and Codecov coverage — solid hygiene. But 886 stars and 0 dependents is a small ecosystem footprint: effectively single-maintainer, limited third-party war stories, no downstream patch pressure. Not evidence of instability, but it argues for a contingency plan and a thin internal wrapper.
  • Version drift (low): pinning to v7 while the platform moves to Laravel 13 creates a forced upgrade at the least convenient time.

Key questions

  1. What is peak page-view volume and the page-view-to-INSERT ratio? (drives queue decision and DB sizing)
  2. Is "unique visitors" a decision-grade metric? Who consumes it, and what decision depends on it?
  3. Build vs buy: does the org already pay for an analytics platform whose numbers this will contradict on day one?
  4. Which Laravel/PHP line are we on today, and what is the upgrade window to 13 / PHP 8.5?
  5. What is the raw-row retention window, and who owns the anonymization and purge job?
  6. Are any ViewRecorded listeners planned, and can each survive without request context?
  7. Who is the accountable internal owner if upstream goes quiet?

Integration Approach

Stack fit

  • Requirements: Laravel 8+ (v7) or Laravel 13 (v8), a relational DB (MySQL/Postgres/SQLite), optional Redis via the Laravel cache for count caching, and a standard queue driver if deferring writes.
  • No new infrastructure: no new service, vendor, secret, or deployment target. For a conservative platform team, that is the core argument — the marginal operational surface is close to zero.
  • Native alignment: same migrations, same RefreshDatabase test harness, same event/observability stack. Instrumentation sits in the same controller methods the team already owns.

Migration path

  • Greenfield: install, publish, migrate, add trait, record in show actions, add composite indexes on (viewable_type, viewable_id, viewed_at) plus visitor/collection columns. Introduce caching only after read p95 breaches its target — not preemptively.
  • From an incrementing counter column: dual-write for one release cycle. Keep views_count while rows start landing, assert that views($m)->count() converges with the legacy counter over a full period, then switch reads and drop the column. This is the one migration path where correctness is cheap to prove.
  • From an external analytics tool: do not backfill. Declare a cutover date, keep the old tool read-only for one reporting cycle, and communicate explicitly that trendlines are discontinuous. That is a stakeholder-comms task, not a technical one, and it is the step most often skipped.
  • From nothing: ship behind a per-model feature flag so rollout is incremental and reversible at any point.

Compatibility

  • Version pinning: ^7 for Laravel 6–12 / PHP 7.4+, ^8 for Laravel 13 / PHP 8.5+. Lock the line to the platform and block bumps until the platform upgrade is scheduled and tested.
  • Database: verify COUNT and correlated-subquery behavior of the ordering scopes on the actual engine in the spike — index and planner behavior differ meaningfully across MySQL, Postgres, and SQLite.
  • Stateless clients: session-backed cooldowns do not dedupe API or mobile traffic. If those surfaces exist, plan a custom visitor resolver (user hash, device id) or accept inflated uniques there.
  • Multi-tenancy: polymorphic viewable_id does not scope by tenant. Use collections or a custom View model carrying tenant_id, and decide this before writing rows, not after.

Sequencing

  1. Spike (1–2 days): install on a scratch branch, migrate, seed ~1M synthetic rows, measure count() and orderByViews p95 on the target DB and engine.
  2. Version decision: select and pin the version line; record a short ADR with the upgrade trigger condition.
  3. Privacy review: data captured, retention, consent/DNT posture, DPIA note. Legal sign-off before any production write.
  4. Data model design: collections, indexes, tenant scoping, retention window — settled up front.
  5. Pilot one low-risk model (e.g., help-center articles) behind a flag; observe for one week.
  6. Expand to primary content models; enable queueing only if write latency appears in traces.
  7. Add caching for hot listing pages and a scheduled retention/purge job.
  8. Ship product-facing surfaces ("most read", trending) only after two to four weeks of stable numbers.
  9. Post-implementation review: reconcile against access logs or the prior tool; publish the expected delta to stakeholders in writing.

Operational Impact

Maintenance

  • Code surface is small: no service to operate and no bespoke schema to design. Recurring work is composer update cadence, index hygiene, the retention/purge job, and version bumps tied to Laravel majors.
  • Bus factor: single-maintainer upstream. Assume you may eventually fork or vendor-patch. Keep the integration behind a thin internal ViewTracker wrapper so a backend swap is a one-file change.
  • Retention is yours: the package stores rows indefinitely. Partitioning or pruning is custom work and must be scheduled, not discovered when the table hits a billion rows.

Support

  • No commercial support or SLA — GitHub issues and docs. Acceptable for directional product analytics; not acceptable for a metric tied to billing, SLAs, or contractual reporting.
  • Internal support load is low technically, higher behaviorally. The recurring ticket is "why don't these numbers match GA?" Pre-empt with a definitions page: what counts as a view, what is filtered (crawlers, DNT, blocked IPs), what "unique" means, and the expected variance versus other tools.
  • Ramp-up for a mid-level Laravel developer: under a day to be productive; about a week to learn the sharp edges (crawler filtering in local tests, queue event context, session cooldown semantics).

Scaling

  • Volume math up front: ~1M page views/day at roughly one row each is ~30M rows/month, ~360M/year. Manageable with correct indexes and a retention window; not manageable as an unpartitioned, unpruned, uncached table.
  • Writes: enable queueing on high-traffic pages — filtering still runs in-request, so bots and cooldown hits never reach a worker. Watch worker concurrency against the DB connection pool.
  • Reads: cache the ranked ID list for listing pages with a 60–300s TTL rather than caching per-model counts; that is the single highest-leverage optimization and it is not in the box.
  • Beyond roughly 5–10M views/month per instance, add a scheduled rollup into daily/weekly aggregates and read product surfaces from that, keeping raw rows for recent-window detail. Budget this as custom work — it is the most likely unplanned cost.

Failure modes

  • Write latency on the request path → p95 regression on content pages; mitigated by queueing.
  • Unbounded growth → slow aggregates, index bloat, backup and replica lag.
  • Cooldown mis-tuning → inflated uniques with cookie-less clients, undercounted returning visitors with long-lived sessions. Both look like "data is wrong" rather than "configuration is wrong."
  • Queue listener context loss → null user/IP attribution, silently, in the worker.
  • Crawler misclassification → apparent zero data during QA (Postman is filtered); double counting in production if the heuristic is loosened.
  • Missing indexes on first deploy → full table scans on orderByViews, discovered only once production data volume arrives.
  • Cascade behavior: with removeViewsOnDelete disabled, orphaned rows accumulate; enabled, bulk deletes on models with millions of views can stall the delete request.
  • Version drift: staying on v7 while the platform moves to Laravel 13 forces an unscheduled bump or freezes security patch cadence.

Ramp-up

  • Day 1: install, migrate, trait on one model, record + count in a controller; verify in tinker with a non-crawler user agent.
  • Week 1: periods, uniques, collections, caching, ordering scopes; write the internal definitions doc.
  • Week 2: queueing, custom visitor resolver, retention job, index and load test at synthetic scale.
  • Team enablement: one short internal session plus the definitions doc. The real onboarding cost is organizational trust in the metric, not the API.
  • First-quarter guardrails: feature flag per model, dashboards on view-record write latency and aggregate query p95, a weekly row-count/table-size check, and a named owner for the purge job and the upstream version bump.
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.
adachsoft/command-executor-lib
tagixo/document-builder
tagixo/page-builder
tagixo/mail-builder
tagixo/form-builder
primix/primix
tagixo/core
alesima/laravel-azure-service-bus
tomshaw/shopcart
likyly/laravel
darthsoup/shoppingcart
tdt/core
labyrinthe/connect-php
encore2/laravel-admin2
cboxdk/laravel-console-kit
shapecode/doctrine-dbal-datetimeutc
gilads-otiannoh254/inertia-protocol
dakiquang/laravel-oauth-refresh-lock
woda/laravel-worktrees
dalefranks/ramasite