Skip to main content
Reviews allow customers to rate and review products. Shopper provides a flexible review system with approval workflows, rating calculations, and polymorphic relationships.

Model

Shopper\Core\Models\Review
use Shopper\Core\Models\Review;

// The model implements
- Shopper\Core\Models\Contracts\Review

Database Schema

ColumnTypeNullableDefaultDescription
idbigintnoautoPrimary key
ratingintegerno-Rating value (typically 1-5)
titletextyesnullReview title
contenttextyesnullReview content
is_recommendedbooleannofalseCustomer recommends product
approvedbooleannofalseReview approval status
reviewrateable_idbigintno-Polymorphic relation ID (product)
reviewrateable_typestringno-Polymorphic relation type
author_idbigintno-Polymorphic relation ID (user)
author_typestringno-Polymorphic relation type
created_attimestampyesnullCreation timestamp
updated_attimestampyesnullLast update timestamp

Relationships

Reviewrateable (Product)

// Get the reviewed entity (usually a product)
$review->reviewrateable; // Product model

Author (User)

// Get the review author
$review->author; // User model

HasReviews Contract

Products implement the HasReviews contract via the InteractsWithReviews trait:
use Shopper\Core\Contracts\HasReviews;
use Shopper\Core\Models\Traits\InteractsWithReviews;

class Product extends Model implements HasReviews
{
    use InteractsWithReviews;
}

Rating Methods

Getting Ratings

// Get all ratings for a product
$product->ratings; // Collection of Review models

// Get all ratings with sorting
$product->getAllRatings($product->id, 'desc');

// Get only approved ratings
$product->getApprovedRatings($product->id);

// Get pending (not approved) ratings
$product->getNotApprovedRatings($product->id);

// Get recent ratings
$product->getRecentRatings($product->id, 5);

// Get recent ratings by user
$product->getRecentUserRatings($userId, 5, approved: true);

Rating Counts

// Count all ratings
$product->countRating(); // Returns integer

// Get rating sum
$product->sumRating(); // Collection with sum

Average Ratings

// Get average rating
$product->averageRating(); // Collection with average

// Get average rating rounded to 1 decimal
$product->averageRating(round: 1); // e.g., 4.5

// Get average rating of approved reviews only
$product->averageRating(round: 1, onlyApproved: true);

Rating Percentage

// Get percentage of 5-star ratings
$product->ratingPercent(5); // e.g., 85.5

// Useful for displaying rating distribution
$distribution = [
    5 => $product->ratingPercent(5),
    4 => $product->ratingPercent(4),
    3 => $product->ratingPercent(3),
    2 => $product->ratingPercent(2),
    1 => $product->ratingPercent(1),
];

Creating Reviews

Via Product Model

use Shopper\Core\Models\Product;

$product = Product::query()->find($id);

// Create a review
$review = $product->rating([
    'rating' => 5,
    'title' => 'Excellent product!',
    'content' => 'This product exceeded my expectations.',
    'is_recommended' => true,
    'approved' => false, // Pending approval
], $user);

Via Review Model

use Shopper\Core\Models\Review;

$review = new Review;
$review = $review->createRating($product, [
    'rating' => 4,
    'title' => 'Good quality',
    'content' => 'Happy with my purchase.',
    'is_recommended' => true,
], $user);

Direct Creation

$review = Review::query()->create([
    'rating' => 5,
    'title' => 'Love it!',
    'content' => 'Best purchase I made.',
    'is_recommended' => true,
    'approved' => false,
    'reviewrateable_id' => $product->id,
    'reviewrateable_type' => get_class($product),
    'author_id' => $user->id,
    'author_type' => get_class($user),
]);

Updating Reviews

// Via product model
$product->updateRating($reviewId, [
    'rating' => 4,
    'content' => 'Updated review content.',
]);

// Via review model
$review = new Review;
$review->updateRating($reviewId, [
    'title' => 'Updated title',
    'content' => 'Updated content',
]);

Approval Management

// Approve a review
$review->updatedApproved(true);

// Unapprove a review
$review->updatedApproved(false);

// Direct update
$review->update(['approved' => true]);

Deleting Reviews

// Via product model
$product->deleteRating($reviewId);

// Via review model
$review = new Review;
$review->deleteRating($reviewId);

// Direct delete
Review::query()->find($reviewId)->delete();

Retrieving Reviews

use Shopper\Core\Models\Review;

// Get all reviews
$reviews = Review::query()
    ->with(['author', 'reviewrateable'])
    ->latest()
    ->get();

// Get pending reviews
$pending = Review::query()
    ->where('approved', false)
    ->with('author')
    ->get();

// Get approved reviews for a product
$productReviews = Review::query()
    ->where('reviewrateable_id', $productId)
    ->where('reviewrateable_type', Product::class)
    ->where('approved', true)
    ->with('author')
    ->latest()
    ->paginate(10);

