Adjacency List’ed Closure Table database design pattern implementation for the Laravel framework.

Overview

ClosureTable

Build Status Latest Release Total Downloads

This is a database manipulation package for the Laravel 5.4+ framework. You may want to use it when you need to store and operate hierarchical data in your database. The package is an implementation of a well-known design pattern called closure table. However, in order to simplify and optimize SQL SELECT queries, it uses adjacency lists to query direct parent/child relationships.

Contents:

Installation

It's strongly recommended to use Composer to install the package:

$ composer require franzose/closure-table

If you use Laravel 5.5+, the package's service provider is automatically registered for you thanks to the package auto-discovery feature. Otherwise, you have to manually add it to your config/app.php:

<?php

return [
    'providers' => [
        Franzose\ClosureTable\ClosureTableServiceProvider::class
    ]
];

Setup

In a basic scenario, you can simply run the following command:

$ php artisan closuretable:make Node

Where Node is the name of the entity model. This is what you get from running the above:

  1. Two models in the app directory: App\Node and App\NodeClosure
  2. A new migration in the database/migrations directory

As you can see, the command requires a single argument, name of the entity model. However, it accepts several options in order to provide some sort of customization:

Option Alias Meaning
namespace ns Custom namespace for generated models. Keep in mind that the given namespace will override model namespaces: php artisan closuretable:make Foo\\Node --namespace=Qux --closure=Bar\\NodeTree will generate Qux\Node and Qux\NodeTree models.
entity-table et Database table name for the entity model
closure c Class name for the closure model
closure-table ct Database table name for the closure model
models-path mdl Directory in which to put generated models
migrations-path mgr Directory in which to put generated migrations
use-innodb i This flag will tell the generator to set database engine to InnoDB. Useful only if you use MySQL

Requirements

You have to keep in mind that, by design of this package, the models/tables have a required minimum of attributes/columns:

Entity
Attribute/Column Customized by Meaning
parent_id Entity::getParentIdColumn() ID of the node's immediate parent, simplifies queries for immediate parent/child nodes.
position Entity::getPositionColumn() Node position, allows to order nodes of the same depth level
ClosureTable
Attribute/Column Customized by Meaning
id
ancestor ClosureTable::getAncestorColumn() Parent (self, immediate, distant) node ID
descendant ClosureTable::getDescendantColumn() Child (self, immediate, distant) node ID
depth ClosureTable::getDepthColumn() Current nesting level, 0+

Examples

In the examples, let's assume that we've set up a Node model which extends the Franzose\ClosureTable\Models\Entity model.

Scopes

Since ClosureTable 6, a lot of query scopes have become available in the Entity model:

ancestors()
ancestorsOf($id)
ancestorsWithSelf()
ancestorsWithSelfOf($id)
descendants()
descendantsOf($id)
descendantsWithSelf()
descendantsWithSelfOf($id)
childNode()
childNodeOf($id)
childAt(int $position)
childOf($id, int $position)
firstChild()
firstChildOf($id)
lastChild()
lastChildOf($id)
childrenRange(int $from, int $to = null)
childrenRangeOf($id, int $from, int $to = null)
sibling()
siblingOf($id)
siblings()
siblingsOf($id)
neighbors()
neighborsOf($id)
siblingAt(int $position)
siblingOfAt($id, int $position)
firstSibling()
firstSiblingOf($id)
lastSibling()
lastSiblingOf($id)
prevSibling()
prevSiblingOf($id)
prevSiblings()
prevSiblingsOf($id)
nextSibling()
nextSiblingOf($id)
nextSiblings()
nextSiblingsOf($id)
siblingsRange(int $from, int $to = null)
siblingsRangeOf($id, int $from, int $to = null)

You can learn how to use query scopes from the Laravel documentation.

Parent/Root

<?php
$nodes = [
    new Node(['id' => 1]),
    new Node(['id' => 2]),
    new Node(['id' => 3]),
    new Node(['id' => 4, 'parent_id' => 1])
];

foreach ($nodes as $node) {
    $node->save();
}

Node::getRoots()->pluck('id')->toArray(); // [1, 2, 3]
Node::find(1)->isRoot(); // true
Node::find(1)->isParent(); // true
Node::find(4)->isRoot(); // false
Node::find(4)->isParent(); // false

// make node 4 a root at the fourth position (1 => 0, 2 => 1, 3 => 2, 4 => 3)
$node = Node::find(4)->makeRoot(3);
$node->isRoot(); // true
$node->position; // 3

Node::find(4)->moveTo(0, Node::find(2)); // same as Node::find(4)->moveTo(0, 2);
Node::find(2)->getChildren()->pluck('id')->toArray(); // [4]

Ancestors

<?php
$nodes = [
    new Node(['id' => 1]),
    new Node(['id' => 2, 'parent_id' => 1]),
    new Node(['id' => 3, 'parent_id' => 2]),
    new Node(['id' => 4, 'parent_id' => 3])
];

foreach ($nodes as $node) {
    $node->save();
}

Node::find(4)->getAncestors()->pluck('id')->toArray(); // [1, 2, 3]
Node::find(4)->countAncestors(); // 3
Node::find(4)->hasAncestors(); // true
Node::find(4)->ancestors()->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3];
Node::find(4)->ancestorsWithSelf()->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3, 4];
Node::ancestorsOf(4)->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3];
Node::ancestorsWithSelfOf(4)->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3, 4];

There are several methods that have been deprecated since ClosureTable 6:

-Node::find(4)->getAncestorsTree();
+Node::find(4)->getAncestors()->toTree();

-Node::find(4)->getAncestorsWhere('id', '>', 1);
+Node::find(4)->ancestors()->where('id', '>', 1)->get();

Descendants

<?php
$nodes = [
    new Node(['id' => 1]),
    new Node(['id' => 2, 'parent_id' => 1]),
    new Node(['id' => 3, 'parent_id' => 2]),
    new Node(['id' => 4, 'parent_id' => 3])
];

foreach ($nodes as $node) {
    $node->save();
}

Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3, 4]
Node::find(1)->countDescendants(); // 3
Node::find(1)->hasDescendants(); // true
Node::find(1)->descendants()->where('id', '<', 4)->get()->pluck('id')->toArray(); // [2, 3];
Node::find(1)->descendantsWithSelf()->where('id', '<', 4)->get()->pluck('id')->toArray(); // [1, 2, 3];
Node::descendantsOf(1)->where('id', '<', 4)->get()->pluck('id')->toArray(); // [2, 3];
Node::descendantsWithSelfOf(1)->where('id', '<', 4)->get()->pluck('id')->toArray(); // [1, 2, 3];

There are several methods that have been deprecated since ClosureTable 6:

-Node::find(4)->getDescendantsTree();
+Node::find(4)->getDescendants()->toTree();

-Node::find(4)->getDescendantsWhere('foo', '=', 'bar');
+Node::find(4)->descendants()->where('foo', '=', 'bar')->get();

Children

<?php
$nodes = [
    new Node(['id' => 1]),
    new Node(['id' => 2, 'parent_id' => 1]),
    new Node(['id' => 3, 'parent_id' => 1]),
    new Node(['id' => 4, 'parent_id' => 1]),
    new Node(['id' => 5, 'parent_id' => 1]),
    new Node(['id' => 6, 'parent_id' => 2]),
    new Node(['id' => 7, 'parent_id' => 3])
];

foreach ($nodes as $node) {
    $node->save();
}

Node::find(1)->getChildren()->pluck('id')->toArray(); // [2, 3, 4, 5]
Node::find(1)->countChildren(); // 3
Node::find(1)->hasChildren(); // true

// get child at the second position (positions start from zero)
Node::find(1)->getChildAt(1)->id; // 3

Node::find(1)->getChildrenRange(1)->pluck('id')->toArray(); // [3, 4, 5]
Node::find(1)->getChildrenRange(0, 2)->pluck('id')->toArray(); // [2, 3, 4]

Node::find(1)->getFirstChild()->id; // 2
Node::find(1)->getLastChild()->id; // 5

Node::find(6)->countChildren(); // 0
Node::find(6)->hasChildren(); // false

Node::find(6)->addChild(new Node(['id' => 7]));

Node::find(1)->addChildren([new Node(['id' => 8]), new Node(['id' => 9])], 2);
Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1, 8 => 2, 9 => 3, 4 => 4, 5 => 5]

// remove child by its position
Node::find(1)->removeChild(2);
Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1, 9 => 2, 4 => 3, 5 => 4]

Node::find(1)->removeChildren(2, 4);
Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1]

Siblings

<?php
$nodes = [
    new Node(['id' => 1]),
    new Node(['id' => 2, 'parent_id' => 1]),
    new Node(['id' => 3, 'parent_id' => 1]),
    new Node(['id' => 4, 'parent_id' => 1]),
    new Node(['id' => 5, 'parent_id' => 1]),
    new Node(['id' => 6, 'parent_id' => 1]),
    new Node(['id' => 7, 'parent_id' => 1])
];

foreach ($nodes as $node) {
    $node->save();
}

Node::find(7)->getFirstSibling()->id; // 2
Node::find(7)->getSiblingAt(0); // 2
Node::find(2)->getLastSibling(); // 7
Node::find(7)->getPrevSibling()->id; // 6
Node::find(7)->getPrevSiblings()->pluck('id')->toArray(); // [2, 3, 4, 5, 6]
Node::find(7)->countPrevSiblings(); // 5
Node::find(7)->hasPrevSiblings(); // true

Node::find(2)->getNextSibling()->id; // 3
Node::find(2)->getNextSiblings()->pluck('id')->toArray(); // [3, 4, 5, 6, 7]
Node::find(2)->countNextSiblings(); // 5
Node::find(2)->hasNextSiblings(); // true

Node::find(3)->getSiblings()->pluck('id')->toArray(); // [2, 4, 5, 6, 7]
Node::find(3)->getNeighbors()->pluck('id')->toArray(); // [2, 4]
Node::find(3)->countSiblings(); // 5
Node::find(3)->hasSiblings(); // true

Node::find(2)->getSiblingsRange(2)->pluck('id')->toArray(); // [4, 5, 6, 7]
Node::find(2)->getSiblingsRange(2, 4)->pluck('id')->toArray(); // [4, 5, 6]

Node::find(4)->addSibling(new Node(['id' => 8]));
Node::find(4)->getNextSiblings()->pluck('id')->toArray(); // [5, 6, 7, 8]

Node::find(4)->addSibling(new Node(['id' => 9]), 1);
Node::find(1)->getChildren()->pluck('position', 'id')->toArray();
// [2 => 0, 9 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 6, 8 => 7]

Node::find(8)->addSiblings([new Node(['id' => 10]), new Node(['id' => 11])]);
Node::find(1)->getChildren()->pluck('position', 'id')->toArray();
// [2 => 0, 9 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 6, 8 => 7, 10 => 8, 11 => 9]

Node::find(2)->addSiblings([new Node(['id' => 12]), new Node(['id' => 13])], 3);
Node::find(1)->getChildren()->pluck('position', 'id')->toArray();
// [2 => 0, 9 => 1, 3 => 2, 12 => 3, 13 => 4, 4 => 5, 5 => 6, 6 => 7, 7 => 8, 8 => 9, 10 => 10, 11 => 11]

Tree

<?php
Node::createFromArray([
    'id' => 1,
    'children' => [
        [
            'id' => 2,
            'children' => [
                [
                    'id' => 3,
                    'children' => [
                        [
                            'id' => 4,
                            'children' => [
                                [
                                    'id' => 5,
                                    'children' => [
                                        [
                                            'id' => 6,
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
]);

Node::find(4)->deleteSubtree();
Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3, 4]

Node::find(4)->deleteSubtree(true);
Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3]

There are several methods that have been deprecated since ClosureTable 6:

-Node::getTree();
-Node::getTreeByQuery(...);
-Node::getTreeWhere('foo', '=', 'bar');
+Node::where('foo', '=', 'bar')->get()->toTree();

Collection methods

This library uses an extended collection class which offers some convenient methods:

<?php
Node::createFromArray([
    'id' => 1,
    'children' => [
        ['id' => 2],
        ['id' => 3],
        ['id' => 4],
        ['id' => 5],
        [
            'id' => 6,
            'children' => [
                ['id' => 7],
                ['id' => 8],
            ]
        ],
    ]
]);

/** @var Franzose\ClosureTable\Extensions\Collection $children */
$children = Node::find(1)->getChildren();
$children->getChildAt(1)->id; // 3
$children->getFirstChild()->id; // 2
$children->getLastChild()->id; // 6
$children->getRange(1)->pluck('id')->toArray(); // [3, 4, 5, 6]
$children->getRange(1, 3)->pluck('id')->toArray(); // [3, 4, 5]
$children->getNeighbors(2)->pluck('id')->toArray(); // [3, 5]
$children->getPrevSiblings(2)->pluck('id')->toArray(); // [2, 3]
$children->getNextSiblings(2)->pluck('id')->toArray(); // [5, 6]
$children->getChildrenOf(4)->pluck('id')->toArray(); // [7, 8]
$children->hasChildren(4); // true
$tree = $children->toTree();
Comments
  • Tree only shows upper level

    Tree only shows upper level

    First of all I want to add that I'm not sure if my data is correct. There's a few things I noticed when creating my items in the database…

    Entries in grades_closure were created, but when moving an item to a parent, only the first one succeeded without error. Every other item added under the same parent throws an error because it tried to add a signed value to the position column.

    Let's say there was a sibling under a parent with position 0, whenever I moved another sibling under the same parent, it would try to add it with a position -1

    It did however end up adding the new item to the database and the position seems to end up ok.

    The second thing I've noticed is that whenever an item was moved to the parent (let's say an item with id 10 was moved to a parent with id 20), 2 entries existed in grades_closure:

    ancestor 10, descendant 10, depth 0
    ancestor 20, descendant 10, depth 1
    

    When requesting the tree with these values in the database, loading was very slow (about a second or two) while there are less than 100 items in the database. On top of that, it showed no tree hierarchy.

    When removing these items in grades_closure that referenced themselves, load time when requesting the entire tree was OK again, but now it only showed the parents. From the documentation, I get the impression that it should retrieve the entire tree, parents + siblings.

    However, requesting the children of a specific parent works without problems, and I can also request the parent of a given child.

    Am I doing something wrong or is this intended behaviour? Also, the documentation left me guessing a bit as to how I go about creating the actual data, I'm sure it's obvious enough when it works, but when something fails, I was left guessing if I went about it the wrong way.

    I'd be happy to add to the documentation if I know the correct approach.

    For your convenience, here's an sql dump of the two tables: https://dl.dropboxusercontent.com/u/139621/30-09-13_grades.sql

    opened by ben-joostens 26
  • Automatic tree structure generation from passed array

    Automatic tree structure generation from passed array

    Okay, as you asked me to further explain my needs and also suggested to @kapooostin to create a new issue, I'll do this now. (here is the source: https://github.com/franzose/ClosureTable/issues/26)

    So, when I create a new node/entity I wanna specify the direct parent and the package should create all the corresponding closure-table (or tree)-entries completely automatically.

    That for, I had that solution: My entities or "nodes"-table had a "parent_id"-column.

    // Parent-ID
    $table->integer('parent_id')->unsigned()->nullable();
    $table->foreign('parent_id')->references('id')->on('nodes');
    

    Any my corresponding model had this boot()-method:

    public static function boot()
        {
            parent::boot();
            static::created(function($model)
            {
                $model->createTreeEntry();
                $model->save();
            });
        }
    

    And this is the "createTreeEntry()"-method:

    /**
    * Creates all the necessary entries in the NodeTree
    */
    public function createTreeEntry()
    {
        $tree = static::$nodeTree;
        $ancestors = array_reverse($this->getAncestors());
    
        foreach($ancestors as $ancestor)
        {
            $tree::create(
                array(
                    'ancestor' => $ancestor->id,
                    'descendant' => $this->id,
                    'level' => $this->calculateLevel()              
                )
            );
        }
    }
    

    So, whenever I created a new node, I specified it's immediate parent (or null if it was a root) and then all the corresponding entries where created completely automatically.

    like this:

    Node::create(
    'parent_id' => '7'
    'description' => 'Child of node with id 7 (contact)'
    );
    

    That´s something I really find convenient and need, but I haven't yet figured out on how I would implement this into "ClosureTable 2" since you took a different approach on a few areas.

    Would be great if we could find a way to implement this automatic approach.

    Kindest regards, The Dancing Bard

    feature 
    opened by ghost 24
  • Example of tables structure

    Example of tables structure

    Hi, I have never used closure tables before, and I am considering to use your class in my new Laravel project. I am a bit confused about which columns must be in each of my tables. Could you provide an example migration, please?

    bug 
    opened by parrker 24
  • [Tests] Errors with prepareTestedSiblings() lead to non execution of tests

    [Tests] Errors with prepareTestedSiblings() lead to non execution of tests

    There's something wrong with the return value of prepareTestedSiblings() because everytime it's called i get a InvalidArgumentException: Value must be provided. error message. I have an issue with xdebug and i can't dig deeper into this at the moment, anyway i noticed that if there's an error a test execution is halted, because a $this->assertEquals(true, false) dropped below the assignment is never evaluated.

    bug 
    opened by vjandrea 21
  • ::roots() return SQL error

    ::roots() return SQL error

    ::roots() returns SQL error :

    SQLSTATE[42703]: Undefined column: 7 ERREUR: la colonne « parentid » n'existe pas LINE 1: ...ies_closure WHERE categories_closure.descendant = parentId A... ^ (SQL: select distinct "categories"., "categories_closure"."ancestor" as "parentId" from "categories" inner join "categories_closure" on "categories_closure"."ancestor" = "categories"."id" and "categories_closure"."descendant" = "categories"."id" where "categories"."deleted_at" is null group by "categories"."id" having (SELECT COUNT() FROM categories_closure WHERE categories_closure.descendant = parentId AND categories_closure.depth > 0) = 0) (Bindings: array ( ))

    bug 
    opened by gchabb 16
  • Iterate through getDescendantsTree() collection ?

    Iterate through getDescendantsTree() collection ?

    getDescendantsTree() return me a nested collection but i only want to get the Descendant tree with a max depth of 6.

    I Tried the reject:

    $test = $user->getDescendantsTree()->reject(function($item){return $item->depth < 6 ;});
    

    But no results. I also tried map(), each ... But i only get the first user.

    Any suggestion ?

    Thank you.

    opened by AngryElephant 11
  • Integrity constraint violation Obj::createFromArray($array)

    Integrity constraint violation Obj::createFromArray($array)

    When you run

    $array = [
                [
                    'id' => 90,
                    'name' => 'About',
                    'position' => 0,
                    'children' => [
                        [
                            'id' => 93,
                            'name' => 'Testimonials'
                        ]
                    ]
                ],
                [
                    'id' => 91,
                    'name' => 'Blog',
                    'position' => 1
                ],
                [
                    'id' => 92,
                    'name' => 'Portfolio',
                    'position' => 2
                ],
            ];
    
            $pages = Page::createFromArray($array);
    

    You get the error:

    [Illuminate\Database\QueryException]                                         
      SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update   
      a child row: a foreign key constraint fails (`DATABASE`.`TABLE`, C  
      ONSTRAINT `TABLE_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENC  
      ES `TABLE` (`id`)) (SQL: insert into `TABLE` (`position`, `real_d  
      epth`) values (0, 0))    
    

    I generated the database using:

    php artisan closuretable:make --entity=page
    
    opened by ghost 11
  • Update Travis to test on Laravel 5.6 to 6.x

    Update Travis to test on Laravel 5.6 to 6.x

    Looking at #222 I was questioning myself if everything is compatible with latest PHP and Laravel versions.

    The main goal of this pull request is to ensure that automated tests on Travis CI can be executed for all the supported Laravel and PHP versions, so I did the following changes:

    • Make PHP 7.0 the minimum PHP version
    • Update composer.json to ensure a Laravel package is specified to mark the version under testing
    • Update composer.json to ensure that the dev dependencies can be resolved to an installable set of packages for the supported PHP and Laravel version
    • Update unit tests to remove deprecation notices
    opened by avvertix 9
  • Various fixes

    Various fixes

    Hi,

    This is a PR containing a couple of fixes, most notably:

    • Add debugging facilities
    • Fix to createFromArray creating multiple entities entry
    • Fix to node reordering on node creation
    • Fix test cases not working
    • Additional test cases for the Entity class
    • Travis CI configuration update
    opened by tomzx 9
  • Declaration of Franzose\ClosureTable\Entity::performUpdate() should be compatible

    Declaration of Franzose\ClosureTable\Entity::performUpdate() should be compatible

    All of a sudden I'm getting this error…

    Declaration of Franzose\ClosureTable\Entity::performUpdate() should be compatible with Illuminate\Database\Eloquent\Model::performUpdate(Illuminate\Database\Eloquent\Builder $query)

    The declaration in Entity looks like this: protected function performUpdate($query)

    And in core: protected function performUpdate(Builder $query)

    opened by ben-joostens 8
  • Get entities on the same level

    Get entities on the same level

    Hi,

    on the schema http://image.prntscr.com/image/734031da1d7c454bbebc78aada981eac.png i have the item 7 id, i would like to get all entities on the same level : 4 5 6.

    Is there any easy way to do this without hitting the db too hard ?

    Thank you and congrats for this great library.

    opened by seuaCoder 7
  • reorderSiblings method doesn't update softdeleted node's position

    reorderSiblings method doesn't update softdeleted node's position

    Hello, I am trying to manage a tree that includes softdeleted nodes and I noticed whenever I update a node's position all the non-softedeleted nodes correctly increment/decrement their position, but the softdeleted nodes do not, causing certain group of nodes to end up with the same position and messing up the order whenever a new position update is made.

    The solution that I think would work would be to include trashed nodes in every scope/builder that. Alternatively I would set the method as protected so that I could override it in the extended model

    opened by snekku-dev 0
  • Unbind relations only when the entity is not recently created

    Unbind relations only when the entity is not recently created

    If I'm not missing anything, moveNodeTo method doesn't need to be called for recently created entities. It issues DELETE query which causes performance issue from time to time, and even worse, deadlocks on some RDBs (e.g. mysql) because of gap lock. This PR stops calling that function by looking at newly added created state.

    opened by i110 0
  • Column name disambiguation in closure table queries

    Column name disambiguation in closure table queries

    The buildAncestorsQuery() and buildDescendantsQuery() methods don't use qualified column names.

    This could lead to "ambiguous column name" errors when the entity table has columns named ancestor, descendants or depth.

    Using qualified column names prevents this.

    opened by Djuuu 0
  • Deprecated interfaces in Entity and ClosureTable

    Deprecated interfaces in Entity and ClosureTable

    Why werent they removed from code if they are deprecated in >6.0.

    6.1.1. still has class Entity extends Eloquent implements EntityInterface with EntityInterface marked as deprecated.. Problem is later in the code (Entity.php@1771) with $parent->getKey() method being undefined.

    Also Entity.php@1904 transaction($callable) method expects parameter type Closure instead of given type callable.

    opened by AnzePratnemerDSpot 0
Releases(v6.1.1)
  • v6.1.1(Oct 6, 2020)

  • v6.1.0(Oct 5, 2020)

  • v6.0.1(May 11, 2020)

  • v6.0(May 4, 2020)

    Improvements

    1. Fixed a lot of bugs related to positioning and movements between ancestors
    2. Introduced a bunch of query scopes to ease querying. They allowed to simplify the internals too!
    3. Introduced some convenient methods to the custom collection in addition to the existing ones
    4. Fixed bugs in the model and migration generators
    5. Improved tests and automated Travis CI builds. Many many thanks to @avvertix for the latter!

    Deprecations

    1. Franzose\ClosureTable\Contracts\EntityInteface has been deprecated and will be removed in the next major release
    2. Franzose\ClosureTable\Contracts\ClosureTableInteface has been deprecated and will be removed in the next major release
    3. Entity's real_depth attribute & database column have been deprecated and are not included in the generated model and migration anymore
    4. The following methods of the Entity model has been deprecated and will be removed in the next major release:
      • Entity::getRealDepthColumn()
      • Entity::getChildrenRelationIndex()
      • Entity::getAncestorsTree()
      • Entity::getAncestorsWhere()
      • Entity::getDescendantsTree()
      • Entity::getDescendantsWhere()
      • Entity::hasChildrenRelation()
      • Entity::getTree()
      • Entity::getTreeByQuery()
      • Entity::getTreeWhere()

    Other notes

    PHP 5.6 support has been dropped. Starting from this release, ClosureTable supports PHP 7.0 and higher.

    Source code(tar.gz)
    Source code(zip)
  • v5.1.1(Aug 27, 2018)

  • v5.1(Sep 10, 2017)

  • v5.0(Feb 10, 2017)

  • v4.1.3(Jul 6, 2016)

  • v4.1.2(Jul 6, 2016)

    I have removed checks for ancestor and descendant arguments to be integers because there are situations when one needs to use another column type.

    Source code(tar.gz)
    Source code(zip)
  • v4.1.1(Jan 6, 2016)

  • v4.1(Jan 3, 2016)

  • v4.0.1(Mar 9, 2015)

  • v4(Mar 9, 2015)

    Big big thanks to @EspadaV8 work! Now that his pull request has been accepted we can release a new package version. ClosureTable 4 fully supports Laravel 5 and also follows PSR-2 coding standard. Stay enjoing hierarchical relations!

    Source code(tar.gz)
    Source code(zip)
  • v3.1.1(Feb 14, 2015)

    In this release, I added the third optional argument to addChild() and addSibling() methods that allows you to get the child or sibling that's being added to an ancestor. Please, explore readme for the examples.

    Source code(tar.gz)
    Source code(zip)
  • v3.1(Feb 3, 2015)

    In this release, issues with removeChild and deleteSubtree methods have been resolved. If you have already been using the package, please set on delete set null constraint to the parent_id foreign key on your entity table, if that constraint has not been set yet.

    Source code(tar.gz)
    Source code(zip)
  • v3.0.5(Jan 5, 2015)

  • v3.0.4(Jun 2, 2014)

  • v3.0.3(Mar 27, 2014)

    Fixed generator that stripped out the first letter of the models. Fixed entity migration stub, made parent_id column unsigned to prevent foreign key constraints. Thanks to @kmccarthyweb for the issues.

    Source code(tar.gz)
    Source code(zip)
  • v3.0.2(Mar 26, 2014)

    Finally, I hope that it is, I fixed nodes reordering and moving bugs. Real depth of all descendants of a node now is updated properly when the node is moved somewhere. Also I added guessing of closure table name if its default model class (i.e. Franzose\ClosureTable\Models\ClosureTable) is not changed to something else.

    P.S. Highly recommended for you to update your ClosureTable to this release.

    Source code(tar.gz)
    Source code(zip)
  • v3.0.1(Mar 13, 2014)

  • v3.0(Mar 12, 2014)

    The new version introduces an injection of the Adjacency List pattern to the pure Closure Table. Adjacency list's features allows to simplify most of the database queries dramatically. In the new version, all the existed bugs were fixed. I also introduced a method, that the community requested long ago. It is called createFromArray and is used to create a bunch of records by just passing an array of attributes to it.

    However, the feature I mentioned is not the only thing I added while developing the 3.0. Explore the source!

    Source code(tar.gz)
    Source code(zip)
  • v2.1.6(Oct 5, 2013)

  • v2.1.5(Oct 4, 2013)

    Fixes:

    1. #27, database table prefix led to wrong closure table name.
    2. #25, wrong behaviour happened while moving a descendant to a new ancestor by using moveTo().
    3. #42, #37, #32, wrong position was set when an entity was moved or created.
    4. #43, position was updated even if a user tried to move an entity to the same ancestor it already belonged to.
    Source code(tar.gz)
    Source code(zip)
  • v2.1(Sep 28, 2013)

    In this release:

    1. Introducing filteredTree method, see #18. You can use it when it's necessary to get a tree, filtered by some condition.
    2. Fixed #19 (An exception thrown by \Franzose\ClosureTable\Collection when tree is empty).
    3. Retrieval methods now support selecting certain columns by passing array with their names.

    Thanks to @vjandrea for the first two improvements.

    Source code(tar.gz)
    Source code(zip)
  • v2.0(Sep 23, 2013)

  • v1.0(Sep 10, 2013)

    Closure table database design pattern implementation for Laravel 3 framework. Being a bundle it's a plug-and-play thing that's easy to use. Have fun!

    Source code(tar.gz)
    Source code(zip)
Owner
Yan Ivanov
Web developer, musician and amateur photographer. Currently in Novosibirsk, Russia.
Yan Ivanov
Export Table definition to spreadsheet by artisan command.

laravel-ddl-export Export Table definition to spreadsheet by artisan command. For MySQL, PostgreSQL Install $ composer require shibuyakosuke/laravel-d

Kosuke Shibuya 8 Nov 16, 2022
Articulate - An alternative ORM for Laravel, making use of the data mapper pattern

Articulate Laravel: 8.* PHP: 8.* License: MIT Author: Ollie Read Author Homepage: https://ollie.codes Articulate is an alternative ORM for Laravel bas

Ollie Codes 4 Jan 4, 2022
[READ-ONLY] A flexible, lightweight and powerful Object-Relational Mapper for PHP, implemented using the DataMapper pattern. This repo is a split of the main code that can be found in https://github.com/cakephp/cakephp

CakePHP ORM The CakePHP ORM provides a powerful and flexible way to work with relational databases. Using a datamapper pattern the ORM allows you to m

CakePHP 146 Sep 28, 2022
Pure PHP NoSQL database with no dependency. Flat file, JSON based document database.

Please give it a Star if you like the project ?? ❤️ SleekDB - A NoSQL Database made using PHP Full documentation: https://sleekdb.github.io/ SleekDB i

Kazi Mehedi Hasan 745 Jan 7, 2023
SleekwareDB is a NoSQL database storage service. A database storage service that can be used for various platforms and is easy to integrate.

SleekwareDB is a NoSQL database storage service. A database storage service that can be used for various platforms and is easy to integrate. NoSQL API

SleekwareDB 12 Dec 11, 2022
TO DO LIST WITH LOGIN AND SIGN UP and LOGOUT using PHP and MySQL please do edit the _dbconnect.php before viewing the website.

TO-DO-LIST-WITH-LOGIN-AND-SIGN-UP TO DO LIST WITH LOGIN AND SIGN UP and LOGOUT using PHP and MySQL please do edit the _dbconnect.php before viewing th

Aniket Singh 2 Sep 28, 2021
[READ ONLY] Subtree split of the Illuminate Database component (see laravel/framework)

Illuminate Database The Illuminate Database component is a full database toolkit for PHP, providing an expressive query builder, ActiveRecord style OR

The Laravel Components 2.5k Dec 27, 2022
The lightweight PHP database framework to accelerate development

The lightweight PHP database framework to accelerate development Features Lightweight - Less than 100 KB, portable with only one file Easy - Extremely

Angel Lai 4.6k Dec 28, 2022
A complete, simple and powerful database framework written in PHP

BaseSQL BaseSQL is a complete database framework written in PHP. It was built to accelerate projects development by handle database connections and qu

Willian Pinheiro 2 Sep 21, 2021
Phpstan-dba - database handling related class reflection extension for PHPStan & framework-specific rules

database handling class reflection extension for PHPStan This extension provides following features: PDO->query knows the array shape of the returned

Markus Staab 175 Dec 29, 2022
The fastest pure PHP database framework with a powerful static code generator, supports horizontal scale up, designed for PHP7

Maghead 4.0.x IS CURRENTLY UNDER HEAVY DEVELOPMENT, API IS NOT STABLE Maghead is an open-source Object-Relational Mapping (ORM) designed for PHP7. Mag

Maghead 477 Dec 24, 2022
This package provides a framework-agnostic database backup manager for dumping to and restoring databases from S3, Dropbox, FTP, SFTP, and Rackspace Cloud

Database Backup Manager This package provides a framework-agnostic database backup manager for dumping to and restoring databases from S3, Dropbox, FT

Backup Manager 1.6k Dec 23, 2022
The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

Mark Armendariz 0 Jan 7, 2022
A drop-in Doctrine ORM 2 implementation for Laravel 5+ and Lumen

Laravel Doctrine ORM A drop-in Doctrine ORM 2 implementation for Laravel 5+ $scientist = new Scientist( 'Albert', 'Einstein' ); $scientist->a

Laravel Doctrine 777 Dec 17, 2022
Laravel Thermite is an extended PostgreSQL Laravel database driver to connect to a CockroachDB cluster.

Laravel Thermite Laravel Thermite is an extended PostgreSQL Laravel database driver to connect to a CockroachDB cluster. ?? Supporting If you are usin

Renoki Co. 9 Nov 15, 2022
A data mapper implementation for your persistence model in PHP.

Atlas.Orm Atlas is a data mapper implementation for persistence models (not domain models). As such, Atlas uses the term "record" to indicate that its

null 427 Dec 30, 2022
🔌 A Doctrine DBAL Driver implementation on top of Swoole Coroutine PostgreSQL extension

Swoole Coroutine PostgreSQL Doctrine DBAL Driver A Doctrine\DBAL\Driver implementation on top of Swoole\Coroutine\PostgreSQL. Getting started Install

Leo Cavalcante 19 Nov 25, 2022
A minimalistic implementation of asynchronous SQL for PHP.

libSQL A minimalistic implementation of asynchronous SQL for PHP. Installation via DEVirion Install the DEVirion plugin and start your server. This wi

null 10 Dec 7, 2022
Async Redis client implementation, built on top of ReactPHP.

clue/reactphp-redis Async Redis client implementation, built on top of ReactPHP. Redis is an open source, advanced, in-memory key-value database. It o

Christian Lück 240 Dec 20, 2022