Skip to main content
Categories provide a hierarchical organization structure for your products. Shopper uses nested categories with parent-child relationships, allowing unlimited depth.

Model

Shopper\Core\Models\Category
The Category model uses the staudenmeir/laravel-adjacency-list package for recursive relationships:
use Shopper\Core\Models\Category;

// The model implements
- Shopper\Core\Models\Contracts\Category
- Spatie\MediaLibrary\HasMedia

// Uses traits
- HasRecursiveRelationships // (from laravel-adjacency-list)

Extending the Model

namespace App\Models;

use Shopper\Core\Models\Category as BaseCategory;

class Category extends BaseCategory
{
    // Add your customizations here
}
Update config/shopper/models.php:
return [
    'category' => \App\Models\Category::class,
];

Database Schema

ColumnTypeNullableDefaultDescription
idbigintnoautoPrimary key
namestringno-Category name
slugstringnoautoURL-friendly identifier (unique)
descriptionlongtextyesnullCategory description
positionintegerno0Display order position
is_enabledbooleannofalseCategory visibility status
iconstringyesnullIcon identifier
seo_titlestring(60)yesnullSEO meta title
seo_descriptionstring(160)yesnullSEO meta description
metadatajsonyesnullAdditional custom data
parent_idbigintyesnullForeign key to parent category
created_attimestampyesnullCreation timestamp
updated_attimestampyesnullLast update timestamp

Hierarchical Structure

Categories support unlimited nesting levels:
Electronics (parent_id: null)
├── Computers (parent_id: 1)
│   ├── Laptops (parent_id: 2)
│   └── Desktops (parent_id: 2)
├── Phones (parent_id: 1)
│   ├── Smartphones (parent_id: 5)
│   └── Accessories (parent_id: 5)
└── Audio (parent_id: 1)

Relationships

Parent Category

// Get parent category
$category->parent; // Returns Category model or null

// Check if has parent
if ($category->parent_id) {
    // This is a subcategory
}

Children Categories

Using laravel-adjacency-list package:
// Get direct children
$category->children;

// Get all descendants (recursive)
$category->descendants;

// Get all ancestors
$category->ancestors;

// Get descendants with depth info
$category->descendantsAndSelf;

Descendant Categories

// Get all descendant categories
$category->descendantCategories;

// Useful for getting all subcategories at any depth

Products

Categories have a polymorphic many-to-many relationship with products:
// Get all products in this category
$category->products;

// Get products count
$category->products()->count();

// Get published products only
$category->products()
    ->where('is_visible', true)
    ->whereDate('published_at', '<=', now())
    ->get();

Available Tree Methods

The laravel-adjacency-list package provides these methods:
MethodDescription
ancestors()The model’s recursive parents
ancestorsAndSelf()The model’s recursive parents and itself
bloodline()The model’s ancestors, descendants and itself
children()The model’s direct children
childrenAndSelf()The model’s direct children and itself
descendants()The model’s recursive children
descendantsAndSelf()The model’s recursive children and itself
parent()The model’s direct parent
parentAndSelf()The model’s direct parent and itself
rootAncestor()The model’s topmost parent
siblings()The parent’s other children
siblingsAndSelf()All the parent’s children
$ancestors = Category::query()->find($id)->ancestors;
$categories = Category::with('descendants')->get();
$total = Category::query()->find($id)->descendants()->count();
Category::query()->find($id)->descendants()->update(['is_enabled' => false]);

Query Scopes

Enabled Categories

use Shopper\Core\Models\Category;

// Get only enabled categories
Category::query()->enabled()->get();

Root Categories

// Get only top-level categories (no parent)
Category::query()->whereNull('parent_id')->get();

// Using the tree structure
Category::tree()->get();

Custom Paths

Categories automatically generate slug paths for nested URLs:
// The model defines custom paths
public function getCustomPaths(): array
{
    return [
        [
            'name' => 'slug_path',
            'column' => 'slug',
            'separator' => '/',
        ],
    ];
}

// Example: electronics/computers/laptops
$category->slug_path;

Status Management

// Enable a category
$category->updateStatus(true);

// Disable a category
$category->updateStatus(false);

Display Helpers

Label with Path

// Get full path name for select options
$category->getLabelOptionName();

// Returns: "Electronics / Computers / Laptops"

Creating Categories

use Shopper\Core\Models\Category;

// Create a root category
$electronics = Category::query()->create([
    'name' => 'Electronics',
    'slug' => 'electronics',
    'is_enabled' => true,
    'position' => 1,
]);

// Create a subcategory
$computers = Category::query()->create([
    'name' => 'Computers',
    'slug' => 'computers',
    'parent_id' => $electronics->id,
    'is_enabled' => true,
]);

// Slug auto-generation with parent prefix
$laptops = Category::query()->create([
    'name' => 'Laptops',
    'slug' => $computers->slug . '-laptops', // computers-laptops
    'parent_id' => $computers->id,
    'is_enabled' => true,
]);

Retrieving Categories

// Get all root categories with children
$categories = Category::query()
    ->whereNull('parent_id')
    ->enabled()
    ->with('children')
    ->orderBy('position')
    ->get();

// Get category tree (all levels)
$tree = Category::query()
    ->enabled()
    ->tree()
    ->get()
    ->toTree();

// Get category with all descendants
$category = Category::query()
    ->where('slug', 'electronics')
    ->with('descendants')
    ->first();

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

// Get categories for navigation
$navCategories = Category::query()
    ->whereNull('parent_id')
    ->enabled()
    ->with(['children' => fn ($q) => $q->enabled()])
    ->orderBy('position')
    ->get();

Working with Products

// Get products in category and all subcategories
$category = Category::query()->find($id);

// Products in this category only
$products = $category->products;

// Products in category and all descendants
$categoryIds = $category->descendants->pluck('id')->push($category->id);

$products = Product::query()
    ->publish()
    ->whereHas('categories', fn ($q) => $q->whereIn('id', $categoryIds))
    ->get();

Disabling Category Feature

// config/shopper/features.php
use Shopper\Enum\FeatureState;

return [
    'category' => FeatureState::Disabled,
];

Components

php artisan shopper:component:publish category
Creates config/shopper/components/category.php:
use Shopper\Livewire;

return [
    'pages' => [
        'category-index' => Livewire\Pages\Category\Index::class,
    ],
    'components' => [
        'slide-overs.category-form' => Livewire\SlideOvers\CategoryForm::class,
        'slide-overs.re-order-categories' => Livewire\SlideOvers\ReOrderCategories::class,
    ],
];

Storefront Example

namespace App\Http\Controllers;

use Shopper\Core\Models\Category;
use Shopper\Core\Models\Product;

class CategoryController extends Controller
{
    public function index()
    {
        $categories = Category::query()
            ->whereNull('parent_id')
            ->enabled()
            ->with(['children' => fn ($q) => $q->enabled()])
            ->withCount('products')
            ->orderBy('position')
            ->get();

        return view('categories.index', compact('categories'));
    }

    public function show(string $slug)
    {
        $category = Category::query()
            ->where('slug', $slug)
            ->enabled()
            ->with('ancestors')
            ->firstOrFail();

        // Get products from category and descendants
        $categoryIds = $category->descendants
            ->pluck('id')
            ->push($category->id);

        $products = Product::query()
            ->publish()
            ->whereHas('categories', fn ($q) => $q->whereIn('id', $categoryIds))
            ->paginate(12);

        return view('categories.show', compact('category', 'products'));
    }
}