mahefa/comment-bundle
mahefa/comment-bundle is a PHP package for adding comment functionality to your application. It provides core building blocks for storing, retrieving, and managing comments, making it easier to integrate discussion features into Laravel or PHP projects.
This is a fork of the long-abandoned friendsofsymfony/comment-bundle — a Symfony bundle, not a Laravel package. Before you type composer require, understand what you're actually adopting: a Symfony DI/HttpKernel/Form/Doctrine stack with a 2022 release date, 0 stars, 0 dependents, and an upstream that was archived years ago. For a Laravel shop the realistic starting points are:
If you are in a Symfony codebase, the minimal path is:
composer require mahefa/comment-bundle
Then register it (Flex-era Symfony uses config/bundles.php, older uses AppKernel), import its routing, and — critically — subclass its mapped superclasses rather than using them directly:
// src/Entity/Comment.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\CommentBundle\Entity\Comment as BaseComment;
/**
* @ORM\Entity
* @ORM\Table(name="comment")
*/
class Comment extends BaseComment
{
}
Do the same for Thread, then point the bundle at your classes:
# config/packages/fos_comment.yml
fos_comment:
db_driver: orm
class:
model:
comment: App\Entity\Comment
thread: App\Entity\Thread
Where to look first, in this order:
Model/ — CommentInterface, ThreadInterface, CommentManagerInterface, ThreadManagerInterface. This is the contract surface you'll actually code against.Entity/BaseComment.php and Entity/BaseThread.php — the persisted shape, including the tree columns (parent, children, level, treeLeft/treeRight).Controller/ — ThreadController and CommentController; these define the HTTP contract (list thread, create comment, edit, delete, vote).Resources/config/ — service ids (fos_comment.manager.comment, fos_comment.manager.thread), the ACL voters, and the routing definitions.Form/ — CommentType, ThreadType; useful if you're replicating validation rules.First use case — a comment thread on a blog post: create a Thread keyed by an identifier like post:42, list it via the thread route, and post replies through the comment-create route. Everything else (moderation, voting, spam) is layered on top of that.
Thread-per-resource via string identifier. FOSCommentBundle does not use a polymorphic foreign key like Laravel's morphTo. Instead a Thread carries a unique string identifier (plus an optional permalink), and the manager resolves it:
$thread = $threadManager->findThreadById('post:42')
?? $threadManager->createThread('post:42');
Adopt this only if you like it; a Laravel dev will find $post->commentThread() more natural. The trap is that identifier has a unique index — namespace it (post:42, page:7) or you'll get collisions the first time you reuse raw IDs across entity types.
Manager-as-service. All persistence goes through CommentManagerInterface, never through the repository directly. This is the bundle's best idea and the one worth stealing: it gives you a single seam for wrapping transactions, firing events, and swapping storage drivers. In Laravel terms, bind a CommentManager in a service provider and keep controllers/model-free.
Extend, don't edit. Every customization path is inheritance or service replacement: subclass BaseComment/BaseThread, override the manager service id in your container, implement SpamDetectionInterface, or drop templates into templates/bundles/FOSCommentBundle/. Treat the vendor directory as read-only.
State machine, not booleans. CommentInterface exposes STATE_VALID, STATE_PENDING, STATE_SPAM, STATE_DELETED. Moderation is a transition between those, and the listing query filters on state. If you're porting to Laravel, copy this enum verbatim — it's better than the is_approved boolean most homegrown implementations reach for.
Event-driven side effects. The bundle emits fos_comment.comment.created, fos_comment.comment.persist, fos_comment.thread.created and friends. Notifications, webhooks and anti-spam enrichment belong in subscribers, not in the controller or the entity. In Laravel this maps cleanly to model observers or domain events dispatched from the manager.
ACL as voters, configured explicitly. Permissions (canCreateCommentOnThread, canEditComment, canDeleteComment, canVoteOnComment) are separate voters under fos_comment.acl.*. Configure them in fos_comment.yml; the defaults are permissive and are the single most common way people ship an open comment form to production.
Voting is pluggable. VoteInterface plus a score denormalized onto the comment. There is no batching — every upvote is an UPDATE on the comment row plus a tree-adjacent write. At scale you'll want to move the score aggregation out to a queue.
Integration tip for a hybrid stack. If you're running the bundle inside a Symfony service and consuming it from Laravel, expose the thread/comment controllers as JSON (they already return a Response you can shape) and let Laravel own users, auth and notifications. Pass a signed user identifier — the bundle's ACL layer will trust whatever you hand it.
If you're actually porting to Laravel, the equivalent stack is kalnoy/laravel-nestedset (or a plain parent_id + recursive CTE) on a comments table with commentable_type/commentable_id, plus a /comments resource controller. Take the state machine, the manager abstraction and the event names; leave the tree columns behind.
bundle concept in Laravel and no service container bridge. composer require inside a Laravel app will pull symfony/http-kernel, symfony/form, symfony/security-core, doctrine/doctrine-bundle and expect a Kernel. You will be maintaining two frameworks to get threaded comments.FOSCommentBundle has been unmaintained since the Symfony 3 era. Assume no security patches, no PHP 8.3+ fixes, and no Doctrine ORM 3 support.doctrine/orm:^2.x or you'll be rewriting mappings before you write a single comment.treeLeft/treeRight/level/childCount are materialized and recomputed on insert. Concurrent replies to the same thread contend on the same rows — expect deadlocks under load. Serialize writes per thread (advisory lock on the root, or a queue with a single worker per thread), and add a rebuild command for when the counters drift.STATE_DELETED leaves the tree intact, so childCount and level-based rendering drift from what users see. Decide early whether deletes are tombstones or real row removals.fos_comment.acl and written a test that an anonymous user cannot create a comment. This is the most common production incident with this family of bundles.SpamDetectionInterface decorator.bin/console debug:router | grep fos_comment before assuming which one your front-end hit. CSRF is enforced on the create endpoints and will silently 400 an AJAX front-end that doesn't send a token.replace, stale branch-alias, or a symfony/* constraint that resolves to an ancient major. Run composer why-not mahefa/comment-bundle and read the requirements before committing.bin/console debug:container fos_comment, debug:event-dispatcher | grep comment, debug:router. Most "the comment didn't save" reports trace back to a mis-aliased model class parameter, not a logic bug.fos_comment.manager.comment with your own CommentManagerInterface implementation; decorate SpamDetectionInterface; add a VoteInterface implementation; subscribe to the fos_comment.* events; override templates under templates/bundles/FOSCommentBundle/. None of these require touching vendor code.Model/ and Entity/ for two hours, copy the state machine and the manager seam, and build a 200-line Laravel-native version. That will outlive this fork.How can I help you explore Laravel packages today?