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

Taxonomy Bundle Laravel Package

enhavo/taxonomy-bundle

Taxonomy bundle for the Enhavo CMS, providing flexible category and tag management on top of Symfony. A subtree split of the main Enhavo repository; see the main project for documentation and support.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a subtree split of the enhavo/enhavo monorepo — the README here is intentionally empty. Treat the monorepo as your source of truth: browse src/Enhavo/Bundle/TaxonomyBundle/ and the docs/ folder there before touching anything. A standalone composer require enhavo/taxonomy-bundle only makes sense if the rest of your app is already an Enhavo project.

Minimal path to a working taxonomy:

  1. Install through the metapackage rather than by hand, so the version matches the rest of Enhavo:
    composer require enhavo/enhavo
    
  2. Register the bundle in config/bundles.php:
    Enhavo\Bundle\TaxonomyBundle\EnhavoTaxonomyBundle::class => ['all' => true],
    
  3. Import routing (check the actual filename under Resources/config/routing* for your version):
    # config/routes/enhavo_taxonomy.yaml
    enhavo_taxonomy:
        resource: '@EnhavoTaxonomyBundle/Resources/config/routing.yaml'
    
  4. Create the schema — the bundle ships Doctrine mappings for Taxonomy and Term. Run bin/console doctrine:migrations:diff to see what it wants to add before doctrine:migrations:migrate.
  5. Seed a taxonomy (e.g. category or tag) via fixtures or the admin UI, then attach terms to your own entity.

Where to look first, in this order:

  • Entity/Taxonomy.php and Entity/Term.php — the domain model (slug, name, parent, position, children).
  • Manager/TaxonomyManager.php, Manager/TermManager.php — the write-side API.
  • Repository/TermRepository.php — the read-side API (findByTaxonomy(...), hierarchy helpers).
  • Form/Type/TermType.php — how the admin term picker is built; the fastest way to understand what a term can carry.
  • Resources/config/services.yaml + Resources/config/doctrine/*.orm.xml — service IDs and column names.

First use case: create a single category taxonomy, add a handful of hierarchical terms, attach them to a Page-like entity with a ManyToMany, and render the tree in Twig. That exercises the whole bundle (entity, repository, form, template) in about an hour.

Implementation Patterns

Reading a taxonomy and its terms. Work through the manager or repository rather than the EntityManager, so overridden model classes keep working:

use Enhavo\Bundle\TaxonomyBundle\Repository\TaxonomyRepository;
use Enhavo\Bundle\TaxonomyBundle\Repository\TermRepository;

$taxonomy = $taxonomyRepository->findOneBy(['slug' => 'category']);
$terms    = $termRepository->findByTaxonomy($taxonomy); // ordered by position in most versions

Attaching terms to your own entity. The bundle's Term is standalone; you own the relation:

#[ORM\ManyToMany(targetEntity: Term::class)]
#[ORM\JoinTable(name: 'app_page_term')]
private Collection $terms;

Filter always by taxonomy when you present choices — terms live in a flat table shared by all taxonomies, so an unfiltered EntityType will happily let editors pick a tag where a category belongs:

$builder->add('terms', EntityType::class, [
    'class'         => Term::class,
    'multiple'      => true,
    'query_builder' => fn (TermRepository $r) => $r->createByTaxonomyQueryBuilder($taxonomy),
]);

Hierarchy rendering. Term::getChildren() is the natural recursive node; a Twig macro beats a service:

{% macro tree(terms) %}
    <ul>
    {% for term in terms %}
        <li>{{ term.name }}
            {% if term.children|length %}{{ _self.tree(term.children) }}{% endif %}
        </li>
    {% endfor %}
    </ul>
{% endmacro %}

Render only roots at the top level (term.parent is null) — otherwise you get the whole tree duplicated.

Extending the admin. Enhavo's resource system lets you override the term form without forking the bundle:

enhavo_resource:
    resources:
        enhavo_taxonomy.term:
            form:
                - App\Form\Type\ExtendedTermType

Overriding the model. Swap in your own entity via the bundle config, then make Doctrine resolve the interface to your class:

enhavo_taxonomy:
    resources:
        term:
            classes:
                model: App\Entity\Term

Pair this with resolve_target_entities so relations typed against TermInterface point at App\Entity\Term. Without that listener you get "target entity not found" at runtime, not at compile time.

Workflow tip: treat taxonomies as data, not code. Seed them in fixtures with fixed slugs (category, tag, topic) and reference those slugs in templates and queries — never by ID.

Gotchas and Tips

  • Subtree split = no docs, no changelog, no issues here. File bugs against the monorepo. If you're reading this package's GitHub page for guidance, you're in the wrong repository.
  • Version lock-in is absolute. This bundle's entities are wired into Enhavo's admin, routing, and translation layers. Mixing a Taxonomy bundle version with a different enhavo/* version is the single most common source of breakage. Pin via the metapackage.
  • Interface vs. class in your own code. Depend on TermInterface / TaxonomyInterface and the repository interfaces, not the concrete classes — that's what makes enhavo_taxonomy.resources.*.classes.model overrides survivable.
  • N+1 on trees. term.children is a lazy collection; rendering a deep tree fires one query per node. Add ->leftJoin('t.children', 'c')->addSelect('c') (or the bundle's findByTaxonomy with join, depending on version) in whatever path renders menus.
  • Cross-taxonomy contamination. TermRepository::findAll() returns terms from every taxonomy. Always constrain by taxonomy. Same for deletion: a term can be referenced by entities that have no relation to its taxonomy.
  • Slug uniqueness scope. Slugs are usually unique per taxonomy, not globally, so two taxonomies can both hold news. Don't build routes assuming a global term slug lookup.
  • Translations. Enhavo's translation layer means $term->getName() may return the source string while the rendered page expects a locale-specific value; look for getTranslation() / the Translation association before assuming names are plain columns.
  • Deleting parents. Removing a term with children typically orphans or cascades depending on the mapping in your installed version — verify the FK onDelete in the ORM XML before letting editors delete nodes.
  • Position field is manual. Ordering is by an integer you maintain; there's no auto-reorder on insert. Bulk imports need to set position explicitly or the tree renders in insertion order.
  • Caching. Taxonomy data changes rarely and reads constantly. Cache the rendered tree per taxonomy + locale with a short TTL, and invalidate in a Doctrine listener on Term/Taxonomy postPersist/postUpdate/postRemove.
  • Zero dependents, zero stars is a signal about ecosystem support, not quality — this is first-party Enhavo code. Judge it by the monorepo's test suite and release cadence, not by this repository's stats.
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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr