zxf5115/laravel-category-module
Installation
composer require zxf5115/laravel-category-module
php artisan vendor:publish --provider="Zxf5115\CategoryModule\CategoryServiceProvider" --tag="config"
php artisan migrate
config/category.php) and runs migrations for the categories table.Basic Usage
use Zxf5115\CategoryModule\Models\Category;
$category = Category::create([
'name' => 'Electronics',
'parent_id' => null, // Root category
'slug' => 'electronics',
'description' => 'All electronics products',
'status' => 1, // Active
]);
$categories = Category::with('children')->where('parent_id', null)->get();
// Get all categories as a nested array
$tree = Category::getTree();
// Check if a category exists
Category::hasSlug('electronics');
First Use Case
// Attach a product to a category
$product->categories()->attach($category->id);
Hierarchical Data Handling
$path = [];
$category->getPath($path); // Fills $path with [root, parent, current]
$categories = Category::where('depth', '<=', 2)->get();
API/Controller Integration
use Zxf5115\CategoryModule\Http\Controllers\CategoryController;
// Extend or override methods in `app/Http/Controllers/CategoryController.php`
return CategoryResource::collection($categories)->additional([
'meta' => ['count' => $categories->count()]
]);
Middleware for Category Access
Route::middleware(['category.access:electronics'])->group(function () {
// Only accessible if user has permission for 'electronics' category
});
Event-Driven Extensions
Category::created(function ($category) {
// Sync with external services (e.g., Elasticsearch)
});
categories table to support many-to-many with other models (e.g., products, posts):
// In a Product model:
public function categories()
{
return $this->morphToMany(Category::class, 'categorizable');
}
$tree = Cache::remember('category.tree', now()->addHours(1), function () {
return Category::getTree();
});
use Zxf5115\CategoryModule\Rules\UniqueSlug;
$request->validate([
'slug' => ['required', new UniqueSlug],
]);
Slug Conflicts
// Avoid:
Category::where('slug', 'old-slug')->update(['slug' => 'new-slug']);
// Use:
$category->update(['slug' => 'new-slug']); // Handles uniqueness internally
Depth Calculation
depth column is auto-updated via observers. If you bypass the model (e.g., raw SQL), depth may become stale:
-- ❌ Avoid direct inserts/updates without model methods
DB::table('categories')->insert([...]);
Parent-Child Loops
A → B → A) will break the tree structure. Validate parent IDs:
$request->validate([
'parent_id' => ['nullable', 'exists:categories,id', 'not_in:' . $category->id],
]);
Migration Conflicts
categories table, ensure the package’s migrations don’t overwrite your changes. Publish and modify the migration:
php artisan vendor:publish --tag="migrations"
parent_id and depth columns directly in the DB. Use:
Category::toTree(); // Debug output
created/updated observers fire unexpectedly, disable them temporarily:
Category::observe([]); // Disable all observers
Custom Fields
categories table and extend the model:
// In app/Models/Category.php
protected $casts = [
'is_featured' => 'boolean',
];
Custom Tree Methods
Category model:
public function getFlatList()
{
return $this->where('parent_id', null)->with('children')->get()->flatten();
}
API Resources
CategoryResource:
namespace App\Http\Resources;
use Zxf5115\CategoryModule\Http\Resources\CategoryResource as BaseResource;
class CategoryResource extends BaseResource
{
public function toArray($request)
{
$array = parent::toArray($request);
$array['custom_field'] = $this->custom_field;
return $array;
}
}
Service Provider Hooks
AppServiceProvider:
$this->app->bind(
\Zxf5115\CategoryModule\Contracts\CategoryRepository::class,
\App\Repositories\CustomCategoryRepository::class
);
Default Values:
The config file (config/category.php) sets defaults like:
'default_status' => 1, // Active by default
'slug_generate_pattern' => 'slug-{random}',
Override these to change behavior (e.g., disable auto-slug generation).
Soft Deletes: Enable soft deletes in the config:
'soft_deletes' => true,
Then use:
$category->delete(); // Soft deletes instead of hard delete
How can I help you explore Laravel packages today?