Created
May 30, 2013 06:24
-
-
Save Meroje/5676023 to your computer and use it in GitHub Desktop.
Materialized Path Tree
http://forums.laravel.io/viewtopic.php?pid=40769
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
with(new Page())->makeRoot(); | |
with(new Page())->makePreviousSiblingOf(Page::find(1)) | |
with(new Page())->makeNextSiblingOf(Page::find(1)) | |
with(new Page())->makeLastChildOf(Page::find(5)) | |
with(new Page())->makeFirstChildOf(Page::find(2)) | |
Page::find(2)->children() | |
Page::find(2)->parent() | |
Page::find(2)->sibling() | |
Page::find(2)->isDescendant(Page::find(3)) | |
Page::find(2)->isAncestor(Page::find(3)) | |
Page::find(2)->isLeaf() | |
Page::allRoot() | |
Page::allLeaf() | |
Page::find(2)->getTreeDepth() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
Schema::create('yourtable', function(Blueprint $table) | |
{ | |
$table->increments('id'); | |
$table->timestamps(); | |
$table->softDeletes(); | |
$table->string('title'); | |
$table->string('tree_path'); | |
$table->integer('tree_order')->unsigned()->default(0); | |
$table->integer('tree_pid')->unsigned()->default(0); | |
$table->integer('tree_depth')->unsigned()->default(0); | |
$table->index('tree_path'); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Model\Tree; | |
abstract class MaterializedPathTable extends \Eloquent { | |
protected $columnTreePid = 'tree_pid'; | |
protected $columnTreeOrder = 'tree_order'; | |
protected $columnTreePath = 'tree_path'; | |
protected $columnTreeDepth = 'tree_depth'; | |
public function makeRoot() | |
{ | |
$this->fill(array( | |
$this->columnTreePath => '.0.', | |
$this->columnTreePid => 0, | |
$this->columnTreeDepth => 0, | |
$this->columnTreeOrder => (static::allRoot()->max($this->columnTreeOrder) + 1) | |
)); | |
$this->save(); | |
return $this; | |
} | |
public function makeFirstChildOf($parent) | |
{ | |
if ($this->exists and $this->isAncestor($parent)) throw new \Exception('Cant move Ancestor to Descendant'); | |
if (!$parent->exists) throw new \Exception('Parent doesnt exist'); | |
$this_ = $this; | |
\DB::transaction(function() use (&$this_, &$parent) | |
{ | |
$parent->children(1)->increment($this_->getColumnTreeOrder()); | |
if ($this_->exists) | |
{ | |
$children = $this_->children()->get(); | |
foreach($children as $child) | |
{ | |
$child->update(array( | |
$child->getColumnTreePath() => str_replace($this_->getTreePath(), $parent->getTreePath().$parent->getKey().'.', $child->getTreePath()), | |
$child->getColumnTreeDepth() => ( $parent->getTreeDepth() + 1 + ($child->getTreeDepth() - $this_->getTreeDepth()) ), | |
)); | |
} | |
} | |
$this_->fill(array( | |
$this_->getColumnTreePath() => $parent->getTreePath().$parent->getKey().'.', | |
$this_->getColumnTreePid() => $parent->getKey(), | |
$this_->getColumnTreeOrder() => 0, | |
$this_->getColumnTreeDepth() => ($parent->getTreeDepth() + 1) | |
)); | |
$this_->save(); | |
}); | |
return $this; | |
} | |
public function makeLastChildOf($parent) | |
{ | |
if ($this->exists and $this->isAncestor($parent)) throw new \Exception('Cant move Ancestor to Descendant'); | |
if (!$parent->exists) throw new \Exception('Parent doesnt exist'); | |
$this_ = $this; | |
\DB::transaction(function() use (&$this_, &$parent) | |
{ | |
if ($this_->exists) | |
{ | |
$children = $this_->children()->get(); | |
foreach($children as $child) | |
{ | |
$child->update(array( | |
$child->getColumnTreePath() => str_replace($this_->getTreePath(), $parent->getTreePath().$parent->getKey().'.', $child->getTreePath()), | |
$child->getColumnTreeDepth() => ( $parent->getTreeDepth() + 1 + ($child->getTreeDepth() - $this_->getTreeDepth()) ), | |
)); | |
} | |
} | |
$this_->fill(array( | |
$this_->getColumnTreePath() => $parent->getTreePath().$parent->getKey().'.', | |
$this_->getColumnTreePid() => $parent->getKey(), | |
$this_->getColumnTreeOrder() => ($parent->children(1)->max($parent->getColumnTreeOrder())+1), | |
$this_->getColumnTreeDepth() => ($parent->getTreeDepth() + 1) | |
)); | |
$this_->save(); | |
}); | |
return $this; | |
} | |
public function makePreviousSiblingOf($sibling) | |
{ | |
return $this->processSiblingOf($sibling, '>='); | |
} | |
public function makeNextSiblingOf($sibling) | |
{ | |
return $this->processSiblingOf($sibling, '>'); | |
} | |
public function parent() | |
{ | |
return $this->newQuery()->where($this->getKeyName(), '=', $this->getTreePid()); | |
} | |
public function sibling() | |
{ | |
return $this->newQuery()->where($this->columnTreePid, '=', $this->getTreePid()); | |
} | |
public function children($depth=0) | |
{ | |
$query = $this->newQuery(); | |
if ($depth==1) | |
{ | |
$query->where($this->columnTreePid, '=', $this->getKey()); | |
} | |
else | |
{ | |
$query->where($this->columnTreePath, 'like', $this->getTreePath().$this->getKey().'.%'); | |
} | |
if ($depth) | |
{ | |
$query->where($this->columnTreeDepth, '<=', $this->getTreeDepth() + $depth); | |
} | |
return $query; | |
} | |
public function isDescendant($ancestor) | |
{ | |
if (!$this->exists) throw new \Exception('Model doesnt exist'); | |
return strpos($this->getTreePath(), $ancestor->getTreePath().$ancestor->getKey().'.')!==false and $ancestor->getTreePath()!==$this->getTreePath(); | |
} | |
public function isAncestor($descendant) | |
{ | |
if (!$this->exists) throw new \Exception('Model doesnt exist'); | |
return strpos($descendant->getTreePath(), $this->getTreePath().$this->getKey().'.')!==false and $descendant->getTreePath()!==$this->getTreePath(); | |
} | |
public function isLeaf() | |
{ | |
if (!$this->exists) throw new \Exception('Model doesnt exist'); | |
return !count($this->children(1)->get()->toArray()); | |
} | |
public function relativeDepth($object) | |
{ | |
return abs($this->getTreeDepth() - $object->getTreeDepth()); | |
} | |
public static function allRoot() | |
{ | |
$instance = new static; | |
$query = $instance->newQuery()->where($instance->getColumnTreePid(), '=', 0); | |
return $query; | |
} | |
public static function allLeaf() | |
{ | |
$instance = with(new static); | |
$query = $instance->newQuery(); | |
$query->select($instance->getTable().'.*'); | |
$query->leftJoin($instance->getTable().' as t_2', function($join) use ($instance) | |
{ | |
$join->on($instance->getTable().'.'.$instance->getKeyName(), '=', 't_2.'.$instance->getColumnTreePid()); | |
}) | |
->whereNull('t_2.id'); | |
return $query; | |
} | |
public function getTreePid() | |
{ | |
return $this->getAttribute($this->columnTreePid); | |
} | |
public function getTreeOrder() | |
{ | |
return $this->getAttribute($this->columnTreeOrder); | |
} | |
public function getTreePath() | |
{ | |
return $this->getAttribute($this->columnTreePath); | |
} | |
public function getTreeDepth() | |
{ | |
return $this->getAttribute($this->columnTreeDepth); | |
} | |
public function getColumnTreePid() | |
{ | |
return $this->columnTreePid; | |
} | |
public function getColumnTreeOrder() | |
{ | |
return $this->columnTreeOrder; | |
} | |
public function getColumnTreePath() | |
{ | |
return $this->columnTreePath; | |
} | |
public function getColumnTreeDepth() | |
{ | |
return $this->columnTreeDepth; | |
} | |
public function setColumnTreePid($name) | |
{ | |
$this->columnTreePid = $name; | |
} | |
public function setColumnTreeOrder($name) | |
{ | |
$this->columnTreeOrder = $name; | |
} | |
public function setColumnTreePath($name) | |
{ | |
$this->columnTreePath = $name; | |
} | |
public function setColumnTreeDepth($name) | |
{ | |
$this->columnTreeDepth = $name; | |
} | |
protected function processSiblingOf($sibling, $op) | |
{ | |
if ($this->exists and $this->isAncestor($sibling)) throw new \Exception('Cant move Ancestor to Descendant'); | |
if (!$sibling->exists) throw new \Exception('Sibling doesnt exist'); | |
$this_ = &$this; | |
\DB::transaction(function() use (&$this_, &$sibling, $op) | |
{ | |
$sibling->sibling()->where($this_->getColumnTreeOrder(), $op, $sibling->getTreeOrder())->increment($this_->getColumnTreeOrder()); | |
if ($this_->exists) | |
{ | |
$children = $this_->children()->get(); | |
foreach($children as $child) | |
{ | |
$child->update(array( | |
$child->getColumnTreePath() => str_replace($this_->getTreePath(), $sibling->getTreePath(), $child->getTreePath()), | |
$child->getColumnTreeDepth() => ( $sibling->getTreeDepth() + ($child->getTreeDepth() - $this_->getTreeDepth()) ), | |
)); | |
} | |
} | |
$this_->fill(array( | |
$this_->getColumnTreePath() => $sibling->getTreePath(), | |
$this_->getColumnTreePid() => $sibling->getTreePid(), | |
$this_->getColumnTreeOrder() => $sibling->getTreeOrder()+($op=='>'?1:0), | |
$this_->getColumnTreeDepth() => $sibling->getTreeDepth(), | |
)); | |
$this_->save(); | |
}); | |
return $this; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment