baks-dev/materials-category
Laravel/PHP модуль «Materials Category» для каталога сырья: управление категориями и структурами материалов, интеграция с baks-dev/materials-catalog. Установка через Composer, тесты PHPUnit (group=materials-category). PHP 8.4+. MIT лицензия.
Install Dependencies
composer require baks-dev/materials-category baks-dev/materials-catalog
Note: Requires PHP 8.4+ and Laravel 10+ (or Symfony 6+).
Publish Config & Migrations
php artisan vendor:publish --provider="BaksDev\MaterialsCategory\MaterialsCategoryServiceProvider"
php artisan migrate
Verify: Check database/migrations/ for materials_categories and materials_category_materials tables.
First Use Case: Create a Category
use BaksDev\MaterialsCategory\Models\Category;
$steel = Category::create([
'name' => 'Steel',
'slug' => 'steel',
'parent_id' => null, // Root category
'description' => 'Ferrous metal alloys',
]);
$alloy = Category::create([
'name' => 'Alloy',
'slug' => 'alloy',
'parent_id' => $steel->id,
]);
Attach to a Material (via materials-catalog)
use BaksDev\MaterialsCatalog\Models\Material;
$material = Material::create([...]);
$material->categories()->attach($alloy->id);
app/Models/BaksDev/MaterialsCategory/Models/ (e.g., Category.php).src/Service/ for business logic (e.g., hierarchy validation).tests/Feature/MaterialsCategory/ for workflow examples.config/materials-category.php (e.g., default hierarchy depth).// Create nested categories
$category = Category::create([
'name' => 'Plastics',
'slug' => 'plastics',
'parent_id' => null,
]);
$pet = Category::create([
'name' => 'PET',
'slug' => 'pet',
'parent_id' => $category->id,
]);
// Validate hierarchy (e.g., prevent loops)
if ($pet->isValidHierarchy()) {
$pet->save();
}
use BaksDev\MaterialsCategory\Services\CategoryImporter;
$importer = new CategoryImporter();
$results = $importer->importFromCsv(
'path/to/materials.csv',
['name', 'slug', 'parent_slug'] // Map CSV columns to model fields
);
// Via materials-catalog
$material = Material::find(1);
$material->categories()->sync([$pet->id, $alloy->id]); // Attach/detach
// Get all children (recursive)
$children = $steel->children()->with('children')->get();
// Check if a material belongs to a category
if ($material->categories->contains($alloy)) {
// Logic for alloy-specific pricing/rules
}
$this->app->bind(
\BaksDev\MaterialsCategory\Service\CategoryValidator::class,
\App\Services\LaravelCategoryValidator::class
);
Validator with Laravel’s:
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($data, [
'name' => 'required|string|max:255',
'slug' => 'required|unique:materials_categories',
]);
BaksDev\MaterialsCategory\Http\Resources\CategoryResource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CustomCategoryResource extends JsonResource {
public function toArray($request) {
return [
'id' => $this->id,
'name' => $this->name,
'material_count' => $this->materials()->count(),
];
}
}
namespace App\Console\Commands;
use Illuminate\Console\Command;
use BaksDev\MaterialsCategory\Models\Category;
class SyncCategoriesCommand extends Command {
protected $signature = 'materials:sync-categories';
public function handle() {
// Custom logic to sync with external API
Category::syncExternalData();
}
}
use Filament\Forms;
use BaksDev\MaterialsCategory\Models\Category;
Filament::registerFormComponents([
Forms\Components\Select::make('parent_id')
->relationship('parent', 'name')
->nullable(),
]);
namespace App\Http\Livewire;
use Livewire\Component;
use BaksDev\MaterialsCategory\Models\Category;
class CategoryManager extends Component {
public $categories;
public $newCategory = '';
public function mount() {
$this->categories = Category::with('children')->get();
}
public function addCategory() {
Category::create(['name' => $this->newCategory]);
$this->newCategory = '';
}
public function render() {
return view('livewire.category-manager');
}
}
Symfony Dependencies
Validator, Container, and EventDispatcher.// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton(
\Symfony\Component\Validator\Validator\ValidatorInterface::class,
fn() => \Illuminate\Support\Facades\Validator::make([], [])
);
}
Hierarchy Validation
A → B → A) may crash.isValidHierarchy() method or add a database constraint:
Schema::table('materials_categories', function (Blueprint $table) {
$table->foreign('parent_id')
->references('id')
->on('materials_categories')
->onDelete('restrict');
});
Missing Localization
localized_name column:
$category->translate('en')->name = 'Steel';
$category->translate('ru')->name = 'Сталь';
Performance with Deep Trees
with('children') queries can be slow for 10+ levels.// Add to Category model
protected $with = ['path'];
Undocumented Events
CategoryCreated) are fired.// In a service provider
$dispatcher->addListener(
\BaksDev\MaterialsCategory\Event\CategoryCreatedEvent::class,
fn($event) => event(new \App\Events\LaravelCategoryCreated($event->category))
);
\DB::enableQueryLog();
$category = Category::with('children')->find(1);
dd(\DB::getQueryLog());
try {
$validator = $this->app->get(\Symfony\Component\Validator\ValidatorInterface::class);
} catch (\Exception $e) {
// Fallback to Laravel validator
}
$category = Category::find(1);
if (!$category->isValidHierarchy()) {
throw new \Exception('Hierarchy loop detected!');
}
Custom Validation Rules
// app/Rules/CustomCategoryRule.php
use Illuminate\Contracts\Validation\Rule;
class CustomCategoryRule implements Rule {
public function passes($attribute, $value) {
return str_contains($value, 'steel') || str_contains($value, 'alloy');
}
public function message() {
return 'Category must include "steel" or "alloy".';
}
}
Usage:
$validator = Validator::make($data, [
'name' => ['required', new CustomCategoryRule],
]);
**Add Custom
How can I help you explore Laravel packages today?