CMS Framework - v2.2.2

Taxonomies Developer Guide

Overview

Taxonomies allow you to categorize and organize content in Digital Shopfront CMS. This guide covers how to register custom taxonomies, configure their behavior, and attach them to content types.

Table of Contents

Understanding Taxonomies

What is a Taxonomy?

A taxonomy is a way to group and classify content. Examples include:

  • Categories (hierarchical) - Technology > Web Development > Laravel
  • Tags (flat) - laravel, php, programming
  • Custom - Product Types, Event Categories, etc.

Built-in Taxonomies

Digital Shopfront CMS includes these built-in taxonomies:

  • post_categories - Blog post categories (hierarchical)
  • post_tags - Blog post tags (flat)
  • page_categories - Page categories (hierarchical)
  • page_tags - Page tags (flat)

Registering Taxonomies

Via Service Provider

Register taxonomies in your service provider's boot() method:

<?php

namespace App\Providers;

use ArtisanPackUI\CMSFramework\Modules\ContentTypes\Managers\TaxonomyManager;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $taxonomyManager = app(TaxonomyManager::class);

        $taxonomyManager->registerTaxonomy([
            'name' => 'Product Categories',
            'slug' => 'product_categories',
            'content_type_slug' => 'products',
            'description' => 'Organize products into categories',
            'hierarchical' => true,
            'show_in_admin' => true,
            'rest_base' => 'product-categories',
        ]);
    }
}

Via Filter Hook

Register taxonomies using the filter hook (useful for plugins):

addFilter('ap.taxonomies.registeredTaxonomies', function ($taxonomies) {
    $taxonomies['product_brands'] = [
        'name' => 'Brands',
        'slug' => 'product_brands',
        'content_type_slug' => 'products',
        'hierarchical' => false,
        'show_in_admin' => true,
    ];

    return $taxonomies;
});

Via Database

Create taxonomies through the admin UI or API:

use ArtisanPackUI\CMSFramework\Modules\ContentTypes\Models\Taxonomy;

$taxonomy = Taxonomy::create([
    'name' => 'Event Types',
    'slug' => 'event_types',
    'content_type_slug' => 'events',
    'hierarchical' => false,
    'show_in_admin' => true,
]);

Hierarchical vs Flat Taxonomies

Hierarchical Taxonomies (Categories)

Hierarchical taxonomies support parent-child relationships:

Technology
├── Web Development
│   ├── Frontend
│   └── Backend
└── Mobile Development
    ├── iOS
    └── Android

Use Cases:

  • Product categories
  • Documentation sections
  • Geographic locations
  • Department structures

Example:

$taxonomyManager->registerTaxonomy([
    'name' => 'Product Categories',
    'slug' => 'product_categories',
    'content_type_slug' => 'products',
    'hierarchical' => true,              // Enable hierarchy
    'show_in_admin' => true,
]);

Creating Hierarchical Terms:

use App\Models\ProductCategory;

// Create parent category
$technology = ProductCategory::create([
    'name' => 'Technology',
    'slug' => 'technology',
]);

// Create child category
$webDev = ProductCategory::create([
    'name' => 'Web Development',
    'slug' => 'web-development',
    'parent_id' => $technology->id,      // Set parent
]);

Flat Taxonomies (Tags)

Flat taxonomies have no hierarchy:

laravel, php, programming, web-development, api

Use Cases:

  • Tags/keywords
  • Colors
  • Sizes
  • Simple classifications

Example:

$taxonomyManager->registerTaxonomy([
    'name' => 'Product Tags',
    'slug' => 'product_tags',
    'content_type_slug' => 'products',
    'hierarchical' => false,             // Flat structure
    'show_in_admin' => true,
]);

Creating Flat Terms:

use App\Models\ProductTag;

ProductTag::create(['name' => 'Featured', 'slug' => 'featured']);
ProductTag::create(['name' => 'On Sale', 'slug' => 'on-sale']);
ProductTag::create(['name' => 'New Arrival', 'slug' => 'new-arrival']);

Attaching to Content Types

Single Content Type

Attach a taxonomy to one content type:

$taxonomyManager->registerTaxonomy([
    'name' => 'Product Categories',
    'slug' => 'product_categories',
    'content_type_slug' => 'products',   // Single content type
]);

Multiple Content Types

Attach a taxonomy to multiple content types by creating separate taxonomies:

// For products
$taxonomyManager->registerTaxonomy([
    'name' => 'Product Categories',
    'slug' => 'product_categories',
    'content_type_slug' => 'products',
]);

// For services (if you want shared terms, use the same taxonomy)
$taxonomyManager->registerTaxonomy([
    'name' => 'Service Categories',
    'slug' => 'service_categories',
    'content_type_slug' => 'services',
]);

Taxonomy Relationships in Models

Define the relationship in your model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Product extends Model
{
    /**
     * Get the categories for the product.
     */
    public function categories(): BelongsToMany
    {
        return $this->belongsToMany(
            ProductCategory::class,
            'product_category_pivots',
            'product_id',
            'product_category_id'
        );
    }

    /**
     * Get the tags for the product.
     */
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(
            ProductTag::class,
            'product_tag_pivots',
            'product_id',
            'product_tag_id'
        );
    }
}

Working with Taxonomy Terms

Attaching Terms to Content

$product = Product::find(1);

// Attach single category
$product->categories()->attach($categoryId);

// Attach multiple categories
$product->categories()->attach([1, 2, 3]);

// Sync categories (removes old, adds new)
$product->categories()->sync([1, 2, 3]);

// Attach with metadata
$product->categories()->attach($categoryId, ['order' => 1]);

Detaching Terms

// Detach single category
$product->categories()->detach($categoryId);

// Detach all categories
$product->categories()->detach();

Querying by Taxonomy

// Get all products in a category
$products = Product::whereHas('categories', function ($query) use ($categoryId) {
    $query->where('product_categories.id', $categoryId);
})->get();

// Get products with any of these tags
$products = Product::whereHas('tags', function ($query) use ($tagIds) {
    $query->whereIn('product_tags.id', $tagIds);
})->get();

// Get products with all of these tags
$products = Product::whereHas('tags', function ($query) use ($tag1Id) {
    $query->where('product_tags.id', $tag1Id);
})->whereHas('tags', function ($query) use ($tag2Id) {
    $query->where('product_tags.id', $tag2Id);
})->get();

Getting Term Children (Hierarchical)

$category = ProductCategory::find(1);

// Get immediate children
$children = $category->children;

// Get all descendants recursively
$descendants = $category->descendants();

// Get parent
$parent = $category->parent;

// Get all ancestors
$ancestors = $category->ancestors();

Creating Taxonomy Models

Taxonomy Model Structure

Create a model for your taxonomy:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;

class ProductCategory extends Model
{
    protected $fillable = [
        'name',
        'slug',
        'description',
        'parent_id',
        'order',
        'metadata',
    ];

    protected function casts(): array
    {
        return [
            'metadata' => 'array',
        ];
    }

    /**
     * Get the products in this category.
     */
    public function products(): BelongsToMany
    {
        return $this->belongsToMany(
            Product::class,
            'product_category_pivots',
            'product_category_id',
            'product_id'
        );
    }

    /**
     * Get the parent category.
     */
    public function parent(): BelongsTo
    {
        return $this->belongsTo(ProductCategory::class, 'parent_id');
    }

    /**
     * Get the child categories.
     */
    public function children(): HasMany
    {
        return $this->hasMany(ProductCategory::class, 'parent_id')
            ->orderBy('order');
    }

    /**
     * Get the permalink for the category archive.
     */
    public function getPermalinkAttribute(): string
    {
        return url("/products/category/{$this->slug}");
    }
}

Migration for Taxonomy

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('product_categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->foreignId('parent_id')->nullable()
                ->constrained('product_categories')
                ->onDelete('cascade');
            $table->integer('order')->default(0);
            $table->json('metadata')->nullable();
            $table->timestamps();

            $table->index('slug');
            $table->index('parent_id');
        });

        // Pivot table
        Schema::create('product_category_pivots', function (Blueprint $table) {
            $table->foreignId('product_id')
                ->constrained()
                ->onDelete('cascade');
            $table->foreignId('product_category_id')
                ->constrained()
                ->onDelete('cascade');
            $table->primary(['product_id', 'product_category_id']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('product_category_pivots');
        Schema::dropIfExists('product_categories');
    }
};

Examples

Product Categories (Hierarchical)

$taxonomyManager->registerTaxonomy([
    'name' => 'Product Categories',
    'slug' => 'product_categories',
    'content_type_slug' => 'products',
    'description' => 'Organize products into hierarchical categories',
    'hierarchical' => true,
    'show_in_admin' => true,
    'rest_base' => 'product-categories',
]);

Product Tags (Flat)

$taxonomyManager->registerTaxonomy([
    'name' => 'Product Tags',
    'slug' => 'product_tags',
    'content_type_slug' => 'products',
    'description' => 'Tag products with keywords',
    'hierarchical' => false,
    'show_in_admin' => true,
    'rest_base' => 'product-tags',
]);

Event Types (Flat)

$taxonomyManager->registerTaxonomy([
    'name' => 'Event Types',
    'slug' => 'event_types',
    'content_type_slug' => 'events',
    'hierarchical' => false,
    'show_in_admin' => true,
]);

Documentation Sections (Hierarchical)

$taxonomyManager->registerTaxonomy([
    'name' => 'Documentation Sections',
    'slug' => 'doc_sections',
    'content_type_slug' => 'docs',
    'hierarchical' => true,
    'show_in_admin' => true,
]);

Best Practices

1. Use Descriptive Names

✓ 'name' => 'Product Categories'
✓ 'name' => 'Event Types'
✗ 'name' => 'Categories'
✗ 'name' => 'Types'

2. Follow Slug Conventions

✓ 'slug' => 'product_categories'
✓ 'slug' => 'event_types'
✗ 'slug' => 'categories'
✗ 'slug' => 'productCategories'

3. Choose Appropriate Hierarchy

// Use hierarchical for nested structures
'hierarchical' => true   // Categories, sections, locations

// Use flat for simple tags
'hierarchical' => false  // Tags, colors, sizes

4. Create Indexes

Always index foreign keys and slug columns:

$table->index('slug');
$table->index('parent_id');

5. Use Pivot Tables

Name pivot tables descriptively:

✓ 'product_category_pivots'
✗ 'category_product'

Retrieving Taxonomies

Get All Registered Taxonomies

$taxonomyManager = app(TaxonomyManager::class);
$taxonomies = $taxonomyManager->getRegisteredTaxonomies();

Get Taxonomies for Content Type

$taxonomies = $taxonomyManager->getTaxonomiesForContentType('products');

Check if Taxonomy Exists

if ($taxonomyManager->taxonomyExists('product_categories')) {
    // Taxonomy exists
}

See Also