turahe/laravel-likeable
Laravel Likeable adds likes and dislikes to any Eloquent model in minutes. Prepare your model, then use built-in methods, scopes, events, and console commands to manage reactions with clean, tested Laravel integration.
Architecture fit. The package is idiomatic modern Laravel: a service provider, a Likeable contract plus trait mixin, most logic pushed into a LikeableService, Eloquent models for Like and LikeCounter, and container bindings so Contracts\Like, Contracts\LikeCounter, and Contracts\LikeableService can be swapped for first-party implementations. That is the right shape for a reusable domain component — it means our application code can depend on the package's contracts rather than its concrete models, and it gives us a documented extension seam if we later need custom reaction types or counter behaviour. Storage is polymorphic (likeable_type/likeable_id), so one pair of tables serves every likeable model, and a denormalised counter table keeps likesCount reads cheap. Likes and dislikes are mutually exclusive by design — a deliberate product semantic, not an implementation detail.
Integration feasibility. High. composer require, publish migrations, migrate, then implements Likeable + use Likeable on each model. A competent Laravel developer reaches a working vertical slice in under a day. There is no front-end opinion, no view publishing, no config file sprawl — it fits API-first, Inertia, and Livewire stacks identically. The published migration means the schema lives in our repository, which is the single most important fact for exit risk: our data outlives the package.
Technical risk. The dominant risk is vendor risk, not technical risk. Nine stars and zero dependents means effectively no peer review, no community bug-finding, and no observable "who else runs this in production." The project's engineering hygiene is unusually good for that level of adoption — 79 tests, PHP 8.3/8.4/8.5 × Laravel 10–13 CI matrix, Larastan, Pint, Codecov, conventional commits, automated changelog and semver releases — but hygiene is not the same as production hardening under load. Two further flags: (1) the package is PHP 8.3+, which excludes Laravel 10 applications still on PHP 8.1/8.2 and may force a runtime upgrade first; (2) the API surface (LikeableService, LikeCounter, collectLikers, likesDiffDislikesCount, likeable:recount) mirrors cybercog/laravel-love closely, which suggests a fork or reimplementation of an abandoned predecessor. Confirm provenance and the license chain before adopting — if it is a revived fork, that is actually a positive (mature API, fresh maintenance) but it should be a conscious decision, not an accident. Secondary risks: counter drift between the likes table and the counter table, event listeners firing in-process and potentially aborting a like, and counter-row contention under viral load.
Key questions.
(likeable_type, likeable_id, user_id, type) and FKs with explicit cascade behaviour on liker/user deletion? Or will double-submits and orphaned likes be our problem?increment or read-modify-write?ModelWasLiked/ModelWasUnliked/ModelWasDisliked/ModelWasUndisliked dispatched after commit, and are listeners expected to be queued? What happens to the like if a listener throws?likeable:recount chunked and memory-bounded — safe to run against a production table while traffic is live?likesCount accessors behave when not eager-loaded — can we guarantee no N+1 in listing endpoints?Stack fit. Backend-only Eloquent concern; compatible with any Laravel 10–13 app on PHP 8.3+, MySQL/Postgres (SQLite is viable for tests but its coarse write locking makes counter contention behaviour non-representative — do not performance-test on SQLite). The likeable:recount command accepts morph-map aliases, so it composes with Relation::enforceMorphMap(), which we should be using anyway.
Migration path. (1) Spike on a throwaway branch against a copy of production schema. (2) Publish migrations with vendor:publish --provider="Turahe\Likeable\LikeableServiceProvider" --tag=migrations and amend them in our repo before running — add the unique index, add FKs with cascadeOnDelete or nullOnDelete per policy, and confirm index coverage for the whereLikedBy / orderByLikesCount scopes. Published migrations are ours forever; treat the vendor version as a starting draft. (3) Register morph aliases. (4) Apply the trait to exactly one low-stakes model first.
Compatibility. Gate on three constraints in CI: PHP >=8.3, Laravel range, and DB driver. Because the package releases frequently via automated version bumps, pin an exact version in composer.json and treat upgrades as reviewed changes, not silent patch bumps. Do not let Renovate auto-merge it.
Sequencing.
ReactionService / ReactsToContent interface in our domain layer that delegates to the package. All controllers, policies, and jobs call our interface; nothing outside that one file imports Turahe\Likeable\*.likeable:recount --type=like / --type=dislike nightly as a reconciliation job, with drift count as a metric.Maintenance. Low steady-state effort, high variance risk. Budget roughly 1–2 engineer-days per quarter for version review, changelog reading, and drift monitoring. Assign a named internal owner — with no dependents and a single maintainer, "the community will catch it" is not an answer. Watch the changelog for breaking changes hidden in minor bumps; automated conventional-commit releases can move faster than a small team's review cadence.
Support. Community-only, GitHub issues, no SLA. Internal escalation path must be "we own it," which is another argument for the abstraction layer: when upstream goes quiet, we patch locally against a pinned version rather than forking.
Scaling. Read path is excellent — denormalised counters make likesCount a cheap join, and orderByLikesCount is index-friendly. Write path is the concern: each reaction is a row insert plus a counter upsert, and the counter row becomes a hot tuple on popular content. Under virality this serialises, and on MySQL/Postgres the failure mode is lock waits and occasional deadlocks, not corruption. Mitigations: retry-with-backoff on deadlock, queue-and-debounce counter updates for high-traffic objects, or read-through counting from the likes table for top-N ranking pages where exactness matters. Also enforce eager-loading conventions (with('likesCounter'), withCount) in code review — the accessor-based API invites accidental N+1 on every listing endpoint.
Failure modes and mitigations.
likeable:recount + drift alert.Ramp-up. Negligible. The README is complete and example-driven; a mid-level Laravel developer is productive within half a day. The real onboarding content is the one-page convention doc we write: use like()/likeToggle() only via our ReactionService, always eager-load counters, never write to the likes tables directly, and how to run recount. Ship that with the first integration PR.
TPM recommendation. Adopt — but as a non-critical-path feature on low-stakes surfaces (comments, posts, articles), behind a first-party abstraction, with a pinned version, amended migrations, and a nightly reconciliation job. Do not put it on the critical path of ranking or moderation logic until it has survived a quarter in production and the provenance/maintainer questions are answered.
How can I help you explore Laravel packages today?