NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Reviews for Laravel

Helpful votes

Any model can vote a review helpful or unhelpful. There is one vote per voter per review — voting again flips it:

use RoundlyConsulting\Reviews\Facades\Reviews;

Reviews::vote($review, $user, true);    // helpful
Reviews::vote($review, $user, false);   // flips the same voter to unhelpful
Reviews::removeVote($review, $user);    // withdraw — bool, false when there was no vote

$review->helpfulScore();                // helpful_count - unhelpful_count

The voter trait

Add CanVoteOnReviews to a voter model for ergonomic helpers:

use RoundlyConsulting\Reviews\Concerns\CanVoteOnReviews;

class User extends Model { use CanVoteOnReviews; }

$user->voteOn($review);                 // helpful by default
$user->voteOn($review, false);          // unhelpful
$user->hasVotedOn($review);             // bool
$user->votedHelpfulOn($review);         // bool
$user->removeVoteFrom($review);         // bool, like removeVote()

Tallies & votes

Each vote change recounts the review’s helpful_count and unhelpful_count from the votes table, so sorting by helpfulness needs no join. The change and its recount run as one transaction under the review’s row lock, so concurrent votes never leave a stale count; the recount writes those two columns only — it doesn’t bump the review’s updated_at or save other unsaved changes on the model you pass:

$review->helpful_count;       // int, denormalized
$review->unhelpful_count;     // int, denormalized
$review->votes;               // HasMany<ReviewVote>

$vote = $review->votes->first();
$vote->helpful;               // bool
$vote->voter;                 // MorphTo — the voting model
$vote->review;                // BelongsTo — the review
  • Review::query()->mostHelpful() orders by net score, newest first on ties.
  • Casting or flipping a vote fires ReviewVoted with the vote; withdrawing fires ReviewVoteRemoved with the voter — only when a vote existed.
  • removeVote() is idempotent and returns false when there was no vote.
  • Votes cascade away when their review is force-deleted.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.