Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Classification Bundle Laravel Package

rmzamora/classification-bundle

A Laravel/PHP package for adding structured classification and taxonomy support to your application. It helps organize records with reusable categories, types, and relationships, making it useful for tagging, content grouping, and hierarchical data management.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a fork of sonata-project/classification-bundle republished under the rmzamora namespace. If you are starting a new project, use the upstream package instead — this fork is archived and receives no fixes. The guidance below assumes you are inheriting a legacy Symfony 2.x/3.x + Sonata Admin application where rmzamora/* forks are already pinned in composer.json.

Minimal install:

composer require rmzamora/classification-bundle

Register the bundle in app/AppKernel.php after the Sonata core bundles and Doctrine extensions:

new Sonata\CoreBundle\SonataCoreBundle(),
new Sonata\BlockBundle\SonataBlockBundle(),
new Sonata\MediaBundle\SonataMediaBundle(),
new Sonata\AdminBundle\SonataAdminBundle(),
new Rmzamora\Bundle\ClassificationBundle\RmzamoraClassificationBundle(),

Add the base entity mappings to config.yml:

doctrine:
    orm:
        mappings:
            gedmo_tree:
                type: annotation
                prefix: Gedmo\Tree\Entity
                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity"
                alias: GedmoTree

Then run php app/console doctrine:schema:update --force (or generate a migration) and load a root Context.

Where to look first, in order:

  1. Resources/config/admin.xml — see which admin services are registered (sonata.classification.admin.category, .tag, .collection, .context).
  2. Entity/BaseCategory.php — the nested-set + slug behavior that everything else hangs off.
  3. Manager/CategoryManager.php and Manager/TagManager.php — the actual query surface you will use day to day.

First use case: log into Sonata Admin → Classification → Contexts, create a default context (slug default), then create a root Category (e.g. "Blog"). You can now attach categories and tags to any content entity in your app.

Implementation Patterns

Tag-scoping pattern. Sonata's tag model is contextual. Never query TagRepository::findAll() — always scope by context:

$context = $this->get('sonata.classification.manager.context')->getCurrentContext();
$tags = $this->get('sonata.classification.manager.tag')
    ->findBy(['context' => $context, 'enabled' => true]);

If your app has multiple contexts (e.g. default for articles, products for a catalog), swap the context in a kernel.request listener before any category or tag queries fire. ContextManager::setCurrentContext($slug) is a mutable global — treat it like the request locale.

Taggable entities. Do not model taxonomy directly on your Post entity. Implement Sonata\ClassificationBundle\Model\TaggableInterface and expose a tags collection (ManyToMany to Tag). Then:

$tagManager = $this->get('sonata.classification.manager.tag');
$tag = $tagManager->findOneBySlug('php', $context);       // existing tag
$post->addTag($tag);
$tagManager->save($tag); // or save the post, depending on cascade config

Expose the same field in your admin form with a sonata_type_model / ModelType configured as multiple => true, by_reference => false.

Category as a routing key, not an ID. Sonata gives you a slugified, tree-aware category. Use CategoryManager::findOneBySlug($slug, $context) for landing pages, and findByParentId($parentId) for breadcrumbs. Avoid leaking numeric IDs into URLs because moving a category in the admin tree changes parent paths but keeps the slug stable.

Rendering a category tree in front-end. Two standard patterns:

  • Query in a controller/twig extension: $categoryManager->getRootCategories($context) then recurse.
  • Use the bundled SonataBlock (sonata.classification.block.category_breadcrumb / sonata.classification.block.tag_cloud) if you already run sonata-project/block-bundle. Configure them in sonata_block.yml and let editors drop them into page templates.

Media collections. A Collection groups Media items. If you build a gallery page, wire the collection to an admin tab and use sonata.classification.manager.collection to fetch collectionHasMedia for the current page. This is the only part of the bundle that hard-depends on sonata-project/media-bundle; if you omit media, keep collections disabled in config.yml and skip registration.

Admin extension pattern. When you need a "Categories" tab on a custom entity admin, extend Sonata\ClassificationBundle\Admin\BaseCategoryAdmin or register an AdminExtension on the base admin and add a sonata_type_model_list field pointing at sonata.classification.admin.category. Always set the context argument in form options so the picker stays scoped.

Gotchas and Tips

Abandoned fork, plan the exit. The package name and namespace differ from upstream (Rmzamora\Bundle\ClassificationBundle vs Sonata\ClassificationBundle), but the code does not diverge meaningfully. When you migrate, it is a namespace + service-id search-and-replace; the DB schema is byte-compatible because the tables still use classification__ prefixes. Budget that migration early — the archive state means no Symfony 4+ support and no security fixes.

The Doctrine tree will bite you. Category uses Gedmo NestedSet. If gedmo/doctrine-extensions is missing or the tree listener is not registered, getRootCategories() and any children / parent relationship will silently return empty. Debug by checking tree_lft, tree_rgt, tree_root columns in classification__category — they should be populated after a successful save. If they are all NULL, your listener is not active. Also: after moving a category in admin, any cached tree must be invalidated.

Context must exist first. Every Category, Tag, and Collection has a mandatory FK to Context. If you seed fixture data without creating the context, or if a form submits with a stale context name after a context rename, you get a ForeignKeyConstraintViolationException or an empty admin list. Seed contexts explicitly in DoctrineFixtures and make the context slug a constant in code.

Slug uniqueness is scoped, not global. Slugs are unique per (context, parent) for categories. The same php tag can exist under two contexts without conflict — but two tags with the same slug in the same context will collide on save. If you import tags programmatically, wrap each in a try/catch or check first via findOneBySlug().

Global current context is a footgun under long-running workers. getCurrentContext() returns "default" unless explicitly set. In a Symfony request this is fine because a listener sets it per request. In a console command, queue worker, or functional test that iterates over multiple tenants, set and restore the context around each unit of work, or you will read the wrong tenant's tags.

Cascade config matters. The default Tag entity is the owning side of the post_tags join table only if you wired it that way. Double-check cascade={"persist"} on your entity's tags property. If you cascade remove, deleting one post can delete shared tags — almost never what you want. Prefer a join table without cascade and explicit TagManager calls.

Admin forms need explicit context options. The bundled sonata_type_model for categories does not auto-scope to the current context unless you pass context => $context->getId() in query or choices. If editors report "they can see categories from the wrong tenant", this is the cause.

Assets are stale. The Resources/public and block templates in this fork were snapshotted from an older Sonata release. If the classification admin UI renders incorrectly (missing icons, broken modal), run assets:install --symlink and assetic:dump, and consider copying the upstream templates instead of debugging the fork's.

Debugging checklist.

  • app/console sonata:admin:list — confirm all four admin services are registered.
  • app/console doctrine:query:sql "SELECT id, slug, tree_lft, tree_rgt FROM classification__category" — inspect tree state directly.
  • app/console sonata:admin:explain sonata.classification.admin.category — verify form fields include context and parent.
  • Cache key sonata_classification — clear it after any bulk import of categories or tags; the fork does not always invalidate cleanly.

Extension point worth knowing. ContextManagerInterface is intentionally small — you can decorate it in your own bundle to inject a tenant-aware context resolved from the security token. That is the cleanest way to retrofit multi-tenancy onto an app that adopted this bundle before multi-tenancy was a requirement.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle