nvl / taxonomy
Hierarchical, attachable and translatable taxonomies for Laravel
Requires
- php: ^8.4
- ext-ctype: *
- ext-filter: *
- laravel/framework: ^13.0
- nvl/core: ^7.0
- nvl/translatable: ^7.0
Requires (Dev)
- larastan/larastan: ^3.10
- laravel/pint: ^1.27
- mockery/mockery: ^1.6
- nvl/ownership: ^7.0
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
- nvl/ownership: Adds optional model-declared ownership, isolation, adoption and captured worker contexts.
Provides
None
Conflicts
- nvl/ownership: <7.0 || >=8.0
Replaces
None
README
Native hierarchical vocabularies, translated terms and owner assignments for PHP 8.4 and Laravel 13. The consumer owns HTTP, orchestration Actions, DTOs, resources and frontend types.
Publish nvl-taxonomy-config when configuring vocabularies and registered owners. Select one migration source and configure authorization before invoking operations. See UPGRADING.md for storage changes and events for publication timing.
The native service PHPDoc is the signature reference: TaxonomyTerms, TaxonomyAssignments, TaxonomyReader. Registry declarations live in TaxonomyRegistry and TaxonomyOwnerRegistry.
TaxonomyTree and TermResolver are internal collaborators. Use TaxonomyReader for authorized resolution and bounded tree reads.
Call these services with an explicit persisted Authenticatable or SystemActor::Background. A model actor is reloaded and authorized through the taxonomy.<ability> Gate policy. The background actor is an explicit maintenance principal; never turn null into it.
Create policies receive the vocabulary name. Update, delete, and move policies receive the locked canonical term, its vocabulary, and the related destination when applicable. Merge policies receive the locked source, vocabulary, and destination; validateMerge uses the same authorization. Assignment policies receive the locked canonical owner and vocabulary. Rebuild policies receive the requested vocabulary when supplied. Undefined abilities deny both human and system actors.
TaxonomyCatalogAuthorization and every TaxonomyBatchAuthorization callback receive the freshly resolved explicit actor as their first argument. The default adapters admit registered capabilities and retain native model scopes; bind SQL visibility policies for private vocabularies and attachments. Policies must not borrow an ambient authenticated user.
Structural terms
- Register vocabulary rules through
TaxonomyRegistryand owner capabilities throughTaxonomyOwnerRegistry. - Use
TaxonomyTerms::create(array $attributes, $actor)andupdate(Term|string $term, array $attributes, $actor, TranslationSyncMode $mode)for canonical slugs, parent, position, metadata, and locale keyedtranslations. - Use
TaxonomyTerms::move,merge,delete,rebuild, andvalidateMergefor complete tree operations. Update, move, delete, and merge require expected revisions. A move validates cycles, depth, vocabulary, and ownership while holding locks. - Names and descriptions live only in
term_translations; slugs remain canonical and locale independent. UseTranslationSyncMode::Replaceonly for an explicit retained locale map. - Structural update patches can omit
translationsto preserve every locale. Replacement requires an explicit locale map. An explicit null position is invalid; null metadata clears it. DeleteTermStrategydetermines handling of children and attachments. Restrict is the default.
Assignments and reads
- Use
TaxonomyAssignments::attach,detach, orsyncon persisted registered owners; all operations retain the owner lock and transaction.syncis all or nothing and preserves supplied order. - Use
TaxonomyReader::resolvefor ordered references and creation of missing roots only in registered open vocabularies. UUID inputs identify existing terms; ambiguous slugs are rejected. - Resolution accepts a list bounded by
limits.bulk_termsdistinct normalized references (500 by default). Creating missing roots requirestaxonomy.create, including when resolving references during assignment; assigning existing terms needs no create permission. - Use
TaxonomyReader::forOwnerswith at most 100 owners and 20 vocabularies. It returns['owners' => stdClass, 'order' => list]; each requested owner and vocabulary has an explicit list of preloaded nativeTermmodels, including empty lists. The attached order is available aspivot_position. - For tree reads use
TaxonomyReader::treewith a bound up to 500 terms. The registered vocabulary and native model scopes apply before loading. - Preserve the correlated owner filter policy (
TaxonomyBatchAuthorization) and native ownership predicates. Never read a private term or another owner's attachment through a loose OR or unscoped relation.
Host filters also require an explicit actor: withAnyTerms($taxonomy, $values, $actor, $policy = null), withAllTerms(...), withoutTerms(...), and inCategory($category, $actor, $includeDescendants = true, $policy = null). They enforce taxonomy.read and pass the canonical actor into SQL policies. Assignment owners must use the same connection as Taxonomy so the owner lock and attachment changes share one transaction.
Ownership and maintenance
- Ownable resources are declared by the configured term root and
TaxonomyResourceRegistrar. Unscoped hosts remain unscoped. Native adoption uses explicit reviewed mappings; it is not automatic old NVL schema conversion. - Use the doctor and maintenance commands for readiness, merge, prune, and position rebuild. Owned operations require an established native owner context.
- Verify cycle and cross vocabulary/owner rejection, merge conflicts, localized copy, revision conflicts, all or nothing assignment sync, deterministic owner batches, empty results, and ownership concurrency.
System actors still require explicit policy permission; their identity is not a bypass. Preserve canonical owner identity, source transactions and native ownership admission when composing host workflows.
Released under the MIT License.
Shared service contracts, model extensions and composition apply alongside the package-specific rules above.
Owner aliases are persisted in attachment termable_type values. Keep aliases stable; changing one requires an explicit migration of existing attachments. Native assignment sync preserves unchanged attachment IDs and creation timestamps and changes only added, removed or repositioned rows. Term deletion defaults to Restrict; direct database deletion still follows the schema's cascading attachment and translation foreign keys.
Ordinary create/update validation locks only the prospective ancestor chain, and flat root validation does not scan the vocabulary. Descendant reads are limited to the affected subtree. Moves and reparenting retain a locked vocabulary snapshot to serialize cycle and depth validation; budget those operations for the vocabulary size. Attachment mutations also lock the canonical owner row; use a shared cache lock store when coordinating multiple processes.
Cache locks coordinate application workers; canonical owner/vocabulary database row locks remain the integrity boundary on the shared transaction source. A process-local cache lock alone is not distributed coordination. Merge removes source attachments already present at the destination and preserves the distinct canonical positions of the others; it does not introduce duplicate owner/vocabulary positions.