xetaio / xetaravel-counts
Automatic *_count for Laravel belongsTo and belongsToMany relations.
Installs: 197
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/xetaio/xetaravel-counts
Requires
- php: ^8.2
- illuminate/database: ^12.0
- illuminate/support: ^12.0
Requires (Dev)
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
README
Unit Tests Stable Version Downloads Laravel License
A lightweight Laravel package that automatically maintains *_count columns
for:
belongsTorelations (ex: Category → Article)belongsToManyrelations via pivot models (ex: Material ↔ Part)
This allows models to keep real-time counters in the database without writing custom observers or manual logic.
Perfect for dashboards, ERPs, statistics, inventory systems, and any domain where counts must remain immediately available and consistent.
✨ Features
- 🔹 Automatic increment/decrement on create/delete
- 🔹 Automatic sync when foreign key changes (update)
- 🔹 Automatic increment on restore (SoftDelete only)
- 🔹 Automatic increment/decrement on attach/detach/sync (pivot)
- 🔹 Zero configuration for Laravel service provider (auto-discovery)
- 🔹 Simple traits you can reuse anywhere
- 🔹 Works on Laravel 12+
📦 Installation
Install via Composer:
composer require xetaio/xetaravel-counts
📚 Usage
This package provides a trait to use in your Models:
HasCounts — belongsTo & belongsToMany relations
1️⃣ Example for belongsTo relations :
Used when a child belongs to a parent, and the parent stores a *_count.
You have:
-
categories.articles_count
-
articles.category_id
Category model
use Illuminate\Database\Eloquent\Model; class Category extends Model { protected $fillable = ['name', 'articles_count']; public function articles() { return $this->hasMany(Article::class); } }
Article model (child)
use Illuminate\Database\Eloquent\Model; use Xetaio\Counts\Concerns\HasCounts; class Article extends Model { use HasCounts; protected $fillable = ['title', 'category_id']; protected static array $countsConfig = [ 'category' => 'articles_count', ]; public function category() { return $this->belongsTo(Category::class); } }
| Action | Effect |
|---|---|
| Article created | category.articles_count++ |
| Article deleted | category.articles_count-- |
| Article restored (SofDeletes only) | category.articles_count++ |
| Article moved to another category | decrements old, increments new |
2️⃣ Example for belongsToMany relations :
Used when two models are linked via a pivot table and both have a *_count.
Example:
-
materials.parts_count -
parts.materials_count -
material_partpivot table
Material model
class Material extends Model { protected $fillable = ['name', 'parts_count']; public function parts() { return $this->belongsToMany(Part::class, 'material_part') ->using(MaterialPart::class) // We need a Pivot Model ->withTimestamps(); } }
Part model
class Part extends Model { protected $fillable = ['name', 'materials_count']; public function materials() { return $this->belongsToMany(Material::class, 'material_part') ->using(MaterialPart::class) // We need a pivot model ->withTimestamps(); } }
Pivot model (the key part)
use Illuminate\Database\Eloquent\Relations\Pivot; use Xetaio\Counts\Concerns\HasCounts; class MaterialPart extends Pivot // Extends to Pivot { use HasCounts; /** * Config the counts */ protected static array $countsConfig = [ 'material' => 'parts_count', 'part' => 'materials_count', ]; public function material() { return $this->belongsTo(Material::class); } public function part() { return $this->belongsTo(Part::class); } }
| Action | Effect |
|---|---|
material->parts()->attach(part) |
increments both counts |
material->parts()->detach(part) |
decrements both counts |
sync([...]) |
decrements/increments both counts |
--
⚡ Performance Notes
This package uses:
increment() / decrement() → atomic SQL updates
No heavy SELECT COUNT(*)
No observers per model
No risk of race conditions beyond DB atomic ops
For large-scale systems, this approach is highly performant.
--
🤝 Contributing
Pull Requests are welcome! Feel free to suggest improvements, new features, or optimizations.