Git Product home page Git Product logo

laravel-adjacency-list's Introduction

CI Code Coverage Scrutinizer Code Quality Latest Stable Version Total Downloads License

Introduction

This Laravel Eloquent extension provides recursive relationships using common table expressions (CTE).

Supports Laravel 5.5.29+.

Compatibility

  • MySQL 8.0+
  • MariaDB 10.2+
  • PostgreSQL 9.4+
  • SQLite 3.8.3+
  • SQL Server 2008+

Installation

composer require staudenmeir/laravel-adjacency-list:"^1.0"

Usage

Getting Started

Consider the following table schema for hierarchical data:

Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->unsignedBigInteger('parent_id')->nullable();
});

Use the HasRecursiveRelationships trait in your model to work with recursive relationships:

class User extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
}

By default, the trait expects a parent key named parent_id. You can customize it by overriding getParentKeyName():

class User extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
    
    public function getParentKeyName()
    {
        return 'parent_id';
    }
}

By default, the trait uses the model's primary key as the local key. You can customize it by overriding getLocalKeyName():

class User extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
    
    public function getLocalKeyName()
    {
        return 'id';
    }
}

Included Relationships

The trait provides various relationships:

  • ancestors(): The model's recursive parents.
  • ancestorsAndSelf(): The model's recursive parents 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 = User::find($id)->ancestors;

$users = User::with('descendants')->get();

$users = User::whereHas('siblings', function ($query) {
    $query->where('name', '=', 'John');
})->get();

$total = User::find($id)->descendants()->count();

User::find($id)->descendants()->update(['active' => false]);

User::find($id)->siblings()->delete();

Custom Relationships

You can also define custom relationships to retrieve related models recursively.

Consider a HasMany relationship between User and Post:

class User extends Model
{
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}

Define a HasManyOfDescendants relationship to get all posts of a user and its descendants:

class User extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;

    public function recursivePosts()
    {
        return $this->hasManyOfDescendantsAndSelf('App\Post');
    }
}

$recursivePosts = User::find($id)->recursivePosts;

$users = User::withCount('recursivePosts')->get();

Use hasManyOfDescendants() to only get the descendants' posts:

class User extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;

    public function descendantPosts()
    {
        return $this->hasManyOfDescendants('App\Post');
    }
}

If you are using the package outside of Laravel or have disabled package discovery for staudenmeir/laravel-cte, you need to add support for common table expressions to the related model:

class Post extends Model
{
    use \Staudenmeir\LaravelCte\Eloquent\QueriesExpressions;
}

Trees

The trait provides the tree() query scope to get all models, beginning at the root(s):

$tree = User::tree()->get();

treeOf() allows you to query trees with custom constraints for the root model(s). Consider a table with multiple separate lists:

$constraint = function ($query) {
    $query->whereNull('parent_id')->where('list_id', 1);
};

$tree = User::treeOf($constraint)->get();

Filters

The trait provides query scopes to filter models by their position in the tree:

  • hasChildren(): Models with children.
  • hasParent(): Models with a parent.
  • isLeaf(): Models without children.
  • isRoot(): Models without a parent.
$noLeaves = User::hasChildren()->get();

$noRoots = User::hasParent()->get();

$leaves = User::isLeaf()->get();

$roots = User::isRoot()->get();

Order

The trait provides query scopes to order models breadth-first or depth-first:

  • breadthFirst(): Get siblings before children.
  • depthFirst(): Get children before siblings.
$tree = User::tree()->breadthFirst()->get();

$descendants = User::find($id)->descendants()->depthFirst()->get();

Depth

The results of ancestor, descendant and tree queries include an additional depth column.

It contains the model's depth relative to the query's parent. The depth is positive for descendants and negative for ancestors:

$descendantsAndSelf = User::find($id)->descendantsAndSelf()->depthFirst()->get();

echo $descendantsAndSelf[0]->depth; // 0
echo $descendantsAndSelf[1]->depth; // 1
echo $descendantsAndSelf[2]->depth; // 2

You can customize the column name by overriding getDepthName():

class User extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;

    public function getDepthName()
    {
        return 'depth';
    }
}

Depth Constraints

You can use the whereDepth() query scope to filter models by their relative depth:

$descendants = User::find($id)->descendants()->whereDepth(2)->get();

$descendants = User::find($id)->descendants()->whereDepth('<', 3)->get();

Queries with whereDepth() constraints that limit the maximum depth still build the entire (sub)tree internally. Both tree scopes allow you to provide a maximum depth that improves query performance by only building the requested section of the tree:

$tree = User::tree(3)->get();

$tree = User::treeOf($constraint, 3)->get();

Path

The results of ancestor, descendant and tree queries include an additional path column.

It contains the dot-separated path of local keys from the query's parent to the model:

$descendantsAndSelf = User::find(1)->descendantsAndSelf()->depthFirst()->get();

echo $descendantsAndSelf[0]->path; // 1
echo $descendantsAndSelf[1]->path; // 1.2
echo $descendantsAndSelf[2]->path; // 1.2.3

You can customize the column name and the separator by overriding the respective methods:

class User extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;

    public function getPathName()
    {
        return 'path';
    }

    public function getPathSeparator()
    {
        return '.';
    }
}

Custom Paths

You can add custom path columns to the query results:

class User extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;

    public function getCustomPaths()
    {
        return [
            [
                'name' => 'slug_path',
                'column' => 'slug',
                'separator' => '/',
            ],
        ];
    }
}

$descendantsAndSelf = User::find(1)->descendantsAndSelf;

echo $descendantsAndSelf[0]->slug_path; // user-1
echo $descendantsAndSelf[1]->slug_path; // user-1/user-2
echo $descendantsAndSelf[2]->slug_path; // user-1/user-2/user-3

Contributing

Please see CONTRIBUTING and CODE OF CONDUCT for details.

laravel-adjacency-list's People

Contributors

staudenmeir avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.