baks-dev/products-category
A PHP module for managing product categories, with cover image uploads, configuration assets, and Doctrine migration support. Install via Composer, run baks:assets:install, and manage database schema changes with Doctrine migrations.
Read this first: it is not a Laravel package. Despite the framing, every command in the README is Symfony: bin/console, baks:assets:install, doctrine:migrations:*, and the internals are Doctrine ORM entities with Symfony bundle config. There is no service provider, no artisan, no vendor:publish, no Eloquent, and no Blade views. If the target app is Laravel, treat this as "a Doctrine-based domain module you must bridge," not a drop-in. If the app is Symfony, everything below is straightforward.
Minimal path to a running install (Symfony host):
composer require baks-dev/products-category
# register the bundle in config/bundles.php (check the package's own composer.json
# for the bundle class name or a recipe/manifest hint)
php bin/console baks:assets:install
sudo mkdir -p public/upload/product_category_cover && sudo chmod 773 public/upload/product_category_cover
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
php bin/phpunit --group=products-category
Where to look in vendor/baks-dev/products-category/, in this order:
src/Resources/config/ — service definitions, Doctrine mapping, routing. This is the real documentation.src/Entity/ — the category entity, the parent/child relation, and the cover value object. Read this before writing any query.src/Repository/ — what query methods already exist; do not hand-roll DQL until you've read them.src/Form/ and src/Controller/ — if present, they define the expected admin flow and validation.translations/ — expect Russian; check whether category names are translatable or just the UI strings.First use case to validate end-to-end: create a root category with a child and a cover image through whatever admin surface exists, then render the tree on a product listing page. If all three work, the install is sound. If the cover writes to disk but 404s, it's a permissions/web-root problem, not a package problem.
Treat the entities as the source of truth; extend around them, not through them. The package owns the schema. Your app should hold the foreign key to the category ID and join outward. Trying to subclass or re-map the entity into an Eloquent model will cost you more than it saves.
Tree access. For a category module the daily work is almost always read-heavy tree rendering:
// Preferred: a repository method the package already exposes.
$tree = $categoryRepository->findAll(); // then build in memory if the set is small
// Avoid per-node lazy loading of children — that is the N+1 you will hit first.
If the package doesn't ship a tree query, add one in your own repository/service that selects the whole table once and assembles the tree in PHP, or use a recursive CTE if you're on Postgres. Cache the assembled tree (Redis or framework cache) and invalidate on category create/update/delete via an event subscriber. Categories change rarely; the tree is read on nearly every page.
Cover uploads. The README's public/upload/product_category_cover with chmod 773 is a hardcoded local-filesystem assumption. Two things to do on day one:
Bridging into a Laravel app, if you must: run Doctrine alongside Eloquent with a separate connection config, expose the categories through a thin read API (a Laravel controller + a small Doctrine repository wrapper), and keep the write path inside the Symfony/Doctrine side or in an admin route group. Never let two ORMs own the same table. The realistic seam is: package owns product_category* tables, Laravel reads them through DB facade queries or a read-only repository, and Laravel's own migrations never touch those tables.
Extending behavior. Use decoration and event subscribers rather than edits:
# config/services.yaml — decorate, don't replace
App\ProductCategory\CachedCategoryRepository:
decorates: 'BaksDev\Products\Category\Repository\...'
arguments: ['@.inner']
Same for validation: if the package's form types are too thin (no max depth, no cycle guard), add a custom constraint and attach it at your controller/form layer instead of patching vendor code.
Testing. php bin/phpunit --group=products-category is the smoke test — run it after every composer update. Add your own fixtures for the edge cases the package almost certainly doesn't cover: circular parent references, very deep trees, cover files that fail to write.
7.4.12 is not Laravel 7.4. It's the package's own semver. The README's php 8.4+ badge is the real constraint — this will not install on PHP 8.2/8.3 projects.baks:assets:install copies files, not symlinks. Re-run it after every composer update, and add it to your deploy script, or you'll ship stale or missing assets on the second deploy.doctrine:migrations:diff and artisan migrate are separate ledgers. Decide which one owns which tables before you generate anything, or you'll get duplicate-table errors that are painful to unwind.chmod 773 is unusual (group-writable, world-writable). In containers, do this in an entrypoint or mount the directory as a volume — building it into the image means it disappears on the next build. Also confirm your web server user is in that group; 773 without group membership still fails.product_category or categories already exists in your schema.composer.lock. Single maintainer, 0 stars, near-weekly releases with changelogs that read v7.4.12 / v7.4.12. There is no changelog signal to tell you what broke. Upgrade deliberately, on a branch, with the test group passing.bin/console debug:container | grep -i category to confirm services registered, bin/console cache:clear after any config change, and check config/bundles.php first when you get "service not found." On the Laravel side, a BindingResolutionException-style failure usually means the bundle isn't registered at all.templates/bundles/<BundleName>/... rather than editing vendor/.How can I help you explore Laravel packages today?