atannex/matrix
Atannex Matrix is a Laravel/PHP package that provides a clean, configurable API for Matrix integration in your applications. It simplifies working with Matrix data and related features, helping Laravel developers add reusable matrix logic with minimal boilerplate.
v1.0.0 is the first stable release, so treat it as the moment to adopt rather than migrate. Scope is broad — views, reading time, clicks, reactions, ratings, reviews, threaded comments, shares, bookmarks, follows, reports — for any Eloquent model, for guests and accounts, plus counters, rollups, trending, scoring, moderation and a JSON API.
composer require atannex/matrix
Requirements: PHP 8.3+, Laravel 11 or 12. Pin ^1.0 now that there is a stable tag.
Where to look first. The release notes deliberately defer to the README, and they do not list publish tags, migration commands, config keys, or route registration. Read the README end-to-end before touching a model — this package touches many tables and you want to know the table footprint and config surface before your first migrate. Verify:
vendor:publish) or auto-loadedFirst use case. Instrument exactly one model — a Post — with the minimum viable slice: record a view, record a reaction from both a guest and a logged-in user, then read the counter back out. Do not enable the full feature set on day one. Once view + reaction round-trips correctly for both actor types, add comments, then rollups, then the API. Each layer depends on the write path being right, and debugging a half-configured matrix of twelve features is significantly harder than debugging one.
Write path / read path separation. This is the central design decision the package is built around, and your code should respect it. Writes go through the package to record events. Reads should hit the running counters the package maintains — never count() or sum() over an event table in a controller or Blade view. If you find yourself aggregating raw engagement rows to render a page, you have bypassed the feature that makes this package worth using.
Guest vs account is a policy decision, not a config default. Decide early, per feature, whether guests can view/react/comment/bookmark. Guests need a stable identity to deduplicate against, and that identity must be resolved in one place — a middleware, a resolver binding, or a single helper — not scattered across controllers. Inconsistent guest identity produces inflated view counts and duplicate reactions, and it is painful to unwind after launch.
Daily rollups are for scheduled work, not request-time. Trending and scoring should read from rollups that a scheduled command produces, not from live aggregation. Put rollup generation in a console command on the scheduler, and let trending/scoring queries stay cheap. If the package ships scoring, treat the score as a derived read model you can recompute — keep the inputs intact.
Wrap engagement writes in an action or service class. Controllers should call RecordReaction::handle($post, $user, 'like'), not the package API directly. This gives you one place to add rate limiting, authorization, bot filtering, and event dispatch, and it keeps the package's surface area from spreading into every controller.
Moderation is two separate concerns. A report is a user signal; hiding or removing content is a moderator action. Keep them distinct in your schema and your UI — do not let a report auto-hide content, and keep an audit trail of who actioned what. Threaded comments compound this: moderation needs to reason about subtrees, not just single rows.
Threaded comments need limits up front. Set a maximum depth and a default ordering before you have data. Retrofitting a depth cap onto a live tree requires data migration; retrofitting an ordering requires a UI change. Also decide soft-delete semantics early — a deleted parent with live children renders differently than a hard delete.
JSON API for the frontend, thin custom endpoints otherwise. If you have an SPA or a JS-heavy frontend, route engagement through the package's API rather than hand-rolling endpoints. For anything custom, keep your controller thin: authorize, validate, delegate to an action, return. Do not rebuild the package's endpoints next to its own.
Use events and listeners for side effects. Notifications on reaction, anti-spam on comment, search indexing on view milestones — these belong in listeners, not inline in the write path. It keeps the synchronous path fast and makes side effects removable.
Counters will drift. Running counters are fast and eventually inconsistent — a failed transaction, a race, a deleted parent, a backfill. Build a reconciliation command before you need it, and schedule it. This is the single most likely operational surprise.
Views and reading time are the noisiest signals. Crawlers, prefetches, refreshes, and tab-restores all inflate them. Filter bots at the write path and dedupe by actor within a time window. Reading time in particular is trivially gamed by a tab left open — cap the reported duration and decide whether to discard outliers rather than trusting the raw number.
Check the migration footprint on existing databases. A package covering twelve engagement features will create a substantial number of tables and indexes. Review the migrations before running them in production, especially on large tables where index creation locks. Test the full migrate on a production-sized copy, not just a fresh local DB.
Rate limit the write endpoints. Reactions, views, and comments are all abuse surfaces, and the package is unlikely to ship opinionated throttling for your traffic profile. Apply Laravel's throttle middleware to both your endpoints and the package's API routes.
Run config:cache after changing config. As with any Laravel package, config changes are invisible under cached config. If a setting appears to have no effect, clear and rebuild the cache before debugging further. Confirm the publish tag names from the README — guessing tags wastes time.
Do not double-instrument. If you already have analytics or an existing likes implementation, disable one before launch. Two systems recording the same interaction produce two different numbers and an argument about which is right.
Extension points to look for in the README: the model trait/interface (for scoping which models are engageable), package events (for side effects and audit), and scoring configuration (for custom weighting). These are where you customize without forking.
Pin and watch 1.x. First stable release means the API is now something the maintainer is committing to, but the surface is wide and v1.0.0 has no patch history yet. Pin ^1.0, read the changelog on each minor, and keep your integration behind the action classes described above so a package-level change is a one-file edit rather than a codebase sweep.
Testing. Engagement state is persistent and cumulative, which makes tests order-dependent if you are careless. Use RefreshDatabase, assert on counters rather than raw rows, and write explicit tests for the guest path — it is the path most likely to be silently broken and least likely to be covered.
How can I help you explore Laravel packages today?