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

Comments Laravel Package

anil/comments

Anil/Comments is a Laravel package for adding threaded comments to any Eloquent model. It provides polymorphic comment associations, nested replies, moderation support, and a straightforward API for quickly integrating comment functionality into your application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish. The package is distributed as a Laravel package, so the entry point is standard:

composer require anil/comments

Laravel auto-discovery should register the service provider. Then publish what you need (config, migrations, and in v2 also likely lang files and admin views/assets — verify the exact tags against the package's README and provider publishes() method, since v2.0.0 ships new surfaces):

php artisan vendor:publish --provider="Anil\Comments\CommentsServiceProvider"
php artisan migrate

Look here first, in this order:

  1. config/comments.php (published) — this is where table names, model overrides, moderation defaults, and (in v2) route/middleware/locale toggles will live. Read it before writing any code; it tells you what the package expects to own.
  2. The migrations — confirm column names, indexes on commentable_type/commentable_id/parent_id, and whether soft-delete and moderation columns exist. This is your contract.
  3. The trait — find the trait name (something like HasComments) that you add to your models. That trait is the entire public Eloquent API surface for attachments.
  4. The admin routes file — if v2 auto-registers routes, php artisan route:list after install is your fastest way to see exactly what got mounted and behind what middleware.

First use case — the one to build on day one. Attach the trait to a single model and prove the round trip before touching anything else:

use Anil\Comments\Traits\HasComments;

class Post extends Model
{
    use HasComments;
}
$post->comments()->create([
    'body' => 'First comment',
    'user_id' => auth()->id(),
]);

Then immediately verify three things, because they determine everything downstream: (a) $post->comments returns the rows you expect, (b) a nested reply works via whatever the parent column/relationship is, and (c) a freshly created comment is not visible in a "published" query if moderation defaults to pending. If (c) surprises you, you just found the most common integration bug before writing a controller.

Do this on day one too: stand up the polymorphic core behind a tiny wrapper service (CommentService or a repository interface) even for the spike. It's a commodity dependency with a single maintainer — the wrapper is what lets you swap it later without touching call sites.

Implementation Patterns

Pattern 1 — Polymorphic attachment as a platform primitive. The value of the package is that one trait unlocks comments on any model. The pattern that pays off is defining a single controller/route for comments keyed by a polymorphic reference, rather than a controller per domain:

Route::post('/{commentableType}/{commentableId}/comments', [CommentController::class, 'store']);

Resolve the model through a morph map (Relation::enforceMorphMap([...])) so you never persist raw FQCNs. This is the one decision that makes unified search, moderation dashboards, and analytics cheap — and it's hard to retrofit after you have data.

Pattern 2 — Threading. Nested replies are almost always a self-referential parent_id on the comments table plus a replies()/children() relationship. Day-to-day you should:

  • Load a thread with eager loading to avoid recursion N+1s: $post->comments()->with('replies.replies')->whereNull('parent_id')->get();
  • Enforce a max depth in your validation layer (the package generally won't stop unbounded nesting), and reject replies whose parent belongs to a different commentable — a cross-thread parent is a real authorization bug if you only check IDs.
  • Consider a materialized path or depth column if you render deep threads; per-level loading gets expensive fast.

Pattern 3 — Moderation workflow. Treat moderation state as a first-class query concern, not a boolean you filter ad hoc. Wrap it:

// Approved-only for public reads, everything for admins/moderators
$post->comments()->where('status', 'approved')->get();

Standard states are pending/approved/rejected (plus soft-deleted). Build the write path so every comment starts in a deterministic state (default pending, or approved only if the author is trusted), and centralize the transition (approve(), reject()) so you don't scatter status strings. If v2 ships an admin surface, wire your own guard (Auth::guard('admin') or a can('moderate-comments') gate) in front of it rather than relying on package defaults — see Gotchas.

Pattern 4 — Admin surface consumption. v2.0.0's admin support likely means publishable views, package controllers, and package routes. Two viable integration styles:

  • Adopt it wholesale: publish views, keep package routes, mount them under your own prefix + your own auth/role middleware via config. Fastest, but you inherit its layout assumptions and get copy changes on upgrade.
  • Own the presentation, borrow the model (recommended for anything customer-facing or branded): disable/override the package routes, build your own thin controller that queries the same models, and only use the package for data + relationships. This keeps you off the package's rendering path, which is where XSS and localization-scope risk live.

Pattern 5 — Localization. v2.0.0 adds translations. Use the standard Laravel flow: publish lang/vendor/comments/*.php, keep overrides in your app's lang/ so upgrades don't clobber them, and if your UI renders package strings, ensure __() receives the package namespace correctly. For multi-market launch, add your locales under the vendor namespace path — that's the "configuration not code" win, but only if the strings are overridable, which you should verify by actually publishing and editing one.

Pattern 6 — Authorization at the boundary. Commenting is a permissions problem disguised as a CRUD problem. Put policies on both axes:

  • Who can comment on this record — CommentPolicy::create checking the parent model's visibility (a user who can't see a private Document can't comment on it).
  • Who can moderate/delete — separate gate, separate middleware; don't overload the admin route with a generic auth check.

Pattern 7 — Events / notifications. If the package dispatches events (verify — likely CommentCreated, CommentApproved), subscribe to them for notifications and audit logging rather than hooking your own writes. If it doesn't, wrap the create/approve calls in your service so you have a single seam to emit your own.

Gotchas and Tips

The v2.0.0 bump is the headline risk. Semver says major = breaking, but the changelog lists three PRs (two StyleCI, one feature) with no migration guide and no breaking-change list. Treat every v1 spike artifact as stale, pin the exact version ("anil/comments": "2.0.0" or a caret on 2.x you've tested), and read the migrations against your v1 schema before upgrading. You cannot distinguish "breaking changes that don't affect us" from "silent query behavior change" from the notes alone.

Auto-registered admin routes are a live attack surface. The moment the provider boots, v2 may register routes/controllers/middleware. After install, run php artisan route:list | grep -i comment and confirm: what prefix, which middleware group, and whether it can be disabled by config. If it ships reachable and you don't use it, disable it at boot or gate it. Do not assume "we didn't build a UI for it" means it isn't reachable.

XSS. Comments are user-authored HTML. If the package (or its admin views) renders body raw, you have stored XSS. Escape on output by default; only render rich text through a sanitizer (e.g., an HTML purifier) if you actually need markup, and sanitize on render, not only on store.

N+1 and thread rendering. The polymorphic relationship plus nested replies is the classic performance trap. Always eager-load; count queries on a 50-comment thread during the spike. If write volume is high, plan denormalized counters (comments_count) and caching now — per-comment queries and count() on every page will not hold.

Morph map drift and orphan cleanup. Raw FQCNs in commentable_type break when you rename/refactor models. Enforce a morph map. Soft-deleting a parent leaves comments orphaned; decide up front (cascade soft-delete, or filter on parent existence) and test it, because the package generally won't clean up after you.

Migration collisions. If you already have a comments table (or a differently named one), the published migration will collide. Configure the table name in config/comments.php before migrating, and check whether models are configurable so you can point at your own subclass without forking.

"Extensive testing" is a claim, not a fact. The v2.0.0 release notes assert it via a PR title. Verify it yourself: is there CI, does the matrix cover MySQL and Postgres and SQLite, and are authorization/moderation branches exercised (not just happy-path CRUD)? Gaps here are your red flag, not a footnote — you're likely among the first production users.

Localization scope creep. Once strings ship in the package, a routine composer update can change user-visible copy. Pin the version and, where copy is customer-facing, override via published lang files rather than editing vendor strings.

You are the de facto maintainer. Six stars, zero dependents, one maintainer. Budget for owning a fork: keep customizations in your own layer (service, policy, views, lang) and patch the package last. Do not let package internals leak into domain code.

Extension points to check in the spike (in priority order): config model overrides, publishable views, whether admin routes/middleware are toggleable, whether strings are overridable, and whether events are dispatched. If any of these are missing, you're forking sooner than you planned — factor that into the estimate.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource