PHPackages                             angkor/laravel-categories - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. angkor/laravel-categories

ActiveLibrary[Database &amp; ORM](/categories/database)

angkor/laravel-categories
=========================

A fork of rinvex/laravel-categories - A polymorphic Laravel package for category management with Nested Sets, Sluggable, and Translatable support.

7.2.0(1y ago)0535↑33.3%MITPHPPHP ^8.2

Since Jul 3Pushed 1y agoCompare

[ Source](https://github.com/angkordotdev/laravel-categories)[ Packagist](https://packagist.org/packages/angkor/laravel-categories)[ Docs](https://open.angkor.dev)[ RSS](/packages/angkor-laravel-categories/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (12)Versions (3)Used By (0)

Angkor Categories
=================

[](#angkor-categories)

> This package is a fork of [rinvex/laravel-categories](https://github.com/rinvex/laravel-categories). We are grateful to the Rinvex team for their excellent work in creating and maintaining the original package.

**Angkor Categories** is a polymorphic Laravel package for category management. You can categorize any eloquent model with ease, and utilize the power of **[Nested Sets](https://github.com/lazychaser/laravel-nestedset)**, and the awesomeness of **[Sluggable](https://github.com/spatie/laravel-sluggable)**, and **[Translatable](https://github.com/spatie/laravel-translatable)** models out of the box.

[![Packagist](https://camo.githubusercontent.com/c295b0c06717b8826c11814f16ee59406273f7436921208eff3a638eb4a5d4b5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f616e676b6f722f6c61726176656c2d63617465676f726965732e7376673f6c6162656c3d5061636b6167697374267374796c653d666c61742d737175617265)](https://packagist.org/packages/angkor/laravel-categories)[![License](https://camo.githubusercontent.com/f19b45367dfcf6f328866465633b0a8a10e0165e0b3de37d91e932d5df7c7351/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f616e676b6f722f6c61726176656c2d63617465676f726965732e7376673f6c6162656c3d4c6963656e7365267374796c653d666c61742d737175617265)](https://github.com/angkordotdev/laravel-categories/blob/main/LICENSE)

Key Features
------------

[](#key-features)

- 🌳 Nested Sets Implementation
- 🌐 Multi-language Support
- 🔍 SEO-friendly Slugs
- 🔌 Polymorphic Relationships
- ⚡ High Performance Design
- 🛠️ Laravel Integration

Credits
-------

[](#credits)

This package is a fork of the excellent [rinvex/laravel-categories](https://github.com/rinvex/laravel-categories) package created by [Rinvex](https://rinvex.com). We are grateful for their work in creating the original foundation for this package.

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- Laravel &gt;= 10.0

Installation
------------

[](#installation)

1. Install the package via composer:

    ```
    composer require angkor/laravel-categories
    ```
2. Publish resources (migrations and config files):

    ```
    php artisan angkor:publish:categories
    ```
3. Execute migrations via the following command:

    ```
    php artisan angkor:migrate:categories
    ```
4. Done!

Usage
-----

[](#usage)

Usage
-----

[](#usage-1)

To add categories support to your eloquent models simply use `\Angkor\Categories\Traits\Categorizable` trait:

```
use Angkor\Categories\Traits\Categorizable;

class Post extends Model
{
    use Categorizable;
    // ...
}
```

### Manage your categories

[](#manage-your-categories)

Your categories are just normal [eloquent](https://laravel.com/docs/master/eloquent) models, so you can manage them like any other model. The package comes with all the essential tools you need for category management.

> **Notes:** Angkor Categories builds on these excellent packages:
>
> - 🌳 Powerful Nested Sets using [`kalnoy/nestedset`](https://github.com/lazychaser/laravel-nestedset)
> - 🔍 Automatic Slugging using [`spatie/laravel-sluggable`](https://github.com/spatie/laravel-sluggable)
> - 🌐 Translations using [`spatie/laravel-translatable`](https://github.com/spatie/laravel-translatable)

### Working with categorizable models

[](#working-with-categorizable-models)

The API is intutive and very straightforward, so let's give it a quick look:

```
// Get all categories
$allCategories = app('angkor.categories.category')->all();

// Get instance of your model
$post = \App\Models\Post::find(123);

// Get attached categories collection
$post->categories;

// Get attached categories query builder
$post->categories();
```

You can attach categories in various ways:

```
// Single category id
$post->attachCategories(1);

// Multiple category IDs array
$post->attachCategories([1, 2, 5]);

// Multiple category IDs collection
$post->attachCategories(collect([1, 2, 5]));

// Single category model instance
$categoryInstance = app('angkor.categories.category')->first();
$post->attachCategories($categoryInstance);

// Single category slug
$post->attachCategories('test-category');

// Multiple category slugs array
$post->attachCategories(['first-category', 'second-category']);

// Multiple category slugs collection
$post->attachCategories(collect(['first-category', 'second-category']));

// Multiple category model instances
$categoryInstances = app('angkor.categories.category')->whereIn('id', [1, 2, 5])->get();
$post->attachCategories($categoryInstances);
```

> **Notes:**
>
> - The `attachCategories()` method attach the given categories to the model without touching the currently attached categories, while there's the `syncCategories()` method that can detach any records that's not in the given items, this method takes a second optional boolean parameter that's set detaching flag to `true` or `false`.
> - To detach model categories you can use the `detachCategories()` method, which uses **exactly** the same signature as the `attachCategories()` method, with additional feature of detaching all currently attached categories by passing null or nothing to that method as follows: `$post->detachCategories();`.

And as you may have expected, you can check if categories attached:

```
// Single category id
$post->hasAnyCategories(1);

// Multiple category IDs array
$post->hasAnyCategories([1, 2, 5]);

// Multiple category IDs collection
$post->hasAnyCategories(collect([1, 2, 5]));

// Single category model instance
$categoryInstance = app('rinvex.categories.category')->first();
$post->hasAnyCategories($categoryInstance);

// Single category slug
$post->hasAnyCategories('test-category');

// Multiple category slugs array
$post->hasAnyCategories(['first-category', 'second-category']);

// Multiple category slugs collection
$post->hasAnyCategories(collect(['first-category', 'second-category']));

// Multiple category model instances
$categoryInstances = app('rinvex.categories.category')->whereIn('id', [1, 2, 5])->get();
$post->hasAnyCategories($categoryInstances);
```

> **Notes:**
>
> - The `hasAnyCategories()` method check if **ANY** of the given categories are attached to the model. It returns boolean `true` or `false` as a result.
> - Similarly the `hasAllCategories()` method uses **exactly** the same signature as the `hasAnyCategories()` method, but it behaves differently and performs a strict comparison to check if **ALL** of the given categories are attached.

### Advanced usage

[](#advanced-usage)

#### Generate category slugs

[](#generate-category-slugs)

**Angkor Categories** auto generates slugs and auto-detects and inserts default translations for you if not provided. You can also pass them explicitly through the normal eloquent `create` method, as follows:

```
app('angkor.categories.category')->create(['name' => ['en' => 'My New Category'], 'slug' => 'custom-category-slug']);
```

> **Note:** Check **[Sluggable](https://github.com/spatie/laravel-sluggable)** package for further details.

#### Smart parameter detection

[](#smart-parameter-detection)

The category methods are smart enough to handle almost all kinds of inputs as you've seen in the above examples. It will check input type and behave accordingly.

#### Retrieve all models attached to the category

[](#retrieve-all-models-attached-to-the-category)

You may encounter a situation where you need to get all models attached to certain category, you do so with ease as follows:

```
$category = app('rinvex.categories.category')->find(1);
$category->entries(\App\Models\Post::class)->get();
```

#### Query scopes

[](#query-scopes)

The package ships with several useful query scopes for your convenience. Here's how to use them:

```
// Single category id
$post->withAnyCategories(1)->get();

// Multiple category IDs array
$post->withAnyCategories([1, 2, 5])->get();

// Multiple category IDs collection
$post->withAnyCategories(collect([1, 2, 5]))->get();

// Single category model instance
$categoryInstance = app('rinvex.categories.category')->first();
$post->withAnyCategories($categoryInstance)->get();

// Single category slug
$post->withAnyCategories('test-category')->get();

// Multiple category slugs array
$post->withAnyCategories(['first-category', 'second-category'])->get();

// Multiple category slugs collection
$post->withAnyCategories(collect(['first-category', 'second-category']))->get();

// Multiple category model instances
$categoryInstances = app('rinvex.categories.category')->whereIn('id', [1, 2, 5])->get();
$post->withAnyCategories($categoryInstances)->get();
```

> **Notes:**
>
> - The `withAnyCategories()` scope finds posts with **ANY** attached categories of the given. It returns normally a query builder, so you can chain it or call `get()` method for example to execute and get results.
> - Similarly there's few other scopes like `withAllCategories()` that finds posts with **ALL** attached categories of the given, `withoutCategories()` which finds posts without **ANY** attached categories of the given, and lastly `withoutAnyCategories()` which find posts without **ANY** attached categories at all. All scopes are created equal, with same signature, and returns query builder.

#### Category translations

[](#category-translations)

Manage category translations with ease as follows:

```
$category = app('rinvex.categories.category')->find(1);

// Update title translations
$category->setTranslation('name', 'en', 'New English Category Title')->save();

// Alternatively you can use default eloquent update
$category->update([
    'name' => [
        'en' => 'New Category',
        'ar' => 'تصنيف جديد',
    ],
]);

// Get single category translation
$category->getTranslation('name', 'en');

// Get all category translations
$category->getTranslations('name');

// Get category title in default locale
$category->name;
```

> **Note:** Check **[Translatable](https://github.com/spatie/laravel-translatable)** package for further details.

---

Manage your nodes/nestedsets
----------------------------

[](#manage-your-nodesnestedsets)

- [Inserting Categories](#inserting-categories)
    - [Creating categories](#creating-categories)
    - [Making a root from existing category](#making-a-root-from-existing-category)
    - [Appending and prepending to the specified parent](#appending-and-prepending-to-the-specified-parent)
    - [Inserting before or after specified category](#inserting-before-or-after-specified-category)
    - [Building a tree from array](#building-a-tree-from-array)
    - [Rebuilding a tree from array](#rebuilding-a-tree-from-array)
- [Retrieving categories](#retrieving-categories)
    - [Ancestors](#ancestors)
    - [Descendants](#descendants)
    - [Siblings](#siblings)
    - [Getting related models from other table](#getting-related-models-from-other-table)
    - [Including category depth](#including-category-depth)
    - [Default order](#default-order)
        - [Shifting a category](#shifting-a-category)
    - [Constraints](#constraints)
    - [Building a tree](#building-a-tree)
        - [Building flat tree](#building-flat-tree)
        - [Getting a subtree](#getting-a-subtree)
- [Deleting categories](#deleting-categories)
- [Helper methods](#helper-methods)
- [Checking consistency](#checking-consistency)
    - [Fixing tree](#fixing-tree)

### Inserting categories

[](#inserting-categories)

Moving and inserting categories includes several database queries, so **transaction is automatically started**when category is saved. It is safe to use global transaction if you work with several models.

Another important note is that **structural manipulations are deferred** until you hit `save` on model (some methods implicitly call `save` and return boolean result of the operation).

If model is successfully saved it doesn't mean that category was moved. If your application depends on whether the category has actually changed its position, use `hasMoved` method:

```
if ($category->save()) {
    $moved = $category->hasMoved();
}
```

#### Creating categories

[](#creating-categories)

When you simply create a category, it will be appended to the end of the tree:

```
app('rinvex.categories.category')->create($attributes); // Saved as root

$category = app('rinvex.categories.category')->fill($attributes);
$category->save(); // Saved as root
```

In this case the category is considered a *root* which means that it doesn't have a parent.

#### Making a root from existing category

[](#making-a-root-from-existing-category)

The category will be appended to the end of the tree:

```
// #1 Implicit save
$category->saveAsRoot();

// #2 Explicit save
$category->makeRoot()->save();
```

#### Appending and prepending to the specified parent

[](#appending-and-prepending-to-the-specified-parent)

If you want to make category a child of other category, you can make it last or first child. Suppose that `$parent` is some existing category, there are few ways to append a category:

```
// #1 Using deferred insert
$category->appendToNode($parent)->save();

// #2 Using parent category
$parent->appendNode($category);

// #3 Using parent's children relationship
$parent->children()->create($attributes);

// #5 Using category's parent relationship
$category->parent()->associate($parent)->save();

// #6 Using the parent attribute
$category->parent_id = $parent->getKey();
$category->save();

// #7 Using static method
app('rinvex.categories.category')->create($attributes, $parent);
```

And only a couple ways to prepend:

```
// #1 Using deferred insert
$category->prependToNode($parent)->save();

// #2 Using parent category
$parent->prependNode($category);
```

#### Inserting before or after specified category

[](#inserting-before-or-after-specified-category)

You can make `$category` to be a neighbor of the `$neighbor` category. Suppose that `$neighbor` is some existing category, while target category can be fresh. If target category exists, it will be moved to the new position and parent will be changed if it's required.

```
# Explicit save
$category->afterNode($neighbor)->save();
$category->beforeNode($neighbor)->save();

# Implicit save
$category->insertAfterNode($neighbor);
$category->insertBeforeNode($neighbor);
```

#### Building a tree from array

[](#building-a-tree-from-array)

When using static method `create` on category, it checks whether attributes contains `children` key. If it does, it creates more categories recursively, as follows:

```
$category = app('rinvex.categories.category')->create([
    'name' => [
        'en' => 'New Category Title',
    ],

    'children' => [
        [
            'name' => 'Bar',

            'children' => [
                [ 'name' => 'Baz' ],
            ],
        ],
    ],
]);
```

`$category->children` now contains a list of created child categories.

#### Rebuilding a tree from array

[](#rebuilding-a-tree-from-array)

You can easily rebuild a tree. This is useful for mass-changing the structure of the tree. Given the `$data` as an array of categories, you can build the tree as follows:

```
$data = [
    [ 'id' => 1, 'name' => 'foo', 'children' => [ ... ] ],
    [ 'name' => 'bar' ],
];

app('rinvex.categories.category')->rebuildTree($data, $delete);
```

There is an id specified for category with the title of `foo` which means that existing category will be filled and saved. If category does not exists `ModelNotFoundException` is thrown. Also, this category has `children` specified which is also an array of categories; they will be processed in the same manner and saved as children of category `foo`.

Category `bar` has no primary key specified, so it will treated as a new one, and be created.

`$delete` shows whether to delete categories that are already exists but not present in `$data`. By default, categories aren't deleted.

### Retrieving categories

[](#retrieving-categories)

*In some cases we will use an `$id` variable which is an id of the target category.*

#### Ancestors

[](#ancestors)

Ancestors make a chain of parents to the category. Helpful for displaying breadcrumbs to the current category.

```
// #1 Using accessor
$result = $category->getAncestors();

// #2 Using a query
$result = $category->ancestors()->get();

// #3 Getting ancestors by primary key
$result = app('rinvex.categories.category')->ancestorsOf($id);
```

#### Descendants

[](#descendants)

Descendants are all categories in a sub tree, i.e. children of category, children of children, etc.

```
// #1 Using relationship
$result = $category->descendants;

// #2 Using a query
$result = $category->descendants()->get();

// #3 Getting descendants by primary key
$result = app('rinvex.categories.category')->descendantsOf($id);

// #3 Get descendants and the category by id
$result = app('rinvex.categories.category')->descendantsAndSelf($id);
```

Descendants can be eagerly loaded:

```
$categories = app('rinvex.categories.category')->with('descendants')->whereIn('id', $idList)->get();
```

#### Siblings

[](#siblings)

Siblings are categories that have same parent.

```
$result = $category->getSiblings();

$result = $category->siblings()->get();
```

To get only next siblings:

```
// Get a sibling that is immediately after the category
$result = $category->getNextSibling();

// Get all siblings that are after the category
$result = $category->getNextSiblings();

// Get all siblings using a query
$result = $category->nextSiblings()->get();
```

To get previous siblings:

```
// Get a sibling that is immediately before the category
$result = $category->getPrevSibling();

// Get all siblings that are before the category
$result = $category->getPrevSiblings();

// Get all siblings using a query
$result = $category->prevSiblings()->get();
```

#### Getting related models from other table

[](#getting-related-models-from-other-table)

Imagine that each category `has many` products. I.e. `HasMany` relationship is established. How can you get all products of `$category` and every its descendant? Easy!

```
// Get ids of descendants
$categories = $category->descendants()->pluck('id');

// Include the id of category itself
$categories[] = $category->getKey();

// Get products
$goods = Product::whereIn('category_id', $categories)->get();
```

Now imagine that each category `has many` posts. I.e. `morphToMany` relationship is established this time. How can you get all posts of `$category` and every its descendant? Is that even possible?! Sure!

```
// Get ids of descendants
$categories = $category->descendants()->pluck('id');

// Include the id of category itself
$categories[] = $category->getKey();

// Get posts
$posts = \App\Models\Post::withCategories($categories)->get();
```

#### Including category depth

[](#including-category-depth)

If you need to know at which level the category is:

```
$result = app('rinvex.categories.category')->withDepth()->find($id);

$depth = $result->depth;
```

Root category will be at level 0. Children of root categories will have a level of 1, etc. To get categories of specified level, you can apply `having` constraint:

```
$result = app('rinvex.categories.category')->withDepth()->having('depth', '=', 1)->get();
```

#### Default order

[](#default-order)

Each category has it's own unique `_lft` value that determines its position in the tree. If you want category to be ordered by this value, you can use `defaultOrder` method on the query builder:

```
// All categories will now be ordered by lft value
$result = app('rinvex.categories.category')->defaultOrder()->get();
```

You can get categories in reversed order:

```
$result = app('rinvex.categories.category')->reversed()->get();
```

##### Shifting a category

[](#shifting-a-category)

To shift category up or down inside parent to affect default order:

```
$bool = $category->down();
$bool = $category->up();

// Shift category by 3 siblings
$bool = $category->down(3);
```

The result of the operation is boolean value of whether the category has changed its position.

#### Constraints

[](#constraints)

Various constraints that can be applied to the query builder:

- **whereIsRoot()** to get only root categories;
- **whereIsAfter($id)** to get every category (not just siblings) that are after a category with specified id;
- **whereIsBefore($id)** to get every category that is before a category with specified id.

Descendants constraints:

```
$result = app('rinvex.categories.category')->whereDescendantOf($category)->get();
$result = app('rinvex.categories.category')->whereNotDescendantOf($category)->get();
$result = app('rinvex.categories.category')->orWhereDescendantOf($category)->get();
$result = app('rinvex.categories.category')->orWhereNotDescendantOf($category)->get();

// Include target category into result set
$result = app('rinvex.categories.category')->whereDescendantOrSelf($category)->get();
```

Ancestor constraints:

```
$result = app('rinvex.categories.category')->whereAncestorOf($category)->get();
```

`$category` can be either a primary key of the model or model instance.

#### Building a tree

[](#building-a-tree)

After getting a set of categories, you can convert it to tree. For example:

```
$tree = app('rinvex.categories.category')->get()->toTree();
```

This will fill `parent` and `children` relationships on every category in the set and you can render a tree using recursive algorithm:

```
$categories = app('rinvex.categories.category')->get()->toTree();

$traverse = function ($categories, $prefix = '-') use (&$traverse) {
    foreach ($categories as $category) {
        echo PHP_EOL.$prefix.' '.$category->name;

        $traverse($category->children, $prefix.'-');
    }
};

$traverse($categories);
```

This will output something like this:

```
- Root
-- Child 1
--- Sub child 1
-- Child 2
- Another root

```

##### Building flat tree

[](#building-flat-tree)

Also, you can build a flat tree: a list of categories where child categories are immediately after parent category. This is helpful when you get categories with custom order (i.e. alphabetically) and don't want to use recursion to iterate over your categories.

```
$categories = app('rinvex.categories.category')->get()->toFlatTree();
```

##### Getting a subtree

[](#getting-a-subtree)

Sometimes you don't need whole tree to be loaded and just some subtree of specific category:

```
$root = app('rinvex.categories.category')->find($rootId);
$tree = $root->descendants->toTree($root);
```

Now `$tree` contains children of `$root` category.

If you don't need `$root` category itself, do following instead:

```
$tree = app('rinvex.categories.category')->descendantsOf($rootId)->toTree($rootId);
```

### Deleting categories

[](#deleting-categories)

To delete a category:

```
$category->delete();
```

**IMPORTANT!** Any descendant that category has will also be **deleted**!

**IMPORTANT!** Categories are required to be deleted as models, **don't** try do delete them using a query like so:

```
app('rinvex.categories.category')->where('id', '=', $id)->delete();
```

**That will break the tree!**

### Helper methods

[](#helper-methods)

```
// Check if category is a descendant of other category
$bool = $category->isDescendantOf($parent);

// Check whether the category is a root:
$bool = $category->isRoot();

// Other checks
$category->isChildOf($other);
$category->isAncestorOf($other);
$category->isSiblingOf($other);
```

### Checking consistency

[](#checking-consistency)

You can check whether a tree is broken (i.e. has some structural errors):

```
// Check if tree is broken
$bool = app('rinvex.categories.category')->isBroken();

// Get tree error statistics
$data = app('rinvex.categories.category')->countErrors();
```

Tree error statistics will return an array with following keys:

- `oddness` -- the number of categories that have wrong set of `lft` and `rgt` values
- `duplicates` -- the number of categories that have same `lft` or `rgt` values
- `wrong_parent` -- the number of categories that have invalid `parent_id` value that doesn't correspond to `lft` and `rgt` values
- `missing_parent` -- the number of categories that have `parent_id` pointing to category that doesn't exists

#### Fixing tree

[](#fixing-tree)

Category tree can now be fixed if broken. Using inheritance info from `parent_id` column, proper `_lft` and `_rgt` values are set for every category.

```
app('rinvex.categories.category')->fixTree();
```

> **Note:** Check **[Nested Sets](https://github.com/lazychaser/laravel-nestedset)** package for further details.

Changelog
---------

[](#changelog)

Refer to the [Changelog](CHANGELOG.md) for a full history of the project.

Support
-------

[](#support)

For support, please use the following channels:

- [GitHub Issues](https://github.com/angkor/laravel-categories/issues)
- [GitHub Discussions](https://github.com/angkor/laravel-categories/discussions)
- [Email Support](mailto:support@angkor.dev)

Contributing &amp; Protocols
----------------------------

[](#contributing--protocols)

Thank you for considering contributing to this project! The contribution guide can be found in [CONTRIBUTING.md](CONTRIBUTING.md).

Bug reports, feature requests, and pull requests are very welcome.

- [Versioning](CONTRIBUTING.md#versioning)
- [Pull Requests](CONTRIBUTING.md#pull-requests)
- [Coding Standards](CONTRIBUTING.md#coding-standards)
- [Feature Requests](CONTRIBUTING.md#feature-requests)
- [Git Flow](CONTRIBUTING.md#git-flow)

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

We take security seriously. If you discover a security vulnerability within Angkor Categories, please send an email to . All security vulnerabilities will be promptly addressed.

Please do not publicly disclose the issue until it has been addressed by our team. We appreciate your responsible disclosure and will work with you to understand and resolve any concerns.

About Rinvex
------------

[](#about-rinvex)

Rinvex is a software solutions startup, specialized in integrated enterprise solutions for SMEs established in Alexandria, Egypt since June 2016. We believe that our drive The Value, The Reach, and The Impact is what differentiates us and unleash the endless possibilities of our philosophy through the power of software. We like to call it Innovation At The Speed Of Life. That’s how we do our share of advancing humanity.

License
-------

[](#license)

This software is released under [The MIT License (MIT)](LICENSE).

Original Package Copyright (c) 2016-2022 Rinvex LLC Modified Package Copyright (c) 2023 Angkor Team

This package is a fork of [rinvex/laravel-categories](https://github.com/rinvex/laravel-categories).

###  Health Score

36

—

LowBetter than 82% of packages

Maintenance49

Moderate activity, may be stable

Popularity17

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity57

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 98.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

373d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b26c90a41092eff8e5a83d3790a63cff5eef411291844571601487148e46a0b8?d=identicon)[angkor](/maintainers/angkor)

---

Top Contributors

[![Omranic](https://avatars.githubusercontent.com/u/406705?v=4)](https://github.com/Omranic "Omranic (339 commits)")[![Rattone](https://avatars.githubusercontent.com/u/7362607?v=4)](https://github.com/Rattone "Rattone (2 commits)")[![semsphy](https://avatars.githubusercontent.com/u/423396?v=4)](https://github.com/semsphy "semsphy (2 commits)")[![dependabot-preview[bot]](https://avatars.githubusercontent.com/in/2141?v=4)](https://github.com/dependabot-preview[bot] "dependabot-preview[bot] (1 commits)")[![khalid3bdallah](https://avatars.githubusercontent.com/u/6986761?v=4)](https://github.com/khalid3bdallah "khalid3bdallah (1 commits)")

---

Tags

laravelmodeleloquentsluggabletranslatablenested-setcategorytaxonomypolymorphiccategorizableangkor

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/angkor-laravel-categories/health.svg)

```
[![Health](https://phpackages.com/badges/angkor-laravel-categories/health.svg)](https://phpackages.com/packages/angkor-laravel-categories)
```

###  Alternatives

[rinvex/laravel-categories

Rinvex Categories is a polymorphic Laravel package, for category management. You can categorize any eloquent model with ease, and utilize the power of Nested Sets, and the awesomeness of Sluggable, and Translatable models out of the box.

470161.6k3](/packages/rinvex-laravel-categories)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[rinvex/laravel-tenants

Rinvex Tenants is a contextually intelligent polymorphic Laravel package, for single db multi-tenancy. You can completely isolate tenants data with ease using the same database, with full power and control over what data to be centrally shared, and what to be tenant related and therefore isolated from others.

823.5k10](/packages/rinvex-laravel-tenants)[baril/bonsai

An implementation of the Closure Tables pattern for Eloquent.

3593.5k](/packages/baril-bonsai)[toponepercent/baum

Baum is an implementation of the Nested Set pattern for Eloquent models.

3154.7k](/packages/toponepercent-baum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
