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

Voting Bundle Laravel Package

msalsas/voting-bundle

A Symfony bundle that adds a flexible voting system to your Doctrine entities. Let users upvote or downvote items anonymously or as registered voters, store votes via Doctrine, and display results with built-in Twig helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: msalsas/voting-bundle is a Symfony bundle, not a Laravel package. It ships Doctrine entities, a manager service, and Twig helpers tied to the Symfony service container and Doctrine ORM. You cannot composer require it into a Laravel app and expect it to boot. The realistic options, in order of effort:

  1. Treat the repo as a reference implementation and copy the pattern into Eloquent models.
  2. Use one of the Laravel-native equivalents (cybercog/laravel-love, rtconner/laravel-likeable, overtrue/laravel-like) if you just need likes/ratings.
  3. Only if you're actually on Symfony: install it directly.

If you are on Symfony, the first-30-minutes path is:

composer require msalsas/voting-bundle

Enable it in config/bundles.php, then look at these four things in the repo, in this order:

  • README.md — the canonical wiring example (entity extension, service injection, event subscriber).
  • src/Entity/ — the abstract Vote class you are expected to extend.
  • src/VotableItemInterface — the contract your "thing being voted on" must implement.
  • The VoteManager (or equivalent manager service) — this is the API you will actually call day to day.

First use case: a 1–5 star rating on an Article. You extend the bundle's Vote entity to add a ManyToOne to your User, implement VotableItemInterface on Article (getId(), average, vote count), map with Doctrine, generate a migration, and expose a single POST /articles/{id}/vote endpoint that calls the manager.

If you are on Laravel, the same first use case looks like: copy the Vote table shape (id, voter_type/voter_id polymorphic, votable_type/votable_id, value, timestamps), add a Votable trait with votes() / averageVote() / voteCount(), and skip the bundle entirely.

Implementation Patterns

Extend, then constrain. The bundle gives you a base Vote entity but leaves the relations to you. The pattern is always: extend the abstract class, add your voter relation (usually User) and your votableItem relation, then add a database unique index on (voter_id, votable_item_id). Do not rely on the bundle to stop double votes — it won't.

Wrap the manager, don't sprinkle it. Inject the manager service into a thin application service (ArticleRatingService) rather than calling it from controllers. This is the single biggest lever for keeping the bundle swappable:

class ArticleRatingService
{
    public function __construct(private VoteManager $votes) {}

    public function rate(User $user, Article $article, int $value): void
    {
        if ($value < 1 || $value > 5) {
            throw new \InvalidArgumentException('Rating must be 1–5');
        }
        $this->votes->vote($user, $article, $value);   // or upsert, depending on semantics
    }
}

Handle "re-vote" as an upsert, not an insert. Most rating UIs let a user change their mind. The bundle's default vote() typically inserts; guard it by querying the existing vote first and updating the value, or catch the unique-constraint violation and update. Decide explicitly whether re-voting is replace, toggle (upvote → neutral), or forbidden.

Two distinct shapes — pick one early. Upvote/downvote (value ∈ {-1, +1}, sum is the score) and star rating (value ∈ 1..5, mean is the average) are different products. The bundle supports both mechanically, but your aggregate queries, caching, and UI are different. Don't mix them on the same model.

Denormalize the aggregate for reads. averageVote() and numberOfVotes() are almost always a SELECT AVG/COUNT under the hood. On an index page of 50 articles that is 100 queries. Add votes_sum / votes_count columns to the votable model, update them in the same transaction as the vote (or via the bundle's event subscriber), and read from those.

Use the event hooks for side effects. The bundle fires a vote event. Subscribe to it for things that should not live in the vote path: granting badges, invalidating a cache key, pushing to a queue, writing an audit log. Keep the listener synchronous only if it's cheap; otherwise dispatch a job.

class VoteListener
{
    public function onVote(VoteEvent $event): void
    {
        Cache::forget("article:{$event->getVotableItem()->getId()}:rating");
    }
}

Rendering: the bundle's Twig extension (star rendering / percentage) is a convenience, not a feature you should depend on. In Laravel, a Blade partial that takes (average, count) and renders five icons is ten lines and gives you full control over half-stars and accessibility.

Integration tip for Laravel teams: define your own Votable interface and RatingService contract before writing any implementation. That way, if you later decide to adopt a native package or migrate off the copied pattern, only the concrete class changes.

Gotchas and Tips

  • Value validation is yours. The bundle stores whatever integer you hand it. Clamp to your range in a FormRequest / DTO before it reaches the manager, and add a DB CHECK or a tinyint with an application-level guard.
  • Unique constraint is mandatory. Without UNIQUE(voter, votable_item), a double-click produces two votes and a skewed average. Add it in the migration, then handle the collision in the service.
  • Cascade deletes are silent bugs. If an Article is deleted without ON DELETE CASCADE on the votes FK, you accumulate orphans and your AVG is computed over ghosts. Set the FK behaviour explicitly and add a cleanup test.
  • N+1 is the default. Aggregates are per-item queries. Either eager-load with a LEFT JOIN/withCount, or keep denormalized counters. Profile the index page before shipping.
  • No routes, controllers, or UI ship with this. It is a data-layer bundle. You own the endpoint, the authorization (Gate/voter — can this user rate this item?), throttling (vote spam), and the response shape.
  • Low-traffic package. 14 stars and 0 dependents means: no security response team, no Laravel bridge, and the API may be undocumented in places. Read src/ directly rather than trusting the README to be current. Pin the version and vendor-fork if you must depend on it.
  • Doctrine-only coupling. The entities are annotations/attributes-based Doctrine mappings. Porting to Eloquent means rewriting the model layer entirely — the value you get from the bundle in a Laravel app is the data model and manager API design, not code reuse.
  • Debugging votes: write a test that inserts N votes and asserts both AVG and COUNT. Most rating bugs are off-by-one in the aggregate or a stale denormalized counter, not the vote itself.
  • Extension points worth knowing: custom event subscribers (side effects), overriding the entity mapping to add fields (e.g., created_at, ip), and subclassing the manager if you need custom aggregation. Prefer subclassing over forking.
  • Migration insurance: keep the bundle behind your own RatingService interface from day one. When the package goes quiet (or when you move to Laravel), you swap the implementation and nothing else in the codebase notices.
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.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect