Let me introduce the third version of my package for Laravel 4. It's intended to use when you need to operate hierarchical data in database. The package is an implementation of a well-known database design pattern called Closure Table. The third version includes many bugfixes and improvements including models and migrations generator.
To install the package, put the following in your composer.json:
"require": {
"franzose/closure-table": "dev-master"
}
And to app/config/app.php
:
'providers' => array(
// ...
'Franzose\ClosureTable\ClosureTableServiceProvider',
),
For example, let's assume you're working on pages. In version 3, you can just use an artisan
command to create models and migrations automatically without preparing all the stuff by hand. Open terminal and put the following:
php artisan closuretable:make --entity=page
All options of the command:
--namespace
,-ns
[optional]: namespace for classes, set by--entity
and--closure
options, helps to avoid namespace duplication in those options--entity
,-e
: entity class name; if namespaced name is used, then the default closure class name will be prepended with that namespace--entity-table
,-et
[optional]: entity table name--closure
,-c
[optional]: closure class name--closure-table
[optional],-ct
: closure table name--models-path
,-mdl
[optional]: custom models path--migrations-path
,-mgr
[optional]: custom migrations path
That's almost all, folks! The ‘dummy’ stuff has just been created for you. You will need to add some fields to your entity migration because the created ‘dummy’ includes just required id
, parent_id
, position
, and real depth
columns:
id
is a regular autoincremented columnparent_id
column is used to simplify immediate ancestor querying and, for example, to simplify building the whole treeposition
column is used widely by the package to make entities sortablereal depth
column is also used to simplify queries and reduce their number
By default, entity’s closure table includes the following columns:
- Autoincremented identifier
- Ancestor column points on a parent node
- Descendant column points on a child node
- Depth column shows a node depth in the tree
It is by closure table pattern design, so remember that you must not delete these four columns.
Remember that many things are made customizable, so see ‘Customization’ for more information.
Once your models and their database tables are created, at last, you can start actually coding. Here I will show you ClosureTable's specific approaches.
$parent = Page::find(15)->getParent();
$page = Page::find(15);
$ancestors = $page->getAncestors();
$ancestors = $page->getAncestorsWhere('position', '=', 1);
$hasAncestors = $page->hasAncestors();
$ancestorsNumber = $page->countAncestors();
$page = Page::find(15);
$children = $page->getChildren();
$hasChildren = $page->hasChildren();
$childrenNumber = $page->countChildren();
$newChild = new Page(array(
'title' => 'The title',
'excerpt' => 'The excerpt',
'content' => 'The content of a child'
));
$newChild2 = new Page(array(
'title' => 'The title',
'excerpt' => 'The excerpt',
'content' => 'The content of a child'
));
$page->appendChild($newChild);
//you can set child position
$page->appendChild($newChild, 5);
$page->appendChildren([$newChild, $newChild2]);
$page->getChildAt(5);
$page->getFirstChild();
$page->getLastChild();
$page->getChildrenRange(0, 2);
$page->removeChild(0);
$page->removeChild(0, true); //force delete
$page->removeChildren(0, 3);
$page->removeChildren(0, 3, true); //force delete
$page = Page::find(15);
$descendants = $page->getDescendants();
$descendants = $page->getDescendantsWhere('position', '=', 1);
$descendantsTree = $page->getDescendantsTree();
$hasDescendants = $page->hasDescendants();
$descendantsNumber = $page->countDescendants();
$page = Page::find(15);
$first = $page->getFirstSibling(); //or $page->getSiblingAt(0);
$last = $page->getLastSibling();
$atpos = $page->getSiblingAt(5);
$prevOne = $page->getPrevSibling();
$prevAll = $page->getPrevSiblings();
$hasPrevs = $page->hasPrevSiblings();
$prevsNumber = $page->countPrevSiblings();
$nextOne = $page->getNextSibling();
$nextAll = $page->getNextSiblings();
$hasNext = $page->hasNextSiblings();
$nextNumber = $page->countNextSiblings();
//in both directions
$hasSiblings = $page->hasSiblings();
$siblingsNumber = $page->countSiblings();
$sibligns = $page->getSiblingsRange(0, 2);
$page->addSibling(new Page);
$page->addSibling(new Page, 3); //third position
$page->addSiblings([new Page, new Page]);
$page->addSiblings([new Page, new Page], 5); //insert from fifth position
$roots = Page::getRoots();
$isRoot = Page::find(23)->isRoot();
Page::find(11)->makeRoot();
$tree = Page::getTree();
$treeByCondition = Page::getTreeWhere('position', '>=', 1);
You deal with the collection, thus you can control its items as you usually do. Descendants? They are already loaded.
$tree = Page::getTree();
$page = $tree->find(15);
$children = $page->getChildren();
$child = $page->getChildAt(3);
$grandchildren = $page->getChildAt(3)->getChildren(); //and so on
$page = Page::find(25);
$page->moveTo(0, Page::find(14));
$page->moveTo(0, 14);
If you don't use foreign keys for some reason, you can delete subtree manually. This will delete the page and all its descendants:
$page = Page::find(34);
$page->deleteSubtree();
$page->deleteSubtree(true); //with subtree ancestor
$page->deleteSubtree(false, true); //without subtree ancestor and force delete
You can customize the default things in your classes created by the ClosureTable artisan
command:
- Entity table name by changing
protected $table
of your ownEntity
(e.g.Page
) - Closure table name by changing
protected $table
of your ownClosureTable
(e.g.PageClosure
) parent_id
,position
, andreal depth
column names by changingconst PARENT_ID
,const POSITION
, andconst REAL_DEPTH
of your ownEntityInterface
(e.g.PageInterface
) respectivelyancestor
,descendant
, anddepth
columns names by changingconst ANCESTOR
,const DESCENDANT
, andconst DEPTH
of your ownClosureTableInterface
(e.g.PageClosureInterface
) respectively.