codrasil / closurable
Closure Table relational database implementation in PHP Laravel for any hierarchic data
Installs: 590
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 2
Forks: 0
Open Issues: 1
pkg:composer/codrasil/closurable
Requires (Dev)
- orchestra/testbench: ^6.0@dev
- phpunit/phpunit: ^8.3@dev
This package is auto-updated.
Last update: 2025-10-07 21:55:21 UTC
README
Closure Table relational database implementation in PHP for any hierarchical data.Requirements
PHP:^7.xMySQL: any version should be fine.
Installation
This package is originally built for Laravel, but can also be used in any PHP project.
Read more about integration in the docs.
via composer:
composer require codrasil/closurable
Publishing Configuration
Pass in the artisan command the package's service provider:
php artisan vendor:publish --provider="Codrasil\Closurable\NestableServiceProvider"
Setup
Generating Migration File
First, run the console command to generate a nested migration table.
Format:
make:closurable [options] [--] <reference>
Example:
We will be using the users model to generate a nested relationship of users.
php artisan make:closurable users
The command accepts an argument of referenced table. This will be the table to be "closured".
It will generate a table named userstree that comes pre-populated with the necessary columns.
The generated file should look something like below:
Schema::create('userstree', function (Blueprint $table) { $table->unsignedBigInteger('ancestor_id')->index(); $table->unsignedBigInteger('descendant_id')->index(); $table->unsignedBigInteger('depth')->index()->default(0); $table->unsignedBigInteger('root')->index()->default(0); $table->unique(['ancestor_id', 'descendant_id']); $table->index(['ancestor_id', 'descendant_id', 'depth']); $table->index(['descendant_id', 'depth']); $table->index(['depth', 'root']); $table->foreign('ancestor_id') ->references('id') ->on('users') ->onDelete('cascade') ->onUpdate('cascade'); $table->foreign('descendant_id') ->references('id') ->on('users') ->onDelete('cascade') ->onUpdate('cascade'); });
Note that you should generate the migration file of the referenced table yourself before running the command (in the above example, you should generate the migration for the users table yourself).
Note to change the table name of the closure table, pass in an option --table or --create:
php artisan make:closurable users --table=familytree
Run php artisan make:closurable --help for more information on configuring the command.
Model usage
Next, use either of the two options on the model to be closure nested.
Following our example above, the User model should either implement:
-
the trait,
Closurable:use Codrasil\Closurable\Closurable; class User extends Authenticatable { use Closurable; }or
-
via extending the abstract class,
Codrasil\Closurable\Model, instead of the default Illuminate Model class:use Codrasil\Closurable\Model; class User extends Model { // Of course, you will need to reimplement the Authenticatable traits // to the User model if you ARE going to nest the User model. }
Usage
Saving a Branch Node
Let's say we have the following data on our users table:
| ID | Name |
|---|---|
| 1 | Henry Walton Jones, Sr. |
| 2 | Indiana Jones |
| 3 | Susie Jones |
| 4 | Henry Walton Jones III |
And we need the following relationship:
To save the relationships described above, we need to use the closurables() from the User model to access the attach(Model $model) method.
$parent = User::find(1); // Jones, Sr. $junior = User::find(2); // Indy $parent->closurables()->attach($junior); ... $child = User::find(3); // Susie $parent->closurables()->attach($child); ... $child = User::find(4); // Jones III $junior->closurables()->attach($child);
The relationship will be saved in the familytree table as:
| ancestor_id | descendant_id | depth | root |
|---|---|---|---|
| 1 | 1 | 0 | 1 |
| 1 | 2 | 1 | 0 |
| 1 | 3 | 1 | 0 |
| 1 | 4 | 2 | 0 |
| 2 | 2 | 0 | 0 |
| 2 | 4 | 1 | 0 |
| 3 | 3 | 0 | 0 |
| 4 | 4 | 0 | 0 |
Visual representation:
Displaying Root Nodes
-
Root
To display resources without parents, use the
rootsscope:$roots = MyModel::roots()->get();
Querying for Adjacent Relations
By default, sorting is handled via the sort column found in the reference table.
If the sort column is unavailable, it will default to id or whatever $this->getKeyName() will return.
-
Siblings
To retrieve all siblings of a child, use
siblings()method:$child = MyModel::find(2); $siblings = $child->siblings(); // or $child->siblings
- A helper method
->hasSiblings()for checking emptiness is available. - An accessor method
getSiblingsAttributeis available.
- A helper method
-
Next Sibling
To display the next sibling in the
$user->children, usenext()method:$firstChild = MyModel::find(2); $secondChild = $firstChild->next(); // or $firstChild->next
- A helper method
->hasNext()for checking nullness is available. - An accessor method
getNextAttributeis available.
- A helper method
-
Previous Sibling
To display the previous sibling, use
previous()method:$secondChild = MyModel::find(3); $firstChild = $secondChild->previous(); // or $secondChild->previous
- A helper method
->hasPrevious()for checking nullness is available. - An accessor method
getPreviousAttributeis available.
- A helper method
Querying for Lineal Relations
-
Parent
To retrieve the immediate parent of the child, use
parent()method:$child = MyModel::find(2); $parent = $child->parent(); // or $child->parent
- A helper method
->hasParent()for checking nullness is available. - An accessor method
getParentAttributeis available.
- A helper method
-
Children
To display the children nodes of a specific resource, use the
children()method:$user = User::find(1);
{{-- in a blade file --}} @foreach ($user->children() as $child) {{ $child->name }} @endforeach {{-- use @dd($user->children) to see entire collection --}}
- A helper method
->hasChildren()for checking emptiness is available. - An accessor method
getChildrenAttributeis available.
- A helper method
-
Ancestors
To retrieve all parents of the child (and the parent of the child's parent, and so on), use
ancestors()method:$child = MyModel::find(4); $ancestors = $child->ancestors(); // will output the parent([of the parent]*n) + the child. // dd($ancestors) to inspect actual data.
- A helper method
->hasAncestors()for checking emptiness is available. - An accessor method
getAncestorsAttributeis available.
- A helper method
-
Descendants
To retrieve all children of the parent (and the children of the parent's children, and so on), use
descendants()method:$parent = MyModel::find(2); $descendants = $parent->descendants(); // will output the children([of the children]*n) + the parent. // dd($descendants) to inspect actual data.
- A helper method
->hasDescendants()for checking emptiness is available. - An accessor method
getDescendantsAttributeis available.
- A helper method
For more use cases of adjacent and lineal relations, checkout Relations section in the docs.
Documentation & Examples
To learn more about the API, visit the docs folder.
For more example implementation, checkout docs/examples folder. The examples page has a variety of use cases like Commenting System, Taxonomic Ranking of the Animal Kingdom, Family Trees, and more.
License
The library is open-source software licensed under the MIT license.