// Get reviews by user
$userReviews = Review::query()
    ->where('author_id', $userId)
    ->where('author_type', User::class)
    ->with('reviewrateable')
    ->get();

Components

php artisan shopper:component:publish review
Creates config/shopper/components/review.php:
use Shopper\Livewire;

return [
    'pages' => [
        'review-index' => Livewire\Pages\Reviews\Index::class,
    ],
    'components' => [
        'slide-overs.review-detail' => Livewire\SlideOvers\ReviewDetail::class,
    ],
];

Storefront Example

Review Display

namespace App\Http\Controllers;

use Shopper\Core\Models\Product;

class ProductController extends Controller
{
    public function show(string $slug)
    {
        $product = Product::query()
            ->where('slug', $slug)
            ->publish()
            ->firstOrFail();

        // Get approved reviews
        $reviews = $product->ratings()
            ->where('approved', true)
            ->with('author')
            ->latest()
            ->paginate(10);

        // Get rating stats
        $averageRating = $product->averageRating(1, onlyApproved: true)->first();
        $reviewCount = $product->ratings()->where('approved', true)->count();

        // Rating distribution
        $distribution = [];
        for ($i = 5; $i >= 1; $i--) {
            $count = $product->ratings()
                ->where('approved', true)
                ->where('rating', $i)
                ->count();
            $distribution[$i] = [
                'count' => $count,
                'percentage' => $reviewCount > 0 ? ($count / $reviewCount) * 100 : 0,
            ];
        }

        return view('products.show', compact(
            'product',
            'reviews',
            'averageRating',
            'reviewCount',
            'distribution'
        ));
    }
}

Review Submission

namespace App\Http\Controllers;

use Shopper\Core\Models\Product;

class ReviewController extends Controller
{
    public function store(Request $request, Product $product)
    {
        $validated = $request->validate([
            'rating' => 'required|integer|min:1|max:5',
            'title' => 'nullable|string|max:255',
            'content' => 'nullable|string|max:2000',
            'is_recommended' => 'boolean',
        ]);

        // Check if user already reviewed this product
        $existingReview = $product->ratings()
            ->where('author_id', auth()->id())
            ->where('author_type', get_class(auth()->user()))
            ->exists();

        if ($existingReview) {
            return back()->with('error', 'You have already reviewed this product.');
        }

        // Create review (pending approval)
        $product->rating([
            ...$validated,
            'approved' => false,
        ], auth()->user());

        return back()->with('success', 'Thank you! Your review is pending approval.');
    }
}

Review Management (Admin)

namespace App\Http\Controllers\Admin;

use Shopper\Core\Models\Review;

class ReviewController extends Controller
{
    public function index()
    {
        $reviews = Review::query()
            ->with(['author', 'reviewrateable'])
            ->latest()
            ->paginate(20);

        return view('admin.reviews.index', compact('reviews'));
    }

    public function approve(Review $review)
    {
        $review->updatedApproved(true);

        return back()->with('success', 'Review approved.');
    }

    public function reject(Review $review)
    {
        $review->delete();

        return back()->with('success', 'Review deleted.');
    }
}

Blade Template Example

{{-- Product rating summary --}}
<div class="rating-summary">
    <div class="average">
        <span class="score">{{ $averageRating ?? 0 }}</span>
        <span class="max">/5</span>
    </div>
    <div class="stars">
        @for ($i = 1; $i <= 5; $i++)
            <svg class="{{ $i <= round($averageRating) ? 'text-yellow-400' : 'text-gray-300' }}">
                <!-- Star icon -->
            </svg>
        @endfor
    </div>
    <p>Based on {{ $reviewCount }} reviews</p>
</div>

{{-- Rating distribution --}}
<div class="distribution">
    @foreach ($distribution as $rating => $data)
        <div class="row">
            <span>{{ $rating }} stars</span>
            <div class="bar" style="width: {{ $data['percentage'] }}%"></div>
            <span>{{ $data['count'] }}</span>
        </div>
    @endforeach
</div>

{{-- Reviews list --}}
@foreach ($reviews as $review)
    <div class="review">
        <div class="header">
            <span class="author">{{ $review->author->full_name }}</span>
            <span class="rating">{{ $review->rating }}/5</span>
            <span class="date">{{ $review->created_at->diffForHumans() }}</span>
        </div>
        @if ($review->title)
            <h4>{{ $review->title }}</h4>
        @endif
        <p>{{ $review->content }}</p>
        @if ($review->is_recommended)
            <span class="recommended">✓ Recommends this product</span>
        @endif
    </div>
@endforeach

Use Cases

MethodUse Case
averageRating()Display product rating stars
countRating()Show total review count
ratingPercent()Build rating distribution chart
getApprovedRatings()Display reviews on product page
getNotApprovedRatings()Admin review moderation queue
getRecentUserRatings()Customer review history