PHPackages                             ghanem/rating - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. ghanem/rating

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

ghanem/rating
=============

Rating system for Laravel

v2.1.1(2w ago)8715.6k361MITPHPPHP ^8.0CI passing

Since Oct 24Pushed 2w ago7 watchersCompare

[ Source](https://github.com/gaitco/rating)[ Packagist](https://packagist.org/packages/ghanem/rating)[ RSS](/packages/ghanem-rating/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (7)Dependencies (9)Versions (13)Used By (1)

 [![Laravel Rating](art/banner.png)](art/banner.png)

 [![Latest Stable Version](https://camo.githubusercontent.com/76802a6bb5a95db22a9b9603c3859fc409273d58883165b5de6f2a8881e8abea/68747470733a2f2f706f7365722e707567782e6f72672f6768616e656d2f726174696e672f762f737461626c652e737667)](https://packagist.org/packages/ghanem/rating) [![License](https://camo.githubusercontent.com/807ed4a7999ec6a6904d1083f706c5b0f4de3f9393917c688a5976e1cfc0f497/68747470733a2f2f706f7365722e707567782e6f72672f6768616e656d2f726174696e672f6c6963656e73652e737667)](https://packagist.org/packages/ghanem/rating) [![Total Downloads](https://camo.githubusercontent.com/04b55bc924778f62382358579142bff1d1ca6cc414eaf32ce2c97b3ae42ed082/68747470733a2f2f706f7365722e707567782e6f72672f6768616e656d2f726174696e672f646f776e6c6f6164732e737667)](https://packagist.org/packages/ghanem/rating)

Laravel Rating
==============

[](#laravel-rating)

Rating system for Laravel 8, 9, 10, 11, 12 &amp; 13.

Using [Filament](https://filamentphp.com)? See [**ghanem/rating-filament**](https://github.com/gaitco/rating-filament) — a star input field, a sortable average-rating table column, an infolist entry and a review moderation relation manager for Filament 4 &amp; 5.

Installation
------------

[](#installation)

```
composer require ghanem/rating
```

> **Upgrading from `V12.0`?** That tag was a mis-tag: it read as "Laravel 12 support" but registered on Packagist as major version **12**, so it outranked every 2.x release. It has been removed. If your `composer.json` says `"ghanem/rating": "^12.0"`, change it to `"^2.1"` and run `composer update ghanem/rating`. No code changes are needed — `v2.1.0` is the same line, plus Laravel 13 support and the migration publishing fixes.

The package uses Laravel's auto-discovery, so no need to manually register the service provider.

Getting started
---------------

[](#getting-started)

Publish and run the migration:

```
php artisan vendor:publish --provider="Ghanem\Rating\RatingServiceProvider"
php artisan migrate
```

Optionally publish the config file:

```
php artisan vendor:publish --tag=rating-config
```

Usage
-----

[](#usage)

### Setup a Model

[](#setup-a-model)

Add the `Ratingable` trait to any model you want to be ratable:

```
use Ghanem\Rating\Traits\Ratingable;

class Post extends Model
{
    use Ratingable;
}
```

> **`Ratingable` is a trait, not an interface.** Put it in `use` inside the class body — never in `implements`. `class Post extends Model implements Ratingable`fails with *"cannot implement Ratingable - it is not an interface"*.

Add the `CanRate` trait to the author model:

```
use Ghanem\Rating\Traits\CanRate;

class User extends Model
{
    use CanRate;
}
```

### Create a rating

[](#create-a-rating)

```
// From the ratable model
$rating = $post->rating(['rating' => 5], $user);

// From the author model
$rating = $user->rate($post, ['rating' => 5]);
```

### Create or update a unique rating

[](#create-or-update-a-unique-rating)

Only one rating per author per model:

```
$rating = $post->ratingUnique(['rating' => 5], $user);

// Or from the author
$rating = $user->rateUnique($post, ['rating' => 5]);
```

### Update a rating

[](#update-a-rating)

```
$rating = $post->updateRating($ratingId, ['rating' => 3]);
```

### Delete a rating

[](#delete-a-rating)

```
$post->deleteRating($ratingId);
```

### Rating with review body

[](#rating-with-review-body)

```
$post->rating([
    'rating' => 5,
    'body' => 'Great article!',
], $user);
```

### Scoped ratings (rate different aspects)

[](#scoped-ratings-rate-different-aspects)

```
$restaurant->rating(['rating' => 5, 'type' => 'food'], $user);
$restaurant->rating(['rating' => 3, 'type' => 'service'], $user);

$restaurant->avgRating('food');    // 5.0
$restaurant->avgRating('service'); // 3.0
$restaurant->avgRating();          // 4.0 (all types)
```

### Weighted ratings

[](#weighted-ratings)

```
$post->rating(['rating' => 5, 'weight' => 2], $verifiedUser);
$post->rating(['rating' => 3, 'weight' => 1], $regularUser);

$post->weightedAvgRating(); // 4.33
```

### Aggregates

[](#aggregates)

All aggregate methods accept an optional `$type` parameter for scoped ratings:

```
$post->avgRating()          // average rating
$post->sumRating()          // sum of all ratings
$post->countRatings()       // total count
$post->countPositive()      // count where rating > 0
$post->countNegative()      // count where rating < 0
$post->ratingPercent()      // percentage (default max: 5)
$post->ratingPercent(10)    // percentage with custom max
$post->weightedAvgRating()  // weighted average
```

All available as attributes too:

```
$post->avgRating
$post->sumRating
$post->countRatings
$post->countPositive
$post->countNegative
$post->ratingPercent
$post->weightedAvgRating
```

### Author queries (CanRate)

[](#author-queries-canrate)

```
$user->hasRated($post);          // bool
$user->getRating($post);         // Rating|null
$user->averageGivenRating();     // float
$user->totalGivenRatings();      // int
$user->ratings;                  // all ratings given
```

### Check if rated

[](#check-if-rated)

```
$post->isRatedBy($user);            // bool
$post->isRatedBy($user, 'food');    // bool (scoped)
```

### Query scopes

[](#query-scopes)

```
// Eager load rating aggregates
Post::withAvgRating()->get();
Post::withSumRating()->get();
Post::withCountRatings()->get();

// Order by ratings
Post::orderByAvgRating()->get();        // desc by default
Post::orderByAvgRating('asc')->get();
Post::orderBySumRating()->get();
Post::orderByCountRatings()->get();

// Filter by minimum rating
Post::minAvgRating(3.5)->get();
Post::minSumRating(10)->get();

// Scoped by type
Post::withAvgRating('food')->get();
Post::orderByAvgRating('desc', 'food')->get();
```

Displaying ratings in Blade
---------------------------

[](#displaying-ratings-in-blade)

The package is storage-only — it ships no views or assets, so you stay in control of your markup. Here is a complete 5-star setup with no JavaScript and no front-end dependencies.

### 1. Read-only stars

[](#1-read-only-stars)

`ratingPercent()` already returns the average as a percentage of the maximum, which is exactly what a CSS clip needs. Fractional averages (3.7 stars) render correctly with no extra work.

```
{{-- resources/views/components/stars.blade.php --}}
@props(['percent' => 0])

merge(['class' => 'stars']) }} style="--rating: {{ $percent }}%">
    ★★★★★

```

```
.stars {
    position: relative;
    display: inline-block;
    color: #d1d5db;
    letter-spacing: 2px;
    white-space: nowrap;
}

.stars::before {
    content: '★★★★★';
    position: absolute;
    top: 0;
    left: 0;
    width: var(--rating);
    overflow: hidden;
    color: #f59e0b;
    letter-spacing: 2px;
}
```

```

{{ number_format($post->avgRating(), 1) }} out of 5 ({{ $post->countRatings() }})
```

For a 10-point scale, pass the max: `$post->ratingPercent(10)`.

### 2. An interactive rating form

[](#2-an-interactive-rating-form)

Radio inputs in reverse order, so the CSS sibling selector can highlight the hovered star and every star before it. Accessible and keyboard-operable, because it is a real radio group.

```

    @csrf

        Your rating

        @foreach (range(5, 1) as $value)
            user()?->getRating($post)?->rating == $value ? 'checked' : '' }}
            >
            ★
        @endforeach

    Submit

```

```
.rating-input {
    display: inline-flex;
    flex-direction: row-reverse; /* lets `~` reach the stars to the left */
    border: 0;
}

.rating-input input {
    position: absolute;
    opacity: 0;      /* hidden from sight, still focusable */
}

.rating-input label {
    cursor: pointer;
    font-size: 1.75rem;
    color: #d1d5db;
}

.rating-input input:checked ~ label,
.rating-input label:hover,
.rating-input label:hover ~ label {
    color: #f59e0b;
}

.rating-input input:focus-visible + label {
    outline: 2px solid #2563eb;
}
```

### 3. Route and controller

[](#3-route-and-controller)

```
// routes/web.php
Route::post('posts/{post}/rate', [RatingController::class, 'store'])
    ->middleware('auth')
    ->name('posts.rate');
```

```
class RatingController extends Controller
{
    public function store(Request $request, Post $post)
    {
        $data = $request->validate([
            'rating' => ['required', 'integer', 'min:1', 'max:5'],
            'body' => ['nullable', 'string', 'max:2000'],
        ]);

        // rateUnique() updates the user's existing rating instead of adding a second one
        $request->user()->rateUnique($post, $data);

        return back()->with('status', 'Thanks for rating!');
    }
}
```

Validate in the request as well as configuring `config/rating.php`. The config bounds throw `InvalidRatingException`, which surfaces as a 500; request validation gives the user a normal field error instead.

### 4. Listing many rated models

[](#4-listing-many-rated-models)

Calling `$post->avgRating()` inside a loop runs one aggregate query **per row**. Load the aggregates with the query instead:

```
$posts = Post::withAvgRating()->withCountRatings()->paginate();
```

```
@foreach ($posts as $post)
    {{-- read the eager-loaded aliases, not the accessors --}}

    {{ $post->ratings_count }} ratings
@endforeach
```

`withAvgRating()` selects a `ratings_avg_rating` alias and `withCountRatings()` selects `ratings_count`. Both are plain columns on the result, so sorting and filtering happen in SQL — see [Query scopes](#query-scopes).

Validation
----------

[](#validation)

Configure rating bounds in `config/rating.php`:

```
return [
    'min' => 1,
    'max' => 5,
    'allow_negative' => false,
];
```

Invalid ratings throw `Ghanem\Rating\Exceptions\InvalidRatingException`.

Events
------

[](#events)

The package fires events on rating lifecycle:

- `Ghanem\Rating\Events\RatingCreated`
- `Ghanem\Rating\Events\RatingUpdated`
- `Ghanem\Rating\Events\RatingDeleted`

Each event has a public `$rating` property with the Rating model.

Filament admin panel
--------------------

[](#filament-admin-panel)

[**ghanem/rating-filament**](https://github.com/gaitco/rating-filament)([Packagist](https://packagist.org/packages/ghanem/rating-filament)) adds Filament 4 &amp; 5 components on top of this package:

```
composer require ghanem/rating-filament
```

ComponentPurpose`RatingInput`Clickable star picker for forms, with validation bounds read from `config/rating.php``RatingColumn`Sortable average-rating column, backed by `withAvgRating()` so it does not N+1`RatingEntry`Read-only stars for infolists`RatingsRelationManager`Moderate the ratings and reviews a record receivedRelated packages
----------------

[](#related-packages)

- [ghanem/rating-filament](https://github.com/gaitco/rating-filament) — Filament 4 &amp; 5 admin components for this package
- [ghanem/friendship](https://github.com/gaitco/friendship) — friendships, requests and blocks for Eloquent models
- [ghanem/friendship-filament](https://github.com/gaitco/friendship-filament) — Filament admin panel for `ghanem/friendship`

Testing
-------

[](#testing)

```
composer test
```

Credits
-------

[](#credits)

This package began life in 2015 as an MIT-licensed package by **DraperStudio / PackageBackup**, whose original repository is no longer published. It has been maintained by [GAIT](https://gaitco.com) ever since, across Laravel 5 through 13. The original copyright notice is retained in [LICENSE](LICENSE).

Sponsor
-------

[](#sponsor)

[Become a Sponsor](https://github.com/sponsors/AbdullahGhanem)

###  Health Score

62

—

FairBetter than 99% of packages

Maintenance97

Actively maintained with recent releases

Popularity41

Moderate usage in the ecosystem

Community22

Small or concentrated contributor base

Maturity75

Established project with proven stability

 Bus Factor1

Top contributor holds 85.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~437 days

Recently: every ~846 days

Total

10

Last Release

17d ago

Major Versions

1.4 → V2.012020-12-15

V2.01 → V12.02026-03-08

PHP version history (3 changes)1.0PHP &gt;=5.5.9

V2.01PHP ^7.4|^8.0

V12.0PHP ^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/fafeedb68c6ad2ddbb249caed67289c8c74073d25fcfb210534a9769d393dda4?d=identicon)[ghanem](/maintainers/ghanem)

---

Top Contributors

[![AbdullahGhanem](https://avatars.githubusercontent.com/u/5055892?v=4)](https://github.com/AbdullahGhanem "AbdullahGhanem (23 commits)")[![cancerimex](https://avatars.githubusercontent.com/u/3083234?v=4)](https://github.com/cancerimex "cancerimex (2 commits)")[![davericher](https://avatars.githubusercontent.com/u/6945951?v=4)](https://github.com/davericher "davericher (1 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

gaitlaravellaravelratingratingrating-starslaravelreviewstarsRatingRatable

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ghanem-rating/health.svg)

```
[![Health](https://phpackages.com/badges/ghanem-rating/health.svg)](https://phpackages.com/packages/ghanem-rating)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

265.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
