modularavel/favoritable
A Laravel package that adds polymorphic favoriting to Eloquent models. Attach the trait to let users favorite and unfavorite records, then query favorites through a simple, reusable API for common app features.
Architecture fit. modularavel/favoritable is a single-purpose Eloquent behavior package: it exposes a trait that gives a model a polymorphic relationship to "favorites" (and typically the inverse on the favoriting model, plus a Favorite model and published migration). Architecturally this is a low-coupling, additive concern. It does not impose a service provider contract on your domain layer, does not own routing or HTTP surface area, and does not require changes to existing tables. That makes it a good fit for teams already using trait-based Eloquent composition (SoftDeletes, Sluggable, etc.) and a poor fit only if your persistence layer is deliberately framework-agnostic (Doctrine, event-sourced aggregates), where an Eloquent-only trait would be an impedance mismatch.
Integration feasibility. Mechanically straightforward: composer require, publish the migration and config, run the migration, add the trait to the target model(s), and optionally register a morph map. Realistically a half-day for a first integration in a single-tenant app, one to three days to do it properly across multiple favoritable models with tests. The work is not in the install — it is in deciding the polymorphic alias strategy (class names vs. morph map keys) and the uniqueness constraint policy, both of which are effectively one-way doors once rows exist in production.
Technical risk. The dominant risk is immaturity, not complexity. v1.0.0 is the first stable tag, and the release notes carry no changelog detail — no list of breaking changes relative to 0.x, no stated minimum Laravel/PHP versions, no deprecation notices. Anyone who consumed a 0.x tag must treat this as potentially breaking and re-verify the trait signature, method names, config keys, and migration schema. Secondary risks: the migration is usually published and then owned by your app, so upstream schema fixes will not reach you automatically; polymorphic foreign keys behave differently across MySQL/Postgres/SQLite with respect to index usage and cascade semantics; and a single-maintainer package under a small vendor org carries bus-factor exposure. Treat v1.0.0 as "API intent declared, provenance unproven."
Key questions.
(favoritable_type, favoritable_id, user_id), and are the morph columns indexed in the correct order for our query patterns?forceDelete — are favorite rows cleaned up, or do we own orphan reaping?Stack fit. Native fit for a conventional Laravel monolith or modular monolith on Eloquent. It composes cleanly with the rest of the modularavel family if you already run those packages, since conventions around config publishing, migration naming, and trait naming will match. It is a poor fit for multi-database or read/write-split setups unless the package is connection-agnostic, and a poor fit for API-only services that intentionally avoid cross-domain joins on user identity.
Migration path. Because there is no prior assessment and no existing adoption, this is a greenfield decision rather than a migration — but treat it as a bounded spike, not a rollout:
withCount, with) against a seeded dataset in the tens of thousands of rows.If you are currently on a 0.x tag, do not upgrade in place. Diff the published migration and trait surface against 1.0.0 first, and stage the upgrade behind a branch with the full test suite green.
Compatibility. Verify explicitly, do not infer: Laravel version constraint, PHP version constraint, database driver support, and UUID vs. auto-increment key assumptions (a UUID-keyed User model breaks a migration with a unsignedBigInteger foreign key — this is the single most common integration failure for packages of this shape). Also confirm behavior under Model::preventLazyLoading() and strict mode, which many teams now enable.
Sequencing. Ship it as an isolated capability: (1) schema + config review, (2) single-model integration with tests, (3) observability on the favorite write path, (4) second-model rollout, (5) morph map + backfill if class-name aliases were used initially. Do not bundle this into a release that also carries user-facing feature work; the failure modes are data-shaped and deserve their own deploy window with a rollback that includes a table drop.
Maintenance. Low code volume, but you inherit the published migration as application-owned schema. Budget for occasional manual reconciliation when upstream changes the schema, and pin the version in composer.json with a caret constraint only after you have confirmed the maintainer's semver discipline. Add the package to your dependency-review list so v1.x minors get read rather than auto-merged.
Support. Single-maintainer, early-stage package. Assume no SLA, no security disclosure process you can point an auditor at, and no commercial backing. That is acceptable for a nice-to-have engagement feature; it is not acceptable if favorites become load-bearing for recommendations, ranking, or billing. Have an exit plan: the data model is simple enough that forking or inlining the ~200 lines of trait and migration is a realistic 1–2 day fallback, and you should say so in the design doc so no one treats the dependency as irreversible.
Scaling. The write path is a single insert — cheap. The read path is where it bites: counting favorites per item across a feed is a classic N+1 and, worse, a GROUP BY over a large polymorphic table with a type column that defeats some index plans. Mitigations: composite index ordered (favoritable_type, favoritable_id, created_at), a denormalized counter column on the favoritable side maintained transactionally or via queued job, and withCount scoped to the page of results. Above roughly 10^7 favorite rows, plan to partition or archive by created_at. Confirm the package does not do per-row existence checks on toggle, which turns a like button into a hotspot.
Failure modes.
ON DELETE CASCADE on a polymorphic FK is not enforceable in most engines; behavior is application-level and easy to lose.Ramp-up. Low for any Laravel-fluent engineer: the trait, the migration, and one config file are readable in an hour. The non-obvious knowledge is the polymorphic alias decision and the counter-cache strategy, both of which should be captured in an ADR at integration time. Estimate one engineer-day to first working integration, plus one to two days for tests, indexing, and the counter strategy — call it three days of engineering to reach production confidence, with no ongoing headcount beyond dependency review.
Recommendation. Approve for a bounded pilot on a single non-critical favoritable model. Gate broader adoption on answers to the version-matrix, unique-index, and delete-cleanup questions above; those three determine whether this is a three-day integration or a slow data-integrity problem.
How can I help you explore Laravel packages today?