Cached aggregates
Live aggregates query on demand. For busy listings, cache reviews_count and reviews_avg on the reviewable’s own table and let the package keep them in sync:
- Set reviews.cache_aggregates to true (REVIEWS_CACHE_AGGREGATES=true).
- Publish the reviews-aggregate-migrations stub, rename its placeholder table to your reviewable’s table, and migrate.
- Add the MaintainsReviewAggregates trait, alongside HasReviews, to that model.
php artisan vendor:publish --tag="reviews-aggregate-migrations"
php artisan migrate// database/migrations/…_add_review_aggregates_to_reviewable_table.php
Schema::table('products', function (Blueprint $table): void { // was 'reviewable_table'
$table->unsignedInteger('reviews_count')->default(0);
$table->decimal('reviews_avg', 8, 4)->nullable();
});use RoundlyConsulting\Reviews\Concerns\HasReviews;
use RoundlyConsulting\Reviews\Concerns\MaintainsReviewAggregates;
class Product extends Model
{
use HasReviews;
use MaintainsReviewAggregates;
}Publish the stub once per reviewable table — each publish produces a freshly timestamped migration.
How it stays in sync
An observer recomputes the columns whenever a top-level review is saved, deleted or restored — so creating, editing, approving, rejecting and deleting all update them. It uses the same approved, top-level semantics as the live aggregates, saves quietly (no model events on your subject), and ignores owner responses and subjects without the trait:
// Read and sort on plain columns — no aggregate query per row:
$top = Product::query()->orderByDesc('reviews_avg')->take(10)->get();
$product->reviews_count; // 320
$product->reviews_avg; // 4.6000
$product->recountReviews(); // recompute and save quietly on demandCustom column names
Override the two column methods to match an existing schema:
class Product extends Model
{
use HasReviews;
use MaintainsReviewAggregates;
public function aggregateCountColumn(): string
{
return 'rating_count';
}
public function aggregateAvgColumn(): string
{
return 'rating_average';
}
}Rebuilding
Seed existing rows after adding the columns, or rebuild them from scratch at any time. The command walks the model in chunks of 100 and fails if the class isn’t an Eloquent model or doesn’t use the trait:
php artisan reviews:recount "App\Models\Product"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 cryptoBy 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.