baks-dev/products-category
BaksDev Product Category — модуль категорий продукции для PHP 8.4+. Установка через Composer, установка ассетов и ресурсов, настройка директории для обложек категорий, миграции Doctrine, тесты PHPUnit (group: products-category).
Pattern: Leverage the ProductCategory entity as the primary abstraction.
// Create
$category = new \BaksDev\ProductsCategoryBundle\Entity\ProductCategory();
$category->setName('Electronics');
$category->setSlug('electronics');
$category->setDescription('All electronic devices');
$entityManager->persist($category);
$entityManager->flush();
// Read (with hierarchy)
$categories = $entityManager->getRepository(ProductCategory::class)
->findBy([], ['parent' => 'ASC']); // Assuming parent-child relationship
// Update
$category->setCoverImage('new_cover.jpg');
$entityManager->flush();
// Delete (soft/hard)
$category->setIsDeleted(true); // If soft delete is implemented
$entityManager->flush();
@Assert\NotBlank on name).Pattern: Integrate with the product_category_cover upload directory.
use Symfony\Component\HttpFoundation\File\UploadedFile;
public function uploadCover(UploadedFile $file, ProductCategory $category)
{
$path = $file->getRealPath();
$filename = uniqid() . '.' . $file->guessExtension();
$destination = $this->getCoverPath($filename);
$file->move($destination, $filename);
$category->setCoverImage($filename);
$entityManager->flush();
return $destination;
}
private function getCoverPath(string $filename): string
{
return public_path('upload/product_category_cover/' . $filename);
}
CoverUploader service.StorageInterface to override default behavior:
$this->app->bind(\BaksDev\ProductsCategoryBundle\Service\StorageInterface::class, function () {
return new \App\Service\S3CoverStorage();
});
Pattern: Recursively fetch categories with their children.
// In ProductCategoryRepository
public function findWithChildren(ProductCategory $parent = null, int $depth = 0)
{
$query = $this->createQueryBuilder('c')
->where('c.parent = :parent')
->setParameter('parent', $parent);
if ($depth > 0) {
$children = $query->getQuery()->getResult();
foreach ($children as $child) {
$child->setChildren($this->findWithChildren($child, $depth - 1));
}
return $children;
}
return $query->getQuery()->getResult();
}
$rootCategories = $repository->findWithChildren(null, 2); // 2 levels deep
Pattern: Extend or use built-in commands for bulk operations.
# Create a new command to bulk-import categories
php bin/console app:import-categories path/to/csv.csv
// In src/Command/ImportCategoriesCommand.php
class ImportCategoriesCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$csvPath = $input->getArgument('path');
$categories = $this->csvImporter->import($csvPath);
foreach ($categories as $data) {
$category = new ProductCategory();
$category->setName($data['name']);
$category->setSlug($data['slug']);
$entityManager->persist($category);
}
$entityManager->flush();
$output->writeln('Imported ' . count($categories) . ' categories.');
}
}
Pattern: Subscribe to package events for custom logic.
CategoryCreatedEventCoverUploadedEventCategoryDeletedEvent// In src/EventListener/CategoryCreatedListener.php
class CategoryCreatedListener
{
public function onCategoryCreated(CategoryCreatedEvent $event)
{
$category = $event->getCategory();
// Send notification, log analytics, etc.
Log::info("New category created: {$category->getName()}");
}
}
services.yaml:
services:
App\EventListener\CategoryCreatedListener:
tags:
- { name: kernel.event_listener, event: category.created, method: onCategoryCreated }
Pattern: Expose categories via Laravel API resources.
namespace App\Http\Resources;
use BaksDev\ProductsCategoryBundle\Entity\ProductCategory;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductCategoryResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'cover_url' => $this->getCoverUrl(),
'children' => ProductCategoryResource::collection($this->children),
];
}
private function getCoverUrl()
{
return asset("upload/product_category_cover/{$this->coverImage}");
}
}
use App\Http\Resources\ProductCategoryResource;
public function index()
{
$categories = $this->categoryRepository->findWithChildren(null, 2);
return ProductCategoryResource::collection($categories);
}
Pattern: Write tests for core functionality using the products-category test group.
public function testCategoryCreation()
{
$category = new ProductCategory();
$category->setName('Test');
$category->setSlug('test');
$entityManager = $this->getEntityManager();
$entityManager->persist($category);
$entityManager->flush();
$this->assertDatabaseHas('product_categories', [
'name' => 'Test',
'slug' => 'test',
]);
}
php bin/phpunit --group=products-category
Pattern: Render categories in Blade templates.
@foreach($categories as $category)
<div class="category">
<h3>{{ $category->name }}</h3>
@if($category->coverImage)
<img src="{{ asset("upload/product_category_cover/{$category->coverImage}") }}" alt="{{ $category->name }}">
@endif
@if(count($category->children) > 0)
<ul>
@include('partials.category-list', ['categories' => $category->children])
</ul>
@endif
</div>
@endforeach
Pattern: Extend or override migrations if needed.
product_categories table.
// In src/Migrations/VersionYYYYMMDDHHMMSS.php
public function up()
{
$this->addSql("ALTER TABLE product_categories ADD meta_data JSON NULL");
}
php bin/console doctrine:migrations:execute 'ALTER TABLE product_categories ADD meta_data JSON NULL'
Pattern: Bind custom services to override or extend package behavior.
// In AppServiceProvider
public function register()
{
$this->app->bind(
\BaksDev\ProductsCategoryBundle\Service\CoverUploaderInterface::class,
\App\Service\CustomCoverUploader::class
);
}
Migration Conflicts
doctrine:migrations:migrate may fail if the package’s tables already exist.php bin/console doctrine:schema:update --dump-sql
--complete flag to force updates:
php bin/console doctrine:migrations:migrate --complete
File Permissions
public/upload/product_category_cover may fail with Permission denied.773 permissions and the web server user (e.g., `wwwHow can I help you explore Laravel packages today?