Code Monkey home page Code Monkey logo

baum's Introduction

Baum

Build Status

Baum is an implementation of the Nested Set pattern for Laravel 5's Eloquent ORM.

For Laravel 4.2.x compatibility, check the 1.0.x branch branch or use the latest 1.0.x tagged release.

Documentation

About Nested Sets

A nested set is a smart way to implement an ordered tree that allows for fast, non-recursive queries. For example, you can fetch all descendants of a node in a single query, no matter how deep the tree. The drawback is that insertions/moves/deletes require complex SQL, but that is handled behind the curtains by this package!

Nested sets are appropriate for ordered trees (e.g. menus, commercial categories) and big trees that must be queried efficiently (e.g. threaded posts).

See the wikipedia entry for nested sets for more info. Also, this is a good introductory tutorial: http://www.evanpetersen.com/item/nested-sets.html

The theory behind, a TL;DR version

An easy way to visualize how a nested set works is to think of a parent entity surrounding all of its children, and its parent surrounding it, etc. So this tree:

root
  |_ Child 1
    |_ Child 1.1
    |_ Child 1.2
  |_ Child 2
    |_ Child 2.1
    |_ Child 2.2

Could be visualized like this:

 ___________________________________________________________________
|  Root                                                             |
|    ____________________________    ____________________________   |
|   |  Child 1                  |   |  Child 2                  |   |
|   |   __________   _________  |   |   __________   _________  |   |
|   |  |  C 1.1  |  |  C 1.2 |  |   |  |  C 2.1  |  |  C 2.2 |  |   |
1   2  3_________4  5________6  7   8  9_________10 11_______12 13  14
|   |___________________________|   |___________________________|   |
|___________________________________________________________________|

The numbers represent the left and right boundaries. The table then might look like this:

id | parent_id | lft  | rgt  | depth | data
 1 |           |    1 |   14 |     0 | root
 2 |         1 |    2 |    7 |     1 | Child 1
 3 |         2 |    3 |    4 |     2 | Child 1.1
 4 |         2 |    5 |    6 |     2 | Child 1.2
 5 |         1 |    8 |   13 |     1 | Child 2
 6 |         5 |    9 |   10 |     2 | Child 2.1
 7 |         5 |   11 |   12 |     2 | Child 2.2

To get all children of a parent node, you

SELECT * WHERE lft IS BETWEEN parent.lft AND parent.rgt

To get the number of children, it's

(right - left - 1)/2

To get a node and all its ancestors going back to the root, you

SELECT * WHERE node.lft IS BETWEEN lft AND rgt

As you can see, queries that would be recursive and prohibitively slow on ordinary trees are suddenly quite fast. Nifty, isn't it?

Installation

Baum works with Laravel 5 onwards. You can add it to your composer.json file with:

"baum/baum": "~1.1"

Run composer install to install it.

As with most Laravel 5 packages you'll then need to register the Baum service provider. To do that, head over your config/app.php file and add the following line into the providers array:

'Baum\Providers\BaumServiceProvider',

Getting started

After the package is correctly installed the easiest way to get started is to run the provided generator:

php artisan baum:install MODEL

Replace model by the class name you plan to use for your Nested Set model.

The generator will install a migration and a model file into your application configured to work with the Nested Set behaviour provided by Baum. You SHOULD take a look at those files, as each of them describes how they can be customized.

Next, you would probably run artisan migrate to apply the migration.

Model configuration

In order to work with Baum, you must ensure that your model class extends Baum\Node.

This is the easiest it can get:

class Category extends Baum\Node {

}

This is a slightly more complex example where we have the column names customized:

class Dictionary extends Baum\Node {

  protected $table = 'dictionary';

  // 'parent_id' column name
  protected $parentColumn = 'parent_id';

  // 'lft' column name
  protected $leftColumn = 'lidx';

  // 'rgt' column name
  protected $rightColumn = 'ridx';

  // 'depth' column name
  protected $depthColumn = 'nesting';

  // guard attributes from mass-assignment
  protected $guarded = array('id', 'parent_id', 'lidx', 'ridx', 'nesting');

}

Remember that, obviously, the column names must match those in the database table.

Migration configuration

You must ensure that the database table that supports your Baum models has the following columns:

  • parent_id: a reference to the parent (int)
  • lft: left index bound (int)
  • rgt: right index bound (int)
  • depth: depth or nesting level (int)

Here is a sample migration file:

class Category extends Migration {

  public function up() {
    Schema::create('categories', function(Blueprint $table) {
      $table->increments('id');

      $table->integer('parent_id')->nullable();
      $table->integer('lft')->nullable();
      $table->integer('rgt')->nullable();
      $table->integer('depth')->nullable();

      $table->string('name', 255);

      $table->timestamps();
    });
  }

  public function down() {
    Schema::drop('categories');
  }

}

You may freely modify the column names, provided you change them both in the migration and the model.

Usage

After you've configured your model and run the migration, you are now ready to use Baum with your model. Below are some examples.

Creating a root node

By default, all nodes are created as roots:

$root = Category::create(['name' => 'Root category']);

Alternatively, you may find yourself in the need of converting an existing node into a root node:

$node->makeRoot();

You may also nullify it's parent_id column to accomplish the same behaviour:

// This works the same as makeRoot()
$node->parent_id = null;
$node->save();

Inserting nodes

// Directly with a relation
$child1 = $root->children()->create(['name' => 'Child 1']);

// with the `makeChildOf` method
$child2 = Category::create(['name' => 'Child 2']);
$child2->makeChildOf($root);

Deleting nodes

$child1->delete();

Descendants of deleted nodes will also be deleted and all the lft and rgt bound will be recalculated. Pleases note that, for now, deleting and deleted model events for the descendants will not be fired.

Getting the nesting level of a node

The getLevel() method will return current nesting level, or depth, of a node.

$node->getLevel() // 0 when root

Moving nodes around

Baum provides several methods for moving nodes around:

  • moveLeft(): Find the left sibling and move to the left of it.
  • moveRight(): Find the right sibling and move to the right of it.
  • moveToLeftOf($otherNode): Move to the node to the left of ...
  • moveToRightOf($otherNode): Move to the node to the right of ...
  • makeNextSiblingOf($otherNode): Alias for moveToRightOf.
  • makeSiblingOf($otherNode): Alias for makeNextSiblingOf.
  • makePreviousSiblingOf($otherNode): Alias for moveToLeftOf.
  • makeChildOf($otherNode): Make the node a child of ...
  • makeFirstChildOf($otherNode): Make the node the first child of ...
  • makeLastChildOf($otherNode): Alias for makeChildOf.
  • makeRoot(): Make current node a root node.

For example:

$root = Creatures::create(['name' => 'The Root of All Evil']);

$dragons = Creatures::create(['name' => 'Here Be Dragons']);
$dragons->makeChildOf($root);

$monsters = new Creatures(['name' => 'Horrible Monsters']);
$monsters->save();

$monsters->makeSiblingOf($dragons);

$demons = Creatures::where('name', '=', 'demons')->first();
$demons->moveToLeftOf($dragons);

Asking questions to your nodes

You can ask some questions to your Baum nodes:

  • isRoot(): Returns true if this is a root node.
  • isLeaf(): Returns true if this is a leaf node (end of a branch).
  • isChild(): Returns true if this is a child node.
  • isDescendantOf($other): Returns true if node is a descendant of the other.
  • isSelfOrDescendantOf($other): Returns true if node is self or a descendant.
  • isAncestorOf($other): Returns true if node is an ancestor of the other.
  • isSelfOrAncestorOf($other): Returns true if node is self or an ancestor.
  • equals($node): current node instance equals the other.
  • insideSubtree($node): Checks whether the given node is inside the subtree defined by the left and right indices.
  • inSameScope($node): Returns true if the given node is in the same scope as the current one. That is, if every column in the scoped property has the same value in both nodes.

Using the nodes from the previous example:

$demons->isRoot(); // => false

$demons->isDescendantOf($root) // => true

Relations

Baum provides two self-referential Eloquent relations for your nodes: parent and children.

$parent = $node->parent()->get();

$children = $node->children()->get();

Root and Leaf scopes

Baum provides some very basic query scopes for accessing the root and leaf nodes:

// Query scope which targets all root nodes
Category::roots()

// All leaf nodes (nodes at the end of a branch)
Category:allLeaves()

You may also be interested in only the first root:

$firstRootNode = Category::root();

Accessing the ancestry/descendancy chain

There are several methods which Baum offers to access the ancestry/descendancy chain of a node in the Nested Set tree. The main thing to keep in mind is that they are provided in two ways:

First as query scopes, returning an Illuminate\Database\Eloquent\Builder instance to continue to query further. To get actual results from these, remember to call get() or first().

  • ancestorsAndSelf(): Targets all the ancestor chain nodes including the current one.
  • ancestors(): Query the ancestor chain nodes excluding the current one.
  • siblingsAndSelf(): Instance scope which targets all children of the parent, including self.
  • siblings(): Instance scope targeting all children of the parent, except self.
  • leaves(): Instance scope targeting all of its nested children which do not have children.
  • descendantsAndSelf(): Scope targeting itself and all of its nested children.
  • descendants(): Set of all children & nested children.
  • immediateDescendants(): Set of all children nodes (non-recursive).

Second, as methods which return actual Baum\Node instances (inside a Collection object where appropiate):

  • getRoot(): Returns the root node starting at the current node.
  • getAncestorsAndSelf(): Retrieve all of the ancestor chain including the current node.
  • getAncestorsAndSelfWithoutRoot(): All ancestors (including the current node) except the root node.
  • getAncestors(): Get all of the ancestor chain from the database excluding the current node.
  • getAncestorsWithoutRoot(): All ancestors except the current node and the root node.
  • getSiblingsAndSelf(): Get all children of the parent, including self.
  • getSiblings(): Return all children of the parent, except self.
  • getLeaves(): Return all of its nested children which do not have children.
  • getDescendantsAndSelf(): Retrieve all nested children and self.
  • getDescendants(): Retrieve all of its children & nested children.
  • getImmediateDescendants(): Retrieve all of its children nodes (non-recursive).

Here's a simple example for iterating a node's descendants (provided a name attribute is available):

$node = Category::where('name', '=', 'Books')->first();

foreach($node->getDescendantsAndSelf() as $descendant) {
  echo "{$descendant->name}";
}

Limiting the levels of children returned

In some situations where the hierarchy depth is huge it might be desirable to limit the number of levels of children returned (depth). You can do this in Baum by using the limitDepth query scope.

The following snippet will get the current node's descendants up to a maximum of 5 depth levels below it:

$node->descendants()->limitDepth(5)->get();

Similarly, you can limit the descendancy levels with both the getDescendants and getDescendantsAndSelf methods by supplying the desired depth limit as the first argument:

// This will work without depth limiting
// 1. As usual
$node->getDescendants();
// 2. Selecting only some attributes
$other->getDescendants(array('id', 'parent_id', 'name'));
...
// With depth limiting
// 1. A maximum of 5 levels of children will be returned
$node->getDescendants(5);
// 2. A max. of 5 levels of children will be returned selecting only some attrs
$other->getDescendants(5, array('id', 'parent_id', 'name'));

Custom sorting column

By default in Baum all results are returned sorted by the lft index column value for consistency.

If you wish to change this default behaviour you need to specify in your model the name of the column you wish to use to sort your results like this:

protected $orderColumn = 'name';

Dumping the hierarchy tree

Baum extends the default Eloquent\Collection class and provides the toHierarchy method to it which returns a nested collection representing the queried tree.

Retrieving a complete tree hierarchy into a regular Collection object with its children properly nested is as simple as:

$tree = Category::where('name', '=', 'Books')->first()->getDescendantsAndSelf()->toHierarchy();

Model events: moving and moved

Baum models fire the following events: moving and moved every time a node is moved around the Nested Set tree. This allows you to hook into those points in the node movement process. As with normal Eloquent model events, if false is returned from the moving event, the movement operation will be cancelled.

The recommended way to hook into those events is by using the model's boot method:

class Category extends Baum\Node {

  public static function boot() {
    parent::boot();

    static::moving(function($node) {
      // Before moving the node this function will be called.
    });

    static::moved(function($node) {
      // After the move operation is processed this function will be
      // called.
    });
  }

}

Scope support

Baum provides a simple method to provide Nested Set "scoping" which restricts what we consider part of a nested set tree. This should allow for multiple nested set trees in the same database table.

To make use of the scoping funcionality you may override the scoped model attribute in your subclass. This attribute should contain an array of the column names (database fields) which shall be used to restrict Nested Set queries:

class Category extends Baum\Node {
  ...
  protected $scoped = array('company_id');
  ...
}

In the previous example, company_id effectively restricts (or "scopes") a Nested Set tree. So, for each value of that field we may be able to construct a full different tree.

$root1 = Category::create(['name' => 'R1', 'company_id' => 1]);
$root2 = Category::create(['name' => 'R2', 'company_id' => 2]);

$child1 = Category::create(['name' => 'C1', 'company_id' => 1]);
$child2 = Category::create(['name' => 'C2', 'company_id' => 2]);

$child1->makeChildOf($root1);
$child2->makeChildOf($root2);

$root1->children()->get(); // <- returns $child1
$root2->children()->get(); // <- returns $child2

All methods which ask or traverse the Nested Set tree will use the scoped attribute (if provided).

Please note that, for now, moving nodes between scopes is not supported.

Validation

The ::isValidNestedSet() static method allows you to check if your underlying tree structure is correct. It mainly checks for these 3 things:

  • Check that the bound indexes lft, rgt are not null, rgt values greater than lft and within the bounds of the parent node (if set).
  • That there are no duplicates for the lft and rgt column values.
  • As the first check does not actually check root nodes, see if each root has the lft and rgt indexes within the bounds of its children.

All of the checks are scope aware and will check each scope separately if needed.

Example usage, given a Category node class:

Category::isValidNestedSet()
=> true

Tree rebuilding

Baum supports for complete tree-structure rebuilding (or reindexing) via the ::rebuild() static method.

This method will re-index all your lft, rgt and depth column values, inspecting your tree only from the parent <-> children relation standpoint. Which means that you only need a correctly filled parent_id column and Baum will try its best to recompute the rest.

This can prove quite useful when something has gone horribly wrong with the index values or it may come quite handy when converting from another implementation (which would probably have a parent_id column).

This operation is also scope aware and will rebuild all of the scopes separately if they are defined.

Simple example usage, given a Category node class:

Category::rebuild()

Valid trees (per the isValidNestedSet method) will not get rebuilt. To force the index rebuilding process simply call the rebuild method with true as the first parameter:

Category::rebuild(true);

Soft deletes

Baum comes with limited support for soft-delete operations. What I mean by limited is that the testing is still limited and the soft delete functionality is changing in the upcoming 4.2 version of the framework, so use this feature wisely.

For now, you may consider a safe restore() operation to be one of:

  • Restoring a leaf node
  • Restoring a whole sub-tree in which the parent is not soft-deleted

Seeding/Mass-assignment

Because Nested Set structures usually involve a number of method calls to build a hierarchy structure (which result in several database queries), Baum provides two convenient methods which will map the supplied array of node attributes and create a hierarchy tree from them:

  • buildTree($nodeList): (static method) Maps the supplied array of node attributes into the database.
  • makeTree($nodeList): (instance method) Maps the supplied array of node attributes into the database using the current node instance as the parent for the provided subtree.

Both methods will create new nodes when the primary key is not supplied, update or create if it is, and delete all nodes which are not present in the affecting scope. Understand that the affecting scope for the buildTree static method is the whole nested set tree and for the makeTree instance method are all of the current node's descendants.

For example, imagine we wanted to map the following category hierarchy into our database:

  • TV & Home Theater
  • Tablets & E-Readers
  • Computers
    • Laptops
      • PC Laptops
      • Macbooks (Air/Pro)
    • Desktops
    • Monitors
  • Cell Phones

This could be easily accomplished with the following code:

$categories = [
  ['id' => 1, 'name' => 'TV & Home Theather'],
  ['id' => 2, 'name' => 'Tablets & E-Readers'],
  ['id' => 3, 'name' => 'Computers', 'children' => [
    ['id' => 4, 'name' => 'Laptops', 'children' => [
      ['id' => 5, 'name' => 'PC Laptops'],
      ['id' => 6, 'name' => 'Macbooks (Air/Pro)']
    ]],
    ['id' => 7, 'name' => 'Desktops'],
    ['id' => 8, 'name' => 'Monitors']
  ]],
  ['id' => 9, 'name' => 'Cell Phones']
];

Category::buildTree($categories) // => true

After that, we may just update the hierarchy as needed:

$categories = [
  ['id' => 1, 'name' => 'TV & Home Theather'],
  ['id' => 2, 'name' => 'Tablets & E-Readers'],
  ['id' => 3, 'name' => 'Computers', 'children' => [
    ['id' => 4, 'name' => 'Laptops', 'children' => [
      ['id' => 5, 'name' => 'PC Laptops'],
      ['id' => 6, 'name' => 'Macbooks (Air/Pro)']
    ]],
    ['id' => 7, 'name' => 'Desktops', 'children' => [
      // These will be created
      ['name' => 'Towers Only'],
      ['name' => 'Desktop Packages'],
      ['name' => 'All-in-One Computers'],
      ['name' => 'Gaming Desktops']
    ]]
    // This one, as it's not present, will be deleted
    // ['id' => 8, 'name' => 'Monitors'],
  ]],
  ['id' => 9, 'name' => 'Cell Phones']
];

Category::buildTree($categories); // => true

The makeTree instance method works in a similar fashion. The only difference is that it will only perform operations on the descendants of the calling node instance.

So now imagine we already have the following hierarchy in the database:

  • Electronics
  • Health Fitness & Beaty
  • Small Appliances
  • Major Appliances

If we execute the following code:

$children = [
  ['name' => 'TV & Home Theather'],
  ['name' => 'Tablets & E-Readers'],
  ['name' => 'Computers', 'children' => [
    ['name' => 'Laptops', 'children' => [
      ['name' => 'PC Laptops'],
      ['name' => 'Macbooks (Air/Pro)']
    ]],
    ['name' => 'Desktops'],
    ['name' => 'Monitors']
  ]],
  ['name' => 'Cell Phones']
];

$electronics = Category::where('name', '=', 'Electronics')->first();
$electronics->makeTree($children); // => true

Would result in:

  • Electronics
    • TV & Home Theater
    • Tablets & E-Readers
    • Computers
      • Laptops
        • PC Laptops
        • Macbooks (Air/Pro)
      • Desktops
      • Monitors
    • Cell Phones
  • Health Fitness & Beaty
  • Small Appliances
  • Major Appliances

Updating and deleting nodes from the subtree works the same way.

Misc/Utility functions

Node extraction query scopes

Baum provides some query scopes which may be used to extract (remove) selected nodes from the current results set.

  • withoutNode(node): Extracts the specified node from the current results set.
  • withoutSelf(): Extracts itself from the current results set.
  • withoutRoot(): Extracts the current root node from the results set.
$node = Category::where('name', '=', 'Some category I do not want to see.')->first();

$root = Category::where('name', '=', 'Old boooks')->first();
var_dump($root->descendantsAndSelf()->withoutNode($node)->get());
... // <- This result set will not contain $node

Get a nested list of column values

The ::getNestedList() static method returns a key-value pair array indicating a node's depth. Useful for silling select elements, etc.

It expects the column name to return, and optionally: the column to use for array keys (will use id if none supplied) and/or a separator:

public static function getNestedList($column, $key = null, $seperator = ' ');

An example use case:

$nestedList = Category::getNestedList('name');
// $nestedList will contain an array like the following:
// array(
//   1 => 'Root 1',
//   2 => ' Child 1',
//   3 => ' Child 2',
//   4 => '  Child 2.1',
//   5 => ' Child 3',
//   6 => 'Root 2'
// );

Further information

You may find additional information, usage examples and/or frequently asked questions about Baum in the wiki.

Feel free to browse the wiki after finishing this README:

https://github.com/etrepat/baum/wiki

Contributing

Thinking of contributing? Maybe you've found some nasty bug? That's great news!

  1. Fork & clone the project: git clone [email protected]:your-username/baum.git.
  2. Run the tests and make sure that they pass with your setup: phpunit.
  3. Create your bugfix/feature branch and code away your changes. Add tests for your changes.
  4. Make sure all the tests still pass: phpunit.
  5. Push to your fork and submit new a pull request.

Please see the CONTRIBUTING.md file for extended guidelines and/or recommendations.

License

Baum is licensed under the terms of the MIT License (See LICENSE file for details).


Coded by Estanislau Trepat (etrepat). I'm also @etrepat on twitter.

baum's People

Contributors

beingtomgreen avatar bmitch avatar ceejayoz avatar daxborges avatar dirkpostma avatar dzcpy avatar etrepat avatar gerp avatar grahamcampbell avatar ignaciogc avatar james2037 avatar mpociot avatar robmeijer avatar superbuba avatar surt avatar ziadoz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

baum's Issues

::roots() returns empty collection when scoped

I have $scoped set to ['menu'] in my model, and I successfully built up some menus. The data looks fine in the database -- each of the four menus has a root node, and then some children, and each menu's root and children have the menu name in the menu column.

But when I call MenuItem::roots()->get() to get the four menus' root nodes I get an empty collection.

If at this point (with the data already in place) I comment out the lines from the model setting $scoped and try again, I do get the four root nodes.

It seems something is wrong with the implementation of the roots method -- perhaps it shouldn't be starting its query builder in the same way as other queries, because we don't actually want to constrain ourselves based on the scoped columns at this point.

How to Get whole Tree Hierarchy ??

I am using your package in my project . I want to know that how to get a tree hierarchy using your model ? ie in to a json tree or array tree ??

Order by on Immediate Descendants (tested case)

This:
$immediateDescendants = static::where('parent_id', $root->id)->orderBy('order')->get();

And
$immediateDescendants = $root->immediateDescendants()->orderBy('order')->get();

Return the same results but the second case does not obey the 'order'. Is this the intended behaviour?

[Proposal] Add getAncestorsNoRoot() and getAncestorsAndSelfNoRoot()

It would be convenient to have these self explanatory functions, e.g. to display a breadcumb path, but without the root.

Background: I like to have a one-tree table because that saves me from having to program HTTP request functions for addRoot(). With 1 tree, it's enough to have only addChild().

missing tablename prefix in column

when i write this code:

first(); ``` foreach($node->getLeaves() as $descendant) { echo "{$descendant->name}"; } ``` ?>

I get this error:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'categoryLists.rgt' in 'where clause' (SQL: select * from cf_categoryLists where lft >= ? and lft < ? and id != ? and categoryLists.rgt - categoryLists.lft = 1 order by lft asc) (Bindings: array ( 0 => 1, 1 => 6, 2 => 3, ))

The error in the sql is that column names are not prefixed when they should be.

Beginner questions

Hello there,

Sorry but i could answer my own questions by looking at the docs or the example. Firstly, when i copy paste syntax like this "$root = Category::create(['name' => 'Root category']);", then i get an error. (Laravel 4.1, PHP 5.3.10 on Ubuntu 12.04)

Is this because i should really pass an array as so: "$root = Category::create(array('name' => 'Root category'));" ? Because when i do that, a new Root category does get added into my categories table.

Then, imagine i added a bunch of Root categories. How do i loop over them? The below will not work.

$categories = Category::roots();
foreach($categories as $c){
print $c->name;
}

Thanks!

forceDelete() Fix

I was getting the following error when I was trying to truly delete a soft deleted object:
Call to a member function getAttributes() on a non-object

I believe the following should fix the issue:
In:
src/Baum/Extensions/Eloquent/Model.php

Change Line 19 From:
$fresh = static::find($this->getKey());

To:
$fresh = static::withTrashed()->find($this->getKey());

exception

Hi.
When i'm trying to use your extension i've got next exception:
"Call to undefined method Baum\Extensions\Query\Builder::getResults()"
How can i fix it?

My model declaration:

class Page extends Baum\Node
{
protected $table='cnt_page';

// 'lft' column name
protected $leftColumn = 'lft';

// 'rgt' column name
protected $rightColumn = 'rgt';

// 'depth' column name
protected $depthColumn = 'lvl';

// guard attributes from mass-assignment
protected $guarded = array('id', 'parent_id', 'lft', 'rgt', 'lvl');

public function author()
{
    return $this->belongsTo('User', 'author_id');
}

public function getIdentedTitleAttribute() {
    $s = str_repeat('&nbsp;', $this->lvl * 4) . $this->title;
    return $s;
}    

}

Inserting Model with scope

When inserting a new Model with a defined scope, the
setDefaultLeftAndRight function doesn't take the scope into account.

I thought rewriting it to something like this would fix the problem, but then moving nodes between scopes makes problems and the testMoveNodeBetweenScopes fails:

$withHighestRightQuery = $this->newQuery()->orderBy($this->getRightColumnName(), 'desc')->take(1);

if ( !empty($this->scoped) ) {
  foreach($this->scoped as $scopeFld)
    $withHighestRightQuery->where($scopeFld, '=', $this->$scopeFld);
}

$withHighestRight = $withHighestRightQuery->first();

$maxRgt = 0;
if ( !is_null($withHighestRight) ) $maxRgt = $withHighestRight->getRight();

$this->setAttribute($this->getLeftColumnName()  , $maxRgt + 1);
$this->setAttribute($this->getRightColumnName() , $maxRgt + 2);

Do you have any idea how to solve this problem?

Is there a way to just dump the entire hierarchy?

I noticed that toHierarchy() can only be used on a single root node. Is there an easy way to just dump out the complete hierarchy of all roots for a given scope, their descendants, and put that all into a hierarchy?

Something like Menu::roots(scope_id)->toHierarchy() ?

Latest dev-develop commit throws error.

After installing latest dev-develop 4bddc73 commit now i have error in my js console.

{"error":{"type":"Symfony\Component\Debug\Exception\FatalErrorException","message":"Call to a member function getConnection() on a non-object","file":"C:\Htdocs\at.univemba.com\uv2\vendor\laravel\framework\src\Illuminate\Database\Capsule\Manager.php","line":84}}

Restoring a deleted model does not restore hierachical structure

I am able to delete a model with $model->delete(); and the lft and rgt variables of the parent model changes, but when you restore the model with $model->restore, lft and rgt of the parent model stays the same. Is this intended or a bug?

Example:

before:
id   |   name     |    parent_id      |   lft  |   rgt   |   depth      |   delete_at
1    |   root     |       null        |    1   |    4    |      0       |      null
2    |   child    |        1          |    2   |    3    |      1       |      null

deleted:
id   |   name     |    parent_id      |   lft  |   rgt   |   depth      |   delete_at
1    |   root     |       null        |    1   |    2    |      0       |      null
2    |   child    |        1          |    2   |    3    |      1       |   (timestamp)

restored:
id   |   name     |    parent_id      |   lft  |   rgt   |   depth      |   delete_at
1    |   root     |       null        |    1   |    2    |      0       |      null
2    |   child    |        1          |    2   |    3    |      1       |      null

How to use with multilingual ?

I need multilingual usage, categories names for each language are stored in another table with relationship on PK id.
How to do that ?

Thanks for help.

Getting an exception with the example from the site

Now, I'm really not sure if it's just me being stupid or this is a real issue, but I've run into the problems (perhaps similar to the issue #17) while I was trying to run the example from your site:

$node = Category::where('name', '=', 'Books');

foreach($node->getDescendantsAndSelf() as $descendant) {
     echo "{$descendant->name}";
}

When I run the above Laravel throws an exception:
Call to undefined method Baum\Extensions\Query\Builder::getDescendantsAndSelf()

Category is the model that was generated by artisan:

use Baum\Node;
class Category extends Node {
    ...
}

And as far as I can tell the bundle was properly installed, I followed the instructions closely.

