Download the PHP package aliziodev/laravel-taxonomy without Composer
On this page you can find all versions of the php package aliziodev/laravel-taxonomy. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download aliziodev/laravel-taxonomy
More information about aliziodev/laravel-taxonomy
Files in aliziodev/laravel-taxonomy
Package laravel-taxonomy
Short Description Laravel Taxonomy is a flexible and powerful package for managing taxonomies, categories, tags, and hierarchical structures in Laravel applications. Features nested-set support for optimal query performance on hierarchical data structures.
License MIT
Informations about the package laravel-taxonomy
Manage categories, tags and any hierarchical structure in Laravel. Terms live in one table, attach to any model through a polymorphic pivot, and hierarchies are maintained as a nested set so ancestor and descendant lookups stay a single query.
🇮🇩 Dokumentasi Bahasa Indonesia
Contents
- Configuration
- Attaching to models
- Metadata
- Slugs and exceptions
- Troubleshooting
Requirements
| Requirement | Version |
|---|---|
| PHP | 8.2 or newer |
| Laravel | 11, 12 or 13 |
Installation
taxonomy:install publishes the config and the migration. Pass --force to overwrite files that already exist — without it, existing files are left untouched and the command tells you so.
To publish individually:
Configuration
config/taxonomy.php, with the shipped defaults:
morph_type is the one setting to get right up front. It decides whether the pivot stores taxonomable_id as an integer, UUID or ULID, and it cannot be changed after you migrate without rewriting the table. Use numeric for the usual auto-incrementing keys.
migrations.autoload controls whether the package registers its migration path with php artisan migrate. Disable it when you run migrations per tenant connection:
Quick start
Add the trait to any model that should carry taxonomies:
Working with taxonomies
The Taxonomy facade proxies TaxonomyManager and exposes exactly these methods:
The facade is not an Eloquent builder.
Taxonomy::where(...)and friends do not exist on it. For query building, import the model instead:
Model scopes: type(), root(), ordered(), roots(), atDepth(), nestedSetOrder().
Bulk imports
create() costs four to seven queries per row — a slug check, a parent or
max(rgt) lookup, the range updates that widen the nested set, the insert, and
a cache bump. Fine for a handful of rows, painful for a seeder.
bulkCreate() resolves slugs in memory, inserts in chunks, and renumbers the
nested set once at the end:
Measured on 10,000 rows:
| Time | Queries | |
|---|---|---|
create() in a loop, flat |
14.8s | 40,000 |
bulkCreate(), flat |
0.95s | 32 |
create() in a loop, nested |
22.0s | 59,980 |
bulkCreate(), nested |
1.0s | 37 |
Both paths produce the same tree; only the number of round trips differs.
It accepts any iterable, so a generator keeps memory flat on large imports:
Rows take name and type (required), plus optional slug, description,
parent_id, sort_order, meta, created_at and updated_at. Slugs are
generated and de-duplicated against both the batch and the rows already in the
table; an explicit slug that is already taken raises DuplicateSlugException,
exactly as create() would.
Model events are not fired. That is the trade for the speed. If you rely on observers, or on anything hooked into
creating/created, keep usingcreate(). Everything the package itself does in those hooks — slug generation, nested set values, cache invalidation —bulkCreate()does for you.
Attaching to models
Each accepts an id, a Taxonomy, an array, or any collection:
These take taxonomy ids or models, not slugs. Passing a slug string attaches nothing. Resolve it first:
Taxonomy::findBySlug('featured', TaxonomyType::Tag).
Reading and checking:
Type-specific variants
Every attach/detach/sync/toggle has an *OfType twin that only touches terms of one type, leaving the rest of the model's taxonomies alone:
Ids that are not of the given type are skipped — that filtering is the point of these methods.
Query scopes
Scopes chain, so combining them is an AND:
filterByTaxonomies() takes a keyed array, handy for request filters:
Hierarchies
Hierarchy is stored twice: as parent_id, and as nested-set lft/rgt/depth columns kept in sync automatically on create, update, delete and restore.
getAncestors()/getDescendants() read lft/rgt and are the fastest option. ancestors()/descendants() follow parent_id and stay correct even if the nested set has drifted — use them if you write to the table outside the model.
Refresh before using the nested-set variants on a model you already held. Adding a child widens its parent's
rgtin the database, and an instance loaded before that still carries the old bounds —getDescendants()then returns an empty collection with no error:
descendants()is keyed on the id, so it is immune to this.
Trees:
Types
TaxonomyType ships Category, Tag, Color, Size, Unit, Type, Brand, Model, Variant. Everywhere a type is accepted you may pass the enum or a plain string, so custom types need no registration:
List them in config so tooling and the rebuild command know about them:
Enum helpers:
Metadata
meta is a JSON column, cast to array:
There is no translation layer; meta is a reasonable home for translations:
Caching
tree(), flatTree() and getNestedTree() are cached for cache.ttl (24 hours by default) and invalidated automatically whenever a taxonomy is created, updated, deleted, restored, moved, or rebuilt.
Invalidation works by bumping a version key, so entries expire logically rather than being enumerated and deleted — that keeps it correct on cache stores without tag support.
Do not wrap these calls in another
Cache::remember(). A second, unversioned layer will not see the package's invalidation and will serve stale trees.
Multi-tenancy
Two things need attention.
1. Isolate the cache. Cache keys are global unless you say otherwise, so without a scope one tenant can be served another tenant's tree. Register a resolver:
Or point taxonomy.cache.scope at an invokable class — a class name rather than a closure, so the config survives php artisan config:cache:
With no scope registered the keys are unchanged from earlier releases, so single-tenant apps need no action.
2. Scope the data yourself. The package ships no tenant_id column. Add one, and replace the unique index — the shipped unique(['slug', 'type', 'deleted_at']) otherwise stops two tenants using the same slug within a type:
Then point the package at a model carrying your scope:
It must extend the package's Taxonomy — that is where slug generation and nested-set maintenance live.
The
*OfTypemethods validate the ids you pass without applying global scopes, so a taxonomy shared across tenants is not silently discarded. The flip side: an id from another tenant will attach if your application passes it through. Validate user input, e.g.Rule::exists()scoped to the tenant.
Slugs and exceptions
Slugs are generated from the name and are unique within a type, so a featured category and a featured tag can coexist.
Two exceptions, both extending TaxonomyException:
MissingSlugException is thrown when slugs.generate is false and no slug was supplied.
Soft deletes interact with uniqueness through two settings: consider_trashed decides whether trashed rows block a slug, and regenerate_on_restore decides whether restoring a row with a now-taken slug renames it or throws.
Console commands
taxonomy:rebuild-nested-set recomputes lft, rgt and depth. You need it only if rows were written outside the model — direct SQL, a raw seeder, a bulk import. It rebuilds every type when no type is given, uses one transaction per type, and clears the caches afterwards. --force is required when running non-interactively.
Examples
- E-commerce product catalog
- Content management system
Troubleshooting
Attaching does nothing, no error. You are probably passing slugs. These methods take ids or models; resolve slugs with Taxonomy::findBySlug() first.
Call to undefined method ... where() on the facade. The facade proxies TaxonomyManager, not Eloquent. Import Models\Taxonomy for query building.
Wrong column type on the pivot. morph_type must match your models' keys and is fixed at migration time. Check it before your first migrate.
Tree looks stale. It should invalidate itself; if you write rows with raw SQL, call Taxonomy::clearCacheForType() and, if lft/rgt are involved, php artisan taxonomy:rebuild-nested-set.
Ancestors or descendants look wrong. The nested set has drifted, usually from direct SQL writes. Run the rebuild command, or use ancestors()/descendants(), which follow parent_id.
Upgrading
See UPGRADE.md. Notable in 2.11: cache isolation for multi-tenant apps, and custom relationship names are deprecated for removal in 3.0.
Contributing
See CONTRIBUTING.md. Commits follow Conventional Commits; releases and the changelog are generated from them.
Security
Report vulnerabilities to [email protected] rather than the public tracker.
License
MIT. See LICENSE.
All versions of laravel-taxonomy with dependencies
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0
illuminate/database Version ^11.0|^12.0|^13.0
laravel/framework Version ^11.0|^12.0|^13.0