codrasil/closurable

Closure Table relational database implementation in PHP Laravel for any hierarchic data

v1.1.1 2020-05-07 09:12 UTC

This package is auto-updated.

Last update: 2024-05-07 18:51:45 UTC


README

Latest Stable Version Total Downloads Latest Unstable Version License

closurables.tree.svg

Closure Table relational database implementation in PHP for any hierarchical data.

Requirements

  • PHP: ^7.x
  • MySQL: 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:

closurables.sample2.svg

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:

closurables.sample1.svg

Displaying Root Nodes
  • Root

    To display resources without parents, use the roots scope:

    $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 getSiblingsAttribute is available.
  • Next Sibling

    To display the next sibling in the $user->children, use next() method:

    $firstChild = MyModel::find(2);
    $secondChild = $firstChild->next(); // or $firstChild->next
    • A helper method ->hasNext() for checking nullness is available.
    • An accessor method getNextAttribute is available.
  • 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 getPreviousAttribute is available.

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 getParentAttribute is available.
  • 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 getChildrenAttribute is available.
  • 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 getAncestorsAttribute is available.
  • 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 getDescendantsAttribute is available.

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.