If the above code is called like this:

$node = Category::where('name', '=', 'Books')->first();

foreach($node->getDescendantsAndSelf() as $descendant) {
     echo "{$descendant->name}";
}

then it works fine because then the $node is a Category instance, but this way it runs 2 queries, instead of just one.

Call to a member function getConnection() on a non-object

When calling makeRoot() or makeChildOf() I get the following error:

Call to a member function getConnection() on a non-object

Error occurs in vendor/laravel/framework/src/Illuminate/Database/Capsule/Manager.php. When returning to an older version of Baum this doesn't happen.

Moving nodes between scopes

I was having this issue on master, and have just upgraded to develop and have the same behaviour.

I have a drag-and-drop front end to a nested menu system, with multiple menus (scopes) visible on the same page. The user can drag menu items from one menu to another.

We've been seeing some issues with items suddenly disappearing after dragging from menu to menu, and a look at the database shows that the depth is being incremented for unknown reasons.

I tried some testing on my own environment and I noticed pretty early that certain values aren't being updated in one situation. That situation is as follows:

I start with my default menu hierarchy (fresh from db:seed, this is stock and the numbers all look right). Two of the menus roots are empty (footer_left and sub).

+-----+--------------+-----------+------+------+-------+----------------------+
| id  | menu         | parent_id | lft  | rgt  | depth | name                 |
+-----+--------------+-----------+------+------+-------+----------------------+
| 561 | footer_left  |      NULL |    1 |    2 |     0 | NULL                 |
| 562 | footer_right |      NULL |    1 |   16 |     0 | NULL                 |
| 563 | footer_right |       562 |    2 |    3 |     1 | Food & Drink         |
| 564 | footer_right |       562 |    4 |    5 |     1 | Accommodations       |
| 565 | footer_right |       562 |    6 |    7 |     1 | Event Calendar       |
| 566 | footer_right |       562 |    8 |    9 |     1 | Things to Do         |
| 567 | footer_right |       562 |   10 |   11 |     1 | Travel Deals         |
| 568 | footer_right |       562 |   12 |   13 |     1 | Itineraries          |
| 569 | footer_right |       562 |   14 |   15 |     1 | Blog                 |
| 550 | main         |      NULL |    1 |   20 |     0 | NULL                 |
| 551 | main         |       550 |    2 |    9 |     1 | Festivals and Events |
| 552 | main         |       551 |    3 |    4 |     2 | Food & Drink         |
| 553 | main         |       551 |    5 |    6 |     2 | Accommodations       |
| 554 | main         |       551 |    7 |    8 |     2 | Event Calendar       |
| 555 | main         |       550 |   10 |   17 |     1 | Things to Do         |
| 556 | main         |       555 |   11 |   12 |     2 | Food & Drink         |
| 557 | main         |       555 |   13 |   14 |     2 | Accommodations       |
| 558 | main         |       555 |   15 |   16 |     2 | Event Calendar       |
| 559 | main         |       550 |   18 |   19 |     1 | Travel Deals         |
| 560 | sub          |      NULL |    1 |    2 |     0 | NULL                 |
+-----+--------------+-----------+------+------+-------+----------------------+

I drag the "Food & Drink" item (523) from the footer_right menu to the footer_left menu. I verify that the input I am sending to my back end is right -- I'm sending menu, parent_id, lft, rgt, depth for each item ID.

