Taxonomies System

Lightpack’s Taxonomies system provides a robust, hierarchical, and framework-native way to add category, menu, or any tree-like structure to your Lucid models. It supports many-to-many polymorphic relationships, deep trees, and flexible querying.


Migration

The Taxonomies system requires two tables:

Create schema migration file:

php console create:migration --support=taxonomies

Run migration:

php console migrate:up

Adding Taxonomies to Your Model

Add TaxonomyTrait to any model to make it taxonomy-aware:

use Lightpack\Taxonomies\TaxonomyTrait;

class Post extends Model {
    use TaxonomyTrait;
}

TaxonomyTrait API

taxonomies()

Returns the model's taxonomies. Can be accessed as a property or method.

$taxonomies = $post->taxonomies;

Pivot Operations

Use the pivot methods to manage taxonomy associations:

// Attach taxonomies by ID
$post->taxonomies()->attach([1, 2, 3]);

// Detach taxonomies by ID
$post->taxonomies()->detach([2]);

// Sync taxonomies (replace all with given IDs)
$post->taxonomies()->sync([1, 3]);

filters()

Query scope to filter models by taxonomy IDs:

// All posts with taxonomy 1 or 2
$posts = Post::filters(['taxonomies' => [1, 2]])->all();

Hierarchy & Tree Operations

Lightpack's Taxonomy model provides a rich set of methods for working with hierarchical data. Each method is designed for clarity, performance, and practical developer use. Below are detailed explanations and usage for each helper:

parent()

Returns a query for the parent taxonomy node, if any.

// fetch parent node
$parent = $taxonomy->parent()->one();

if ($parent) {
    echo $parent->name;
}

children()

Returns a query for all direct child nodes, ordered by sort_order then id.

$children = $taxonomy->children()->all();

foreach ($children as $child) {
    echo $child->name;
}

siblings()

Returns all taxonomy nodes at the same level (same parent), excluding the current node, ordered by sort_order then id.

$siblings = $taxonomy->siblings();

foreach ($siblings as $sibling) {
    echo $sibling->name;
}

ancestors()

Returns all ancestor nodes from the root down to the immediate parent.

$ancestors = $taxonomy->ancestors();

foreach ($ancestors as $ancestor) {
    echo $ancestor->name;
}

descendants()

Returns all descendant nodes (children, grandchildren, etc.) in depth-first order.

$descendants = $taxonomy->descendants();
foreach ($descendants as $descendant) {
    echo $descendant->name;
}

tree()

Returns this node and all descendants as a nested array structure.

$tree = $taxonomy->tree();
print_r($tree);

forest()

Returns all root nodes as an array of nested trees.

$forest = Taxonomy::forest();
foreach ($forest as $tree) {
    // Each $tree is a nested array
}

fullSlug($separator = '/')

Signature: fullSlug(string $separator = '/'): string

Returns the full hierarchical slug for this node, joining ancestor and self slugs.

$slug = $taxonomy->fullSlug('-'); // e.g., "root-child-grandchild"

breadcrumbs()

Returns an array of taxonomy nodes from root to this node (breadcrumb trail).

$trail = $taxonomy->breadcrumbs();

foreach ($trail as $node) {
    echo $node->name;
}

moveTo()

Signature: moveTo(?int $newParentId = null): void

Moves this node (and its entire subtree) under a new parent node.

$taxonomy->moveTo(2); // Move under taxonomy ID 2
$taxonomy->moveTo(null); // Move to root

reorder()

Signature: static reorder(array $idOrderMap): void

Bulk update the sort_order of multiple nodes. Pass an array of [taxonomy_id => sort_order, ...].

Taxonomy::reorder([
    2 => 1, // taxonomy ID 2 becomes first
    3 => 2, // taxonomy ID 3 becomes second
]);

bulkMove()

Signature: static bulkMove(array $ids, ?int $newParentId = null): void

Move multiple taxonomy nodes under a new parent in one call.

Taxonomy::bulkMove([3, 4], 5); // Move nodes 3 and 4 under node 5

Edge Cases & Behavior


Practical End-to-End Examples

Below are practical, real-world scenarios that demonstrate how to use Lightpack Taxonomies in your application. These examples cover taxonomy creation, building a tree, attaching to models, querying, and navigation.

1. Creating a Category Tree

// Create root categories
$news = new Taxonomy();
$news->name = 'News';
$news->slug = 'news';
$news->type = 'category';
$news->save();

$opinion = new Taxonomy();
$opinion->name = 'Opinion';
$opinion->slug = 'opinion';
$opinion->type = 'category';
$opinion->save();

// Create children
$local = new Taxonomy();
$local->name = 'Local';
$local->slug = 'local';
$local->type = 'category';
$local->parent_id = $news->id;
$local->save();

$global = new Taxonomy();
$global->name = 'Global';
$global->slug = 'global';
$global->type = 'category';
$global->parent_id = $news->id;
$global->save();

2. Attaching Taxonomies to a Model

// Assume Post model uses TaxonomyTrait
$post = Post::find(101);

// Attach categories by ID
$post->attachTaxonomies([$news->id, $local->id]);

3. Querying Posts by Taxonomy

// Get all posts tagged as 'News' OR 'Local'
$posts = Post::filters(['taxonomies' => [$news->id, $local->id]])->all();

4. Navigating the Taxonomy Tree

// Get all children of 'News'
$children = $news->children()->all(); // ['Local', 'Global']

// Get all ancestors of 'Local'
$ancestors = $local->ancestors(); // ['News']

// Get full slug for 'Local'
$slug = $local->fullSlug(); // 'news/local'

// Get breadcrumbs for 'Local'
$trail = $local->breadcrumbs(); // ['News', 'Local']

5. Moving and Reordering Taxonomies

// Move 'Local' under 'Opinion'
$local->moveTo($opinion->id);

// Bulk reorder children of 'News'
Taxonomy::reorder([
    $global->id => 1, // 'Global' first
    $local->id => 2,  // 'Local' second
]);

6. Building a Complete Tree or Forest

// Get the full tree for 'News'
$newsTree = $news->tree();

// Get all taxonomy trees (forest)
$forest = Taxonomy::forest();

7. Using Meta Fields

$news->meta = [
    'icon' => 'fa-newspaper',
    'color' => '#3366cc',
    'description' => 'All news articles',
];
$news->save();

// Access meta
$icon = $news->meta['icon'];

These examples show how to: