Installation:
composer require oleaass/laravel-categories
Publish the migration (if needed) and run:
php artisan migrate
Model Setup:
Add the Categorizable trait to your model (e.g., Product):
use OleAass\Categories\Categorizable;
class Product extends Model
{
use Categorizable;
}
First Use Case: Attach a category to a model instance:
$product = Product::find(1);
$product->categories()->attach(1); // Attach category ID 1
Retrieve categories for a model:
$categories = $product->categories; // Collection of Category models
database/migrations/ for the categories and categoryables tables.Category model is auto-discovered (located in vendor/oleaass/laravel-categories/src/Models/Category.php).Category:: for helper methods (e.g., Category::create(['name' => 'Electronics'])).CRUD for Categories:
// Create
$category = Category::create(['name' => 'Books']);
// Read
$categories = Category::all();
// Update
$category->update(['name' => 'Updated Books']);
// Delete
$category->delete();
Polymorphic Relationships:
Attach categories to any model (e.g., Product, Article):
class Article extends Model
{
use Categorizable;
}
$article = Article::find(1);
$article->categories()->attach([1, 2]); // Attach multiple categories
Scopes and Querying: Filter models by category:
$products = Product::withCategories([1, 2])->get(); // Products in categories 1 or 2
$products = Product::inCategory(1)->get(); // Products in category 1 (uses scope)
Hierarchical Categories (Optional):
If extending for parent-child relationships, override the Category model:
class Category extends \OleAass\Categories\Models\Category
{
public function children()
{
return $this->hasMany(self::class, 'parent_id');
}
}
use Illuminate\Support\Facades\Validator;
$validator = Validator::make(['name' => ''], [
'name' => 'required|string|max:255|unique:categories',
]);
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'products_count' => $this->products->count(), // Polymorphic relation
];
}
$categories = Cache::remember('all_categories', now()->addHours(1), function () {
return Category::all();
});
Polymorphic Table Naming:
The pivot table (categoryables) uses Laravel’s default naming convention. If you customize pivot table names elsewhere, ensure consistency:
// In Category model (if overriding)
public function categoryables()
{
return $this->morphToMany('categoryable', 'categoryables', 'category_id', 'categoryable_id', 'categoryable_type');
}
Model Binding:
Avoid binding polymorphic models directly to category routes without checking the categoryable_type:
// Risky: Assumes all categoryables are Products
Route::get('/categories/{category}/products', function (Category $category) {
return $category->products; // Fails if categoryable_type is 'Article'
});
Fix: Use explicit queries:
$products = Product::whereHas('categories', fn ($q) => $q->where('categories.id', $category->id))->get();
Mass Assignment:
Enable fillable in the Category model if using create() with arrays:
protected $fillable = ['name', 'slug', 'description'];
Migration Conflicts:
If the categories table already exists, manually adjust the migration or drop it first:
php artisan migrate:fresh --env=testing
Categorizable trait is correctly used and the pivot table exists:
php artisan schema:dump
categoryable_type and categoryable_id are set in the pivot table:
SELECT * FROM categoryables WHERE categoryable_type = 'App\Models\Product';
Category model has the SoftDeletes trait and the deleted_at column exists.Custom Category Model:
Extend the default Category model:
namespace App\Models;
use OleAass\Categories\Models\Category as BaseCategory;
class Category extends BaseCategory
{
protected $table = 'custom_categories';
public $timestamps = false;
}
Update the service provider to bind your custom model:
$this->app->bind('category', function () {
return new \App\Models\Category();
});
Custom Pivot Table:
Override the pivot table name in the Categorizable trait:
public function categories()
{
return $this->morphToMany(
config('categories.model'),
'category',
'custom_categoryables',
'categoryable_id',
'category_id',
'categoryable_type'
);
}
Events:
Listen for category-related events (e.g., CategoryCreated):
use OleAass\Categories\Events\CategoryCreated;
CategoryCreated::dispatch($category);
Register listeners in EventServiceProvider:
protected $listen = [
CategoryCreated::class => [
\App\Listeners\LogCategoryCreation::class,
],
];
API Resources:
Create a dedicated CategoryResource for consistent API responses:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CategoryResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'models_count' => $this->categoryables()->count(),
'models' => $this->categoryables, // Polymorphic collection
];
}
}
How can I help you explore Laravel packages today?