My back end code loads all the MenuItem objects and orders them by menu and then lft. I loop through once and update the 'menu' property of each, in case any need to change scope. Then I loop through in that order again and for each one except root nodes (whether it's been edited or not -- detecting reorderings etc seems like a lot of work) I reload $node and $parentNode to ensure data is fresh and then run $node->makeChildOf($parentNode). This works just fine and reorders things as expected as long as I'm just reordering within a particular menu/scope, but in this example where I'm moving from one scope to another I get an unexpected result.

+-----+--------------+-----------+------+------+-------+----------------------+
| id  | menu         | parent_id | lft  | rgt  | depth | name                 |
+-----+--------------+-----------+------+------+-------+----------------------+
| 561 | footer_left  |      NULL |    1 |    2 |     0 | NULL                 |
| 563 | footer_left  |       562 |    2 |    3 |     1 | Food & Drink         |
| 562 | footer_right |      NULL |    1 |   16 |     0 | NULL                 |
| 564 | footer_right |       562 |    4 |    5 |     1 | Accommodations       |
| 565 | footer_right |       562 |    6 |    7 |     1 | Event Calendar       |
| 566 | footer_right |       562 |    8 |    9 |     1 | Things to Do         |
| 567 | footer_right |       562 |   10 |   11 |     1 | Travel Deals         |
| 568 | footer_right |       562 |   12 |   13 |     1 | Itineraries          |
| 569 | footer_right |       562 |   14 |   15 |     1 | Blog                 |
| 550 | main         |      NULL |    1 |   20 |     0 | NULL                 |
| 551 | main         |       550 |    2 |    9 |     1 | Festivals and Events |
| 552 | main         |       551 |    3 |    4 |     2 | Food & Drink         |
| 553 | main         |       551 |    5 |    6 |     2 | Accommodations       |
| 554 | main         |       551 |    7 |    8 |     2 | Event Calendar       |
| 555 | main         |       550 |   10 |   17 |     1 | Things to Do         |
| 556 | main         |       555 |   11 |   12 |     2 | Food & Drink         |
| 557 | main         |       555 |   13 |   14 |     2 | Accommodations       |
| 558 | main         |       555 |   15 |   16 |     2 | Event Calendar       |
| 559 | main         |       550 |   18 |   19 |     1 | Travel Deals         |
| 560 | sub          |      NULL |    1 |    2 |     0 | NULL                 |
+-----+--------------+-----------+------+------+-------+----------------------+

So, id 563, the one I moved, has changed scope. But note that its parent_id has not changed. Its lft and rgt happen to be right, but they're the same as they were before (which was also right). The depth is right. But also note that the footer_left root's lft and rgt have not been updated -- in particular, the rgt should now be 4. footer_right's subtree hasn't updated its lft and rgt values either, but I'm not sure if that's an issue or not.

So something above is broken.

It gets interesting when I move a different menu item. Re-seeding the database I have this:

+-----+--------------+-----------+------+------+-------+----------------------+
| id  | menu         | parent_id | lft  | rgt  | depth | name                 |
+-----+--------------+-----------+------+------+-------+----------------------+
| 581 | footer_left  |      NULL |    1 |    2 |     0 | NULL                 |
| 582 | footer_right |      NULL |    1 |   16 |     0 | NULL                 |
| 583 | footer_right |       582 |    2 |    3 |     1 | Food & Drink         |
| 584 | footer_right |       582 |    4 |    5 |     1 | Accommodations       |
| 585 | footer_right |       582 |    6 |    7 |     1 | Event Calendar       |
| 586 | footer_right |       582 |    8 |    9 |     1 | Things to Do         |
| 587 | footer_right |       582 |   10 |   11 |     1 | Travel Deals         |
| 588 | footer_right |       582 |   12 |   13 |     1 | Itineraries          |
| 589 | footer_right |       582 |   14 |   15 |     1 | Blog                 |
| 570 | main         |      NULL |    1 |   20 |     0 | NULL                 |
| 571 | main         |       570 |    2 |    9 |     1 | Festivals and Events |
| 572 | main         |       571 |    3 |    4 |     2 | Food & Drink         |
| 573 | main         |       571 |    5 |    6 |     2 | Accommodations       |
| 574 | main         |       571 |    7 |    8 |     2 | Event Calendar       |
| 575 | main         |       570 |   10 |   17 |     1 | Things to Do         |
| 576 | main         |       575 |   11 |   12 |     2 | Food & Drink         |
| 577 | main         |       575 |   13 |   14 |     2 | Accommodations       |
| 578 | main         |       575 |   15 |   16 |     2 | Event Calendar       |
| 579 | main         |       570 |   18 |   19 |     1 | Travel Deals         |
| 580 | sub          |      NULL |    1 |    2 |     0 | NULL                 |
+-----+--------------+-----------+------+------+-------+----------------------+

If I now move 'Accommodations' (584) from footer_right to footer_left, I end up with this:

+-----+--------------+-----------+------+------+-------+----------------------+
| id  | menu         | parent_id | lft  | rgt  | depth | name                 |
+-----+--------------+-----------+------+------+-------+----------------------+
| 581 | footer_left  |      NULL |    1 |    4 |     0 | NULL                 |
| 584 | footer_left  |       581 |    2 |    3 |     1 | Accommodations       |
| 582 | footer_right |      NULL |    1 |   16 |     0 | NULL                 |
| 583 | footer_right |       582 |    4 |    5 |     1 | Food & Drink         |
| 585 | footer_right |       582 |    6 |    7 |     1 | Event Calendar       |
| 586 | footer_right |       582 |    8 |    9 |     1 | Things to Do         |
| 587 | footer_right |       582 |   10 |   11 |     1 | Travel Deals         |
| 588 | footer_right |       582 |   12 |   13 |     1 | Itineraries          |
| 589 | footer_right |       582 |   14 |   15 |     1 | Blog                 |
| 570 | main         |      NULL |    1 |   20 |     0 | NULL                 |
| 571 | main         |       570 |    2 |    9 |     1 | Festivals and Events |
| 572 | main         |       571 |    3 |    4 |     2 | Food & Drink         |
| 573 | main         |       571 |    5 |    6 |     2 | Accommodations       |
| 574 | main         |       571 |    7 |    8 |     2 | Event Calendar       |
| 575 | main         |       570 |   10 |   17 |     1 | Things to Do         |
| 576 | main         |       575 |   11 |   12 |     2 | Food & Drink         |
| 577 | main         |       575 |   13 |   14 |     2 | Accommodations       |
| 578 | main         |       575 |   15 |   16 |     2 | Event Calendar       |
| 579 | main         |       570 |   18 |   19 |     1 | Travel Deals         |
| 580 | sub          |      NULL |    1 |    2 |     0 | NULL                 |
+-----+--------------+-----------+------+------+-------+----------------------+

Again, I'm not sure if it's an issue that footer_right root and children's lft and rgt values haven't changed, but this time the moved node 584 has the correct parent_id, lft and rgt, and the footer_left root 581 has the correct lft and rgt too.

The issue above occurs whether I run MenuItem::rebuild() after all this or not. I also tried running MenuItem::rebuild() between passes (after I change the scopes where necessary), and I have the same issue.

Is there something I'm doing wrong? Or is this a bug?

How to use with Pivot Tables?

Hello, can anyone help me find out, how to use baum with Pivot Tables?

I have created those tables:
Categories table:

categories
- id 
- name 
...

Articles table:

articles
- id
- title
...

Articles and categories table

article_category
- id
- category_id
- article_id

The categories tree looks like this:

News - ID - 1 (Root)
...
Sport - ID - 2 (Root)
 - Soccer - ID ( 3 ) ( Parent - Sport )
 - Basketball - ID ( 4 ) ( Parent - Sport )
... 

in my Article model I have categories() method

public function categories() {
    return $this->belongsToMany('Category');
}

So, what I need is here, to get all Articles from Sport's Category ( including Soccer and Basketball ).

Can anyone help me to write that 'query'?

Thanks!

[Proposal] Nested tree Flat to hierarchical array

Would be good to have this addition somewhere. Maybe in the collection returned? I didn't examine closely a way to override the Collection sets in Eloquent.

It takes a flat array with all the tree and converts it in a hierarchical array. I setted a path specifically for my models, it would be better having the option to specify a path settings or something.

I took it from: http://www.sitepoint.com/forums/showthread.php?143727-Converting-Nested-Set-Array-into-a-Nested-Array

       function nestedArray(&$result, $right = 'rgh', $left = 'lft', $path = '', $level = -1) {
            $new = array();
            if(is_array($result)) {
                while(list($n, $sub) = each($result)) {
                    $subId = $sub['id'];
                    $new[$subId] = $sub;
                    $alias = (isset($sub['data']))?$sub['data']['alias']:'';
                    $new[$subId]['_tree_path'] = $path.'/'.$alias;
                    $new[$subId]['_tree_level'] = $level + 1;

                    if($sub[$right] - $sub[$left] != 1) {
                        // recurse ($result is manipulated by reference!)
                        $new[$subId]['children'] = nestedArray($result, $right, $left, $new[$subId]['_tree_path'], $new[$subId]['_tree_level']);
                    }

                    $next_id = key($result);
                    if($next_id && $result[$next_id]['parent_id'] != $sub['parent_id']) {
                        return $new;
                    }
                }
            }
            return $new;
        }

Event for updating children while parent moved.

It would be useful to have an additional event handler for the case of updating children while it's parent has just moved. Currently, every descendant of moved node gets save()'d here:

https://github.com/etrepat/baum/blob/master/src/Baum/Move.php#L120

However it's hard to distinguish if the saved event of each descendant was fired due to parent's move within the tree. I think it would be useful for the descendant nodes to "know" that their parent has just moved, so they can be updated accordingly.

Laravel 4.1

Now that laravel 4.1 is out, can you please update the composer file, so it can be used here aswell? Right now it conflicts.

makeRoot() fails on already existing items

Hello,

I have a table and want to make it a nested set table. So I added the neccessary columns and adding new elements/items to that table works really well.
However, I have to convert the already existing items to root elements. I tried the following:

Category::find(2)->makeRoot();

However, I'm running into the following error then:

PHP Fatal error:  Uncaught exception 'InvalidArgumentException' with message 'Value must be provided.' in /var/www/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:350
Stack trace:
#0 [internal function]: Illuminate\Database\Query\Builder->where('lft', '<=', NULL)
#1 /var/www/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(606): call_user_func_array(Array, Array)
#2 /var/www/vendor/baum/baum/src/Baum/Node.php(405): Illuminate\Database\Eloquent\Builder->where('lft', '<=', NULL)
#3 /var/www/vendor/baum/baum/src/Baum/Node.php(385): Baum\Node->ancestorsAndSelf()
#4 /var/www/vendor/baum/baum/src/Baum/Node.php(756): Baum\Node->getRoot()
#5 /var/www/vendor/d11wtq/boris/lib/Boris/EvalWorker.php(133) : eval()'d code(1): Baum\Node->makeRoot()
#6 /var/www/vendor/d11wtq/boris/lib/Boris/EvalWorker.php(133): eval()
#7 /var/www/vendor/d11wtq/boris/lib/Boris/Boris.php(171): Boris\EvalWorker->start()
#8 /var/www/vendor/laravel/framework/src/Illuminate/Foundation/Console/TinkerCommand.php(49): Boris\Boris in /var/www/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php on line 350

The error is clear, but I don't know how to solve it? Am I doing sth wrong? Or is there any other solution to my problem?

toHierarchy Method and unexpected behaviour

toHierarchy() method does not work as expected always
I have following category table
categories

When we use
case 1: Category::all()->toHierarchy() it works fine
case 2:
Category::where('name','=','Child 1')->getDescendantsAndSelf(array('id','name'))->toHierarchy()
as given in the documentation it
gives error "Call to undefined method Baum\Extensions\Query\Builder::getDescendantsAndSelf()"

case 3: Category::where('name','=','Child 1')->first()->getDescendantsAndSelf(array('id','name'))->toHierarchy
gives wrong hierarchy tree as following

|-Child 1
  | --  Child 1.1
      | --  Child 1.2

Rather it should give

|-Child 1
  | --  Child 1.1
  | --  Child 1.2

What is getting wrong? Or I am not using it proper way? Thank you.

Question about efficiency

Hey,

First of all, great project, thanks for building it.

I was reading the theory behind nested sets which states

Nested sets are very slow for inserts because it requires updating left and right domain values for all records in the table after the insert. This can cause a lot of database thrash[citation needed] as many rows are rewritten and indexes rebuilt.

My use case is tags. Organisations have many tags, which they can use to tag other models that they own (e.g. assets). This seems similar to your categories example. However, unlike categories of which there may only be a small amount (say for a shop), there could be a lot of tags in that table. Everyone's tags from across the whole product, but they would only own / be able to access a subset of those. It would be possible that this table could get big, and I'm worried about the intensity of those rewrites that have to occur.

Any thoughts?

Moving root nodes

Can't move root nodes.
I have two root nodes $rootNodeOne and $rootNodeTwo

$rootNodeTwo->moveToLeftOf($rootNodeOne);

Got SQL error.

Exception when running Node::rebuild()

I'm trying to use the develop branch. When I run the rebuild static method I get an exception:

Call to undefined method Baum\Extensions\Query\Builder::rebuild()

How do Traverse Hierarchy?

Once you have a tree hierarchy using the ->toHiearchy() method how do you traverse it to output a

    list or options for a drop down list?

MakeRoot when already Root

When I'm trying to rebuild my whole tree structure, I get an error on my first root node when I call $node->makeRoot();

I'm looping through an array which contains all the new data.

new array:

   [0] => array(
        id => 1,
        children => array(
               [0] => array(
                   id => 3
               ),
               [1] => array(
                   id => 2
               ),
        ),
   )

old array, id 2 and 3 switched

Code:

function changeTree(parentId, $children)
{
        $parent = getNodeFromDB($parentId);

        foreach($children as $child)
        {
            $node = getNodeFromDB($child['id']);

            if(empty($parent))
            {
                //IS ROOT
                $node->makeRoot();
            }
            else
            {
                //IS CHILD OF $PARENT
                $node->makeChildOf($parent);
            }

            if(!empty($child['children']))
            {
                $this->changeTree($parent->id, $child['children']);
            }
        }
}

Error 'A node cannot be moved to itself.' is set at the first node which is a root node (parentId = 0)

How can I change this to rebuild my whole tree structure?

Use traits instead.

It might be nicer to use this feature if people didn't have to extend your particular model class to make use of it.

version 1.0.10

Hi I download the newest version

There is a run out of memory problem when I use the makeChildOf method.
I configure the php.ini file
Nothing change.

But when I use version 1.0.9, it works perfectly

Can't use method return value in write context

Hey!

With PHP 5.4.9 and dev-develop branch of baum I get this error:

Can't use method return value in write context

open: vendor/baum/baum/src/Baum/Node.php
  /**
   * Returns wether this particular node instance is scoped by certain fields
   * or not.
   *
   * @return boolean
   */
  public function isScoped() {
    return !empty($this->getScopedColumns());
  }

Could you fix this for < PHP 5.5.x users, since returning empty with a function/method only works for 5.5.x.

Thanks!

Upgrade to ~4

Hi, this looks to be an excellent package, exactly what I was looking for.

Do you have a time scale for merging the develop branch into master to update the Laravel dependencies to ~4? I'm currently working with the develop branch and everything looks to be working correctly.

Accessing the ancestry/descendancy chain problem

Hello!

In docs you wrote that i can dump a hierarchy tree in this way:

Category::where('name', '=', Books)->getDescendantsAndSelf()->toHierarchy();

Unfortunately it doesn't works.

The only way i could get the whole tree is this:

$category = Category::where('name', '=', Books)->first();
$category->getDescendantsAndSelf()->toHierarchy();

Is it my fault or it's a bug, or maybe it's a problem in the doc?
I'm using laravel 4.1 i dont't know if it's matter in this case.

Thanks!

Support for PHP 5.3

It appears that this package was written with PHP 5.4 in mind, as it uses shorthand array syntax (i.e. ['foo', 'bar']). This feature was introduced in PHP 5.4, and subsequently causes parse errors in PHP 5.3.

It would be a good idea to use the traditional array definition syntax in order to make this package compatible with PHP >= 5.3.7, which is what Laravel 4 supports.

Scoping

I tried the provided scoping example:

$root1 = Category::create(['name' => 'R1', 'company_id' => 1]);
$root2 = Category::create(['name' => 'R2', 'company_id' => 2]);

$child1 = Category::create(['name' => 'C1', 'company_id' => 1]);
$child2 = Category::create(['name' => 'C2', 'company_id' => 2]);

$child1->makeChildOf($root1);
$child2->makeChildOf($root2);

$root1->children()->get(); // <- returns $child1
$root2->children()->get(); // <- returns $child2

and got this in DB:

+----+------+-----------+------+------+-------+---------------------+---------------------+----------+
| id | name | parent_id | lft  | rgt  | depth | created_at          | updated_at          | ruler_id |
+----+------+-----------+------+------+-------+---------------------+---------------------+----------+
|  1 | R1   |      NULL |    1 |    4 |     0 | 2013-07-31 14:06:49 | 2013-07-31 14:06:49 |        1 |
|  2 | R2   |      NULL |    3 |    6 |     0 | 2013-07-31 14:06:49 | 2013-07-31 14:06:49 |        2 |
|  3 | C1   |         1 |    2 |    3 |     1 | 2013-07-31 14:06:49 | 2013-07-31 14:06:49 |        1 |
|  4 | C2   |         2 |    4 |    5 |     1 | 2013-07-31 14:06:49 | 2013-07-31 14:06:49 |        2 |
+----+------+-----------+------+------+-------+---------------------+---------------------+----------+

I'm not sure if this is expected. This is almost the same result as if

protected $scoped = array('ruler_id');

was not set at all.

I was expecting data structure such that if I delete from table where ruler_id 2 I'm left with a fully functional data set, but that's not the case.

Further more, I don't see a need for root nodes that would hold their forests. Use case example might be a Stores table as a parent of Categories table, where Categories table has a store_id as a scope. If I want to use scopes in this scenario I would have to first create a store node inside a Categories table where in fact I don't need it at all, and it pragmatically doesn't belong there.

I hope I explained the issue well enough.

Sorting Nodes?

Is there a way to change the default sort so nodes are listed alphabetically under their respective parent nodes?

Testing a baum model and boot function

Hey,

was just fighting with my very simple test function for my CategoryRepository (which extends my category node model) for two hours.

What I was trying to do:
Setup a simple category structure for testing within the setUp() test function. This works on the first test but if it comes to my second test the initialization of my test data it is not!

I found out it belongs to the way of binding the creating event within the boot function of Node.php .

Actually I have no idea if there is a better way for baum. I got it working for me by adding
CategoryRepository::boot();
within the setUp() of my CategoryRepositoryTest ...

This also belongs to this topic: laravel/framework#1181

<?php
use Zizaco\FactoryMuff\Facade\FactoryMuff;

class CategoryRepositoryTest extends TestCase
{
    public function setUp() 
    {
        parent::setUp();

        $this->Repository = new CategoryRepository;

        // This isfixing this problem https://github.com/laravel/framework/issues/1181
        CategoryRepository::boot();

        // Generate a category tree database
        $this->root1 = FactoryMuff::create('CategoryRepository');
        $this->root2 = FactoryMuff::create('CategoryRepository');
        $this->cat1 = FactoryMuff::create('CategoryRepository');
        $this->cat2 = FactoryMuff::create('CategoryRepository');
        $this->cat3 = FactoryMuff::create('CategoryRepository');
        // Build levels
        $this->cat1->makeChildOf($this->root1);
        $this->cat2->makeChildOf($this->root2);
        $this->cat3->makeChildOf($this->cat1);

    }

    public function testSetup() {
        $this->assertTrue( $this->root1->isRoot() ,'root1 is not a root cat');
        $this->assertTrue( $this->root2->isRoot() ,'root2 is not a root cat');

        $this->assertFalse( $this->cat1->isRoot() ,'cat1 is a root cat');
        $this->assertFalse( $this->cat3->isRoot() ,'cat2 is a root cat');

        $this->assertEquals( $this->cat1->parent_id, $this->cat1->parent()->first()->id ,'cat1 is not a child of root1');
        $this->assertEquals( $this->cat3->parent_id, $this->cat1->id ,'cat3 is not a child of cat1');
    }

    public function testGetTree() {

    }
    public function testGetTree2() {

    }
}
?>

[ERROR] Using $this when not in object context

I'm sorry if i implemented wrong but i cant get around this error. This error is thrown on /vendor/baum/baum/src/Baum/Node.php on line 697.

/**

  • Sets the depth attribute
    *

  • @return \Baum\Node
    */
    public function setDepth() {
    $this->getConnection()->transaction(function() {
    -----> $this->reload();

    $level = $this->getLevel();

    $this->newQuery()->where($this->getKeyName(), '=', $this->getKey())->update(array($this->getDepthColumnName() => $level));
    $this->setAttribute($this->getDepthColumnName(), $level);
    });

    return $this;
    }

Eager loading children

How do I eager load all a model's descendants? I'm traversing the tree from a given node and every time I ask for a node's children it's sending out another database query. They're really adding up.

I'm using toHierarchy in a different area of my code which already seems very awkward since I have to get a single Node model, then call the method, and now I've got a collection I need to get the first element of again. I can live with that, though I'd prefer to just start with the Node and be able to grab children without a new query actually being made, having already preloaded all children. But anyway, when I traverse through the Collection toHierarchy gave me I still get new database queries getting fired off.

In that case I'm doing

$menus = [];
foreach (MenuItem::roots()->get() as $root) {
    $menus[$root->menu] = $root->getDescendantsAndSelf()->toHierarchy();
}

The view has

@foreach ($menus as $menu => $hierarchy)
    <?php $root = $hierarchy->first(); ?>
    <div class="menu panel panel-primary" data-id="{{{ $root->getKey() }}}">
...
        @include('admin.menu-items.children', ['model' => $root])
...
    </div>
@endforeach

The children partial view has

<ol class="nested-sortable">
    @foreach ($model->children as $node)
        @include('admin.menu-items.item', ['model' => $node, 'parentId' => $model->getKey()])
    @endforeach
</ol>

The item partial view prints an <li> in which the children partial is called again recursively on $model. There are more and more queries being fired off.

Add trunks() to complement leaves() methods

Hi,

It'd be great if Node had a trunks() family of methods to retrieve all descendants which aren't leaves. Something like:

  public function trunks() {
    $grammar = $this->getConnection()->getQueryGrammar();

    $rgtCol = $grammar->wrap($this->getQualifiedRightColumnName());
    $lftCol = $grammar->wrap($this->getQualifiedLeftColumnName());

    return $this->descendants()
                ->whereRaw($rgtCol . ' - ' . $lftCol . ' != 1');
  }

  public function getTrunks($columns = array('*')) {
    return $this->trunks()->get($columns);
  }

  public static function allTrunks() {
    $instance = new static;

    $grammar = $instance->getConnection()->getQueryGrammar();

    $rgtCol = $grammar->wrap($instance->getQualifiedRightColumnName());
    $lftCol = $grammar->wrap($instance->getQualifiedLeftColumnName());

    return $instance->newNestedSetQuery()
                    ->whereRaw($rgtCol . ' - ' .$lftCol . ' != 1');
  }

  public function isTrunk() {
    return $this->exists && ($this->getRight() - $this->getLeft() != 1);
  }

Thing::roots()->orderBy('label') not working

This is a fantastic thing you have done here, thank you.

I controllers I have been trying to order the roots using something like Thing::roots()->orderBy('label'). It's not working and i am not getting any errors. There are other ways i can do this, but I figured I would leave a note here in the off case that it should be working.

bug doesnt set nodes as root

Hi there,

I just ran into this bug. When having a node with parent_id set, setting parent_id to null will not result in the node being made root. I figured the reason lies in the function Node.php/storeNewParent()

public function storeNewParent() {
    $dirty = $this->getDirty();

    if ( isset($dirty[$this->getParentColumnName()]) )

Instead it should be

public function storeNewParent() {
    if ( $this->isDirty($this->getParentColumnName()) && !(!$this->exists && $this->isRoot()) ) {

As isset returns false when the value is null, the is_null in the following function moveToNewParent() can otherwise never be true.

Edit: Added check to avoid setting new root node as root again - this throws an error in makeRoot() otherwise.

Nested with items

I understand this nested is for managing category but how do I attach item to it let's say my Item belongs to women and dress.
Do I create another table to say item belongs to index of women and dress ? or does this package has got some function to handle this ?

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.