1
0
mirror of synced 2024-12-13 14:56:01 +03:00

cleaning up draft/

This commit is contained in:
romanb 2007-10-21 09:00:40 +00:00
parent 7dbb38719d
commit 7d1e03901a
28 changed files with 14 additions and 3432 deletions

View File

@ -1,386 +0,0 @@
<?php
/*please note that this is a very DRAFT and basic example of how you can use the different functions available in the tree implentation*/
require_once("../lib/Doctrine.php");
// autoloading objects, modified function to search drafts folder first, should run this test script from the drafts folder
spl_autoload_register(array('Doctrine', 'autoload'));
// define our tree
class Menu extends Doctrine_Record {
public function setTableDefinition() {
$this->setTableName('menu');
// add this your table definition to set the table as NestedSet tree implementation
$this->actsAsTree('NestedSet');
// you do not need to add any columns specific to the nested set implementation, these are added for you
$this->hasColumn("name","string",30);
}
// this __toString() function is used to get the name for the path, see node::getPath
public function __toString() {
return $this->get('name');
}
}
// set connections to database
$dsn = 'mysql:dbname=nestedset;host=localhost';
$user = 'user';
$password = 'pass';
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$manager = Doctrine_Manager::getInstance();
$conn = $manager->openConnection($dbh);
// create root
$root = new Menu();
$root->set('name', 'root');
$manager->getTable('Menu')->getTree()->createRoot($root);
// build tree
$two = new Menu();
$two->set('name', '2');
$root->getNode()->addChild($two);
$one = new Menu();
$one->set('name', '1');
$one->getNode()->insertAsPrevSiblingOf($two);
// refresh as node's lft and rgt values have changed, zYne, can we automate this?
$two->refresh();
$three = new Menu();
$three->set('name', '3');
$three->getNode()->insertAsNextSiblingOf($two);
$two->refresh();
$one_one = new Menu();
$one_one->set('name', '1.1');
$one_one->getNode()->insertAsFirstChildOf($one);
$one->refresh();
$one_two = new Menu();
$one_two->set('name', '1.2');
$one_two->getNode()->insertAsLastChildOf($one);
$one_two->refresh();
$one_two_one = new Menu();
$one_two_one->set('name', '1.2.1');
$one_two->getNode()->addChild($one_two_one);
$root->refresh();
$four = new Menu();
$four->set('name', '4');
$root->getNode()->addChild($four);
$root->refresh();
$five = new Menu();
$five->set('name', '5');
$root->getNode()->addChild($five);
$root->refresh();
$six = new Menu();
$six->set('name', '6');
$root->getNode()->addChild($six);
output_message('initial tree');
output_tree($root);
$one_one->refresh();
$six->set('name', '1.0 (was 6)');
$six->getNode()->moveAsPrevSiblingOf($one_one);
$one_two->refresh();
$five->refresh();
$five->set('name', '1.3 (was 5)');
$five->getNode()->moveAsNextSiblingOf($one_two);
$one_one->refresh();
$four->refresh();
$four->set('name', '1.1.1 (was 4)');
$four->getNode()->moveAsFirstChildOf($one_one);
$root->refresh();
$one_two_one->refresh();
$one_two_one->set('name', 'last (was 1.2.1)');
$one_two_one->getNode()->moveAsLastChildOf($root);
output_message('transformed tree');
output_tree($root);
$one_one->refresh();
$one_one->deleteNode();
output_message('delete 1.1');
output_tree($root);
// now test fetching root
$tree_root = $manager->getTable('Menu')->getTree()->findRoot();
output_message('testing fetch root and outputting tree from the root node');
output_tree($tree_root);
// now test fetching the tree
output_message('testing fetching entire tree using tree::fetchTree()');
$tree = $manager->getTable('Menu')->getTree()->fetchTree();
while($node = $tree->next())
{
output_node($node);
}
// now test fetching the tree
output_message('testing fetching entire tree using tree::fetchTree(), excluding root node');
$tree = $manager->getTable('Menu')->getTree()->fetchTree(array('include_record' => false));
while($node = $tree->next())
{
output_node($node);
}
// now test fetching the branch
output_message('testing fetching branch for 1, using tree::fetchBranch()');
$one->refresh();
$branch = $manager->getTable('Menu')->getTree()->fetchBranch($one->get('id'));
while($node = $branch->next())
{
output_node($node);
}
// now test fetching the tree
output_message('testing fetching branch for 1, using tree::fetchBranch() excluding node 1');
$tree = $manager->getTable('Menu')->getTree()->fetchBranch($one->get('id'), array('include_record' => false));
while($node = $tree->next())
{
output_node($node);
}
// now perform some tests
output_message('descendants for 1');
$descendants = $one->getNode()->getDescendants();
while($descendant = $descendants->next())
{
output_node($descendant);
}
// move one and children under two
$two->refresh();
$one->getNode()->moveAsFirstChildOf($two);
output_message('moved one as first child of 2');
output_tree($root);
output_message('descendants for 2');
$two->refresh();
$descendants = $two->getNode()->getDescendants();
while($descendant = $descendants->next())
{
output_node($descendant);
}
output_message('number descendants for 2');
echo $two->getNode()->getNumberDescendants() .'</br>';
output_message('children for 2 (notice excludes children of children, known as descendants)');
$children = $two->getNode()->getChildren();
while($child = $children->next())
{
output_node($child);
}
output_message('number children for 2');
echo $two->getNode()->getNumberChildren() .'</br>';
output_message('path to 1');
$path = $one->getNode()->getPath(' > ');
echo $path .'<br />';
output_message('path to 1 (including 1)');
$path = $one->getNode()->getPath(' > ', true);
echo $path .'<br />';
output_message('1 has parent');
$hasParent = $one->getNode()->hasParent();
$msg = $hasParent ? 'true' : 'false';
echo $msg . '</br/>';
output_message('parent to 1');
$parent = $one->getNode()->getParent();
if($parent->exists())
{
echo $parent->get('name') .'<br />';
}
output_message('root isRoot?');
$isRoot = $root->getNode()->isRoot();
$msg = $isRoot ? 'true' : 'false';
echo $msg . '</br/>';
output_message('one isRoot?');
$isRoot = $one->getNode()->isRoot();
$msg = $isRoot ? 'true' : 'false';
echo $msg . '</br/>';
output_message('root hasParent');
$hasParent = $root->getNode()->hasParent();
$msg = $hasParent ? 'true' : 'false';
echo $msg . '</br/>';
output_message('root getParent');
$parent = $root->getNode()->getParent();
if($parent->exists())
{
echo $parent->get('name') .'<br />';
}
output_message('get first child of root');
$record = $root->getNode()->getFirstChild();
if($record->exists())
{
echo $record->get('name') .'<br />';
}
output_message('get last child of root');
$record = $root->getNode()->getLastChild();
if($record->exists())
{
echo $record->get('name') .'<br />';
}
$one_two->refresh();
output_message('get prev sibling of 1.2');
$record = $one_two->getNode()->getPrevSibling();
if($record->exists())
{
echo $record->get('name') .'<br />';
}
output_message('get next sibling of 1.2');
$record = $one_two->getNode()->getNextSibling();
if($record->exists())
{
echo $record->get('name') .'<br />';
}
output_message('siblings of 1.2');
$siblings = $one_two->getNode()->getSiblings();
foreach($siblings as $sibling)
{
if($sibling->exists())
echo $sibling->get('name') .'<br />';
}
output_message('siblings of 1.2 (including 1.2)');
$siblings = $one_two->getNode()->getSiblings(true);
foreach($siblings as $sibling)
{
if($sibling->exists())
echo $sibling->get('name') .'<br />';
}
$new = new Menu();
$new->set('name', 'parent of 1.2');
$new->getNode()->insertAsParentOf($one_two);
output_message('added a parent to 1.2');
output_tree($root);
try {
$dummy = new Menu();
$dummy->set('name', 'dummy');
$dummy->save();
}
catch (Doctrine_Exception $e)
{
output_message('You cannot save a node unless it is in the tree');
}
try {
$fake = new Menu();
$fake->set('name', 'dummy');
$fake->set('lft', 200);
$fake->set('rgt', 1);
$fake->save();
}
catch (Doctrine_Exception $e)
{
output_message('You cannot save a node with bad lft and rgt values');
}
// check last remaining tests
output_message('New parent is descendant of 1');
$one->refresh();
$res = $new->getNode()->isDescendantOf($one);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
output_message('New parent is descendant of 2');
$two->refresh();
$res = $new->getNode()->isDescendantOf($two);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
output_message('New parent is descendant of 1.2');
$one_two->refresh();
$res = $new->getNode()->isDescendantOf($one_two);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
output_message('New parent is descendant of or equal to 1');
$one->refresh();
$res = $new->getNode()->isDescendantOfOrEqualTo($one);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
output_message('New parent is descendant of or equal to 1.2');
$one_two->refresh();
$res = $new->getNode()->isDescendantOfOrEqualTo($one_two);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
output_message('New parent is descendant of or equal to 1.3');
$five->refresh();
$res = $new->getNode()->isDescendantOfOrEqualTo($new);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
function output_tree($root)
{
// display tree
// first we must refresh the node as the tree has been transformed
$root->refresh();
// next we must get the iterator to traverse the tree from the root node
$traverse = $root->getNode()->traverse();
output_node($root);
// now we traverse the tree and output the menu items
while($item = $traverse->next())
{
output_node($item);
}
unset($traverse);
}
function output_node($record)
{
echo str_repeat('-', $record->getNode()->getLevel()) . $record->get('name') . ' (has children:'.$record->getNode()->hasChildren().') '. ' (is leaf:'.$record->getNode()->isLeaf().') '.'<br/>';
}
function output_message($msg)
{
echo "<br /><strong><em>$msg</em></strong>".'<br />';
}

View File

@ -1,217 +0,0 @@
<?php
/*please note that this is a very DRAFT and basic example of how you can use the different functions available in the tree implentation with many roots in one table*/
require_once("../lib/Doctrine.php");
// autoloading objects, modified function to search drafts folder first, should run this test script from the drafts folder
function __autoload($classname) {
if (class_exists($classname)) {
return false;
}
if ( ! $path) {
$path = dirname(__FILE__);
}
$classpath = str_replace('Doctrine_', '',$classname);
$class = $path.DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR,$classpath) . '.php';
if ( !file_exists($class)) {
return Doctrine::autoload($classname);
}
require_once($class);
return true;
}
// define our tree
class Menu extends Doctrine_Record {
public function setTableDefinition() {
$this->setTableName('menu_many_roots');
// add this your table definition to set the table as NestedSet tree implementation
// with many roots
$this->actsAsTree('NestedSet', array('has_many_roots' => true));
// you do not need to add any columns specific to the nested set implementation, these are added for you
$this->hasColumn("name","string",30);
}
// this __toString() function is used to get the name for the path, see node::getPath
public function __toString() {
return $this->get('name');
}
}
// set connections to database
$dsn = 'mysql:dbname=nestedset;host=localhost';
$user = 'user';
$password = 'pass';
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$manager = Doctrine_Manager::getInstance();
$conn = $manager->openConnection($dbh);
// create root
$root = new Menu();
$root->set('name', 'root');
$manager->getTable('Menu')->getTree()->createRoot($root);
// build tree
$two = new Menu();
$two->set('name', '2');
$root->getNode()->addChild($two);
$one = new Menu();
$one->set('name', '1');
$one->getNode()->insertAsPrevSiblingOf($two);
// refresh as node's lft and rgt values have changed, zYne, can we automate this?
$two->refresh();
$three = new Menu();
$three->set('name', '3');
$three->getNode()->insertAsNextSiblingOf($two);
$two->refresh();
$one_one = new Menu();
$one_one->set('name', '1.1');
$one_one->getNode()->insertAsFirstChildOf($one);
$one->refresh();
$one_two = new Menu();
$one_two->set('name', '1.2');
$one_two->getNode()->insertAsLastChildOf($one);
$one_two->refresh();
$one_two_one = new Menu();
$one_two_one->set('name', '1.2.1');
$one_two->getNode()->addChild($one_two_one);
$root->refresh();
$four = new Menu();
$four->set('name', '4');
$root->getNode()->addChild($four);
$root->refresh();
$five = new Menu();
$five->set('name', '5');
$root->getNode()->addChild($five);
$root->refresh();
$six = new Menu();
$six->set('name', '6');
$root->getNode()->addChild($six);
output_message('initial root');
output_tree($root);
// create a new root with a tree
$root2 = new Menu();
$root2->set('name', 'new root');
$manager->getTable('Menu')->getTree()->createRoot($root2);
// build tree
$two2 = new Menu();
$two2->set('name', '2');
$root2->getNode()->addChild($two2);
$one2 = new Menu();
$one2->set('name', '1');
$one2->getNode()->insertAsPrevSiblingOf($two2);
// refresh as node's lft and rgt values have changed, zYne, can we automate this?
$two2->refresh();
$three2 = new Menu();
$three2->set('name', '3');
$three2->getNode()->insertAsNextSiblingOf($two2);
$two2->refresh();
$one_one2 = new Menu();
$one_one2->set('name', '1.1');
$one_one2->getNode()->insertAsFirstChildOf($one2);
$one2->refresh();
$one_two2 = new Menu();
$one_two2->set('name', '1.2');
$one_two2->getNode()->insertAsLastChildOf($one2);
$one_two2->refresh();
$one_two_one2 = new Menu();
$one_two_one2->set('name', '1.2.1');
$one_two2->getNode()->addChild($one_two_one2);
$root2->refresh();
$four2 = new Menu();
$four2->set('name', '4');
$root2->getNode()->addChild($four2);
output_message('new root');
output_tree($root2);
$one_one->refresh();
$six->set('name', '1.0 (was 6)');
$six->getNode()->moveAsPrevSiblingOf($one_one);
$one_two->refresh();
$five->refresh();
$five->set('name', '1.3 (was 5)');
$five->getNode()->moveAsNextSiblingOf($one_two);
$one_one->refresh();
$four->refresh();
$four->set('name', '1.1.1 (was 4)');
$four->getNode()->moveAsFirstChildOf($one_one);
$root->refresh();
$one_two_one->refresh();
$one_two_one->set('name', 'last (was 1.2.1)');
$one_two_one->getNode()->moveAsLastChildOf($root);
output_message('transformed initial root');
output_tree($root);
function output_tree($root)
{
// display tree
// first we must refresh the node as the tree has been transformed
$root->refresh();
// next we must get the iterator to traverse the tree from the root node
$traverse = $root->getNode()->traverse();
output_node($root);
// now we traverse the tree and output the menu items
while($item = $traverse->next())
{
output_node($item);
}
unset($traverse);
}
function output_node($record)
{
echo str_repeat('-', $record->getNode()->getLevel()) . $record->get('name') . ' (has children:'.$record->getNode()->hasChildren().') '. ' (is leaf:'.$record->getNode()->isLeaf().') '.'<br/>';
}
function output_message($msg)
{
echo "<br /><strong><em>$msg</em></strong>".'<br />';
}

View File

@ -1,158 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node implements IteratorAggregate
{
/**
* @param object $record reference to associated Doctrine_Record instance
*/
protected $record;
/**
* @param array $options
*/
protected $options;
/**
* @param string $iteratorType (Pre | Post | Level)
*/
protected $iteratorType;
/**
* @param array $iteratorOptions
*/
protected $iteratorOptions;
/**
* contructor, creates node with reference to record and any options
*
* @param object $record instance of Doctrine_Record
* @param array $options options
*/
public function __construct(Doctrine_Record $record, $options)
{
$this->record = $record;
$this->options = $options;
}
/**
* factory method to return node instance based upon chosen implementation
*
* @param object $record instance of Doctrine_Record
* @param string $impName implementation (NestedSet, AdjacencyList, MaterializedPath)
* @param array $options options
* @return object $options instance of Doctrine_Node
*/
public static function factory(Doctrine_Record $record, $implName, $options = array())
{
$class = 'Doctrine_Node_' . $implName;
if ( ! class_exists($class)) {
throw new Doctrine_Node_Exception("The class $class must exist and extend Doctrine_Node");
}
return new $class($record, $options);
}
/**
* setter for record attribute
*
* @param object $record instance of Doctrine_Record
*/
public function setRecord(Doctrine_Record $record)
{
$this->record = $record;
}
/**
* getter for record attribute
*
* @return object instance of Doctrine_Record
*/
public function getRecord()
{
return $this->record;
}
/**
* convenience function for getIterator
*
* @param string $type type of iterator (Pre | Post | Level)
* @param array $options options
*/
public function traverse($type = 'Pre', $options = array())
{
return $this->getIterator($type, $options);
}
/**
* get iterator
*
* @param string $type type of iterator (Pre | Post | Level)
* @param array $options options
*/
public function getIterator($type = null, $options = null)
{
if ($type === null) {
$type = (isset($this->iteratorType) ? $this->iteratorType : 'Pre');
}
if ($options === null) {
$options = (isset($this->iteratorOptions) ? $this->iteratorOptions : array());
}
$implName = $this->record->getTable()->getTreeImplName();
$iteratorClass = 'Doctrine_Node_' . $implName . '_' . ucfirst(strtolower($type)) . 'OrderIterator';
return new $iteratorClass($this->record, $options);
}
/**
* sets node's iterator type
*
* @param int
*/
public function setIteratorType($type)
{
$this->iteratorType = $type;
}
/**
* sets node's iterator options
*
* @param int
*/
public function setIteratorOptions($options)
{
$this->iteratorOptions = $options;
}
}

View File

@ -1,35 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_AdjacencyList
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_AdjacencyList extends Doctrine_Node implements Doctrine_Node_Interface
{}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_AdjacencyList_LevelOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_AdjacencyList_LevelOrderIterator implements Iterator
{}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_AdjacencyList_PostOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_AdjacencyList_PostOrderIterator implements Iterator
{}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_AdjacencyList_PreOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_AdjacencyList_PreOrderIterator implements Iterator
{}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_Exception
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_Exception extends Doctrine_Exception
{}

View File

@ -1,268 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_Interface
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
interface Doctrine_Node_Interface {
/**
* test if node has previous sibling
*
* @return bool
*/
public function hasPrevSibling();
/**
* test if node has next sibling
*
* @return bool
*/
public function hasNextSibling();
/**
* test if node has children
*
* @return bool
*/
public function hasChildren();
/**
* test if node has parent
*
* @return bool
*/
public function hasParent();
/**
* gets record of prev sibling or empty record
*
* @return object Doctrine_Record
*/
public function getPrevSibling();
/**
* gets record of next sibling or empty record
*
* @return object Doctrine_Record
*/
public function getNextSibling();
/**
* gets siblings for node
*
* @return array array of sibling Doctrine_Record objects
*/
public function getSiblings($includeNode = false);
/**
* gets record of first child or empty record
*
* @return object Doctrine_Record
*/
public function getFirstChild();
/**
* gets record of last child or empty record
*
* @return object Doctrine_Record
*/
public function getLastChild();
/**
* gets children for node (direct descendants only)
*
* @return array array of sibling Doctrine_Record objects
*/
public function getChildren();
/**
* gets descendants for node (direct descendants only)
*
* @return iterator iterator to traverse descendants from node
*/
public function getDescendants();
/**
* gets record of parent or empty record
*
* @return object Doctrine_Record
*/
public function getParent();
/**
* gets ancestors for node
*
* @return object Doctrine_Collection
*/
public function getAncestors();
/**
* gets path to node from root, uses record::toString() method to get node names
*
* @param string $seperator path seperator
* @param bool $includeNode whether or not to include node at end of path
* @return string string representation of path
*/
public function getPath($seperator = ' > ', $includeNode = false);
/**
* gets level (depth) of node in the tree
*
* @return int
*/
public function getLevel();
/**
* gets number of children (direct descendants)
*
* @return int
*/
public function getNumberChildren();
/**
* gets number of descendants (children and their children)
*
* @return int
*/
public function getNumberDescendants();
/**
* inserts node as parent of dest record
*
* @return bool
*/
public function insertAsParentOf(Doctrine_Record $dest);
/**
* inserts node as previous sibling of dest record
*
* @return bool
*/
public function insertAsPrevSiblingOf(Doctrine_Record $dest);
/**
* inserts node as next sibling of dest record
*
* @return bool
*/
public function insertAsNextSiblingOf(Doctrine_Record $dest);
/**
* inserts node as first child of dest record
*
* @return bool
*/
public function insertAsFirstChildOf(Doctrine_Record $dest);
/**
* inserts node as first child of dest record
*
* @return bool
*/
public function insertAsLastChildOf(Doctrine_Record $dest);
/**
* moves node as prev sibling of dest record
*
*/
public function moveAsPrevSiblingOf(Doctrine_Record $dest);
/**
* moves node as next sibling of dest record
*
*/
public function moveAsNextSiblingOf(Doctrine_Record $dest);
/**
* moves node as first child of dest record
*
*/
public function moveAsFirstChildOf(Doctrine_Record $dest);
/**
* moves node as last child of dest record
*
*/
public function moveAsLastChildOf(Doctrine_Record $dest);
/**
* adds node as last child of record
*
*/
public function addChild(Doctrine_Record $record);
/**
* determines if node is leaf
*
* @return bool
*/
public function isLeaf();
/**
* determines if node is root
*
* @return bool
*/
public function isRoot();
/**
* determines if node is equal to subject node
*
* @return bool
*/
public function isEqualTo(Doctrine_Record $subj);
/**
* determines if node is child of subject node
*
* @return bool
*/
public function isDescendantOf(Doctrine_Record $subj);
/**
* determines if node is child of or sibling to subject node
*
* @return bool
*/
public function isDescendantOfOrEqualTo(Doctrine_Record $subj);
/**
* determines if node is valid
*
* @return bool
*/
public function isValidNode();
/**
* deletes node and it's descendants
*
*/
public function delete();
}

View File

@ -1,35 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_MaterializedPath
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_MaterializedPath extends Doctrine_Node implements Doctrine_Node_Interface
{}

View File

@ -1,68 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_MaterializedPath_LevelOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_MaterializedPath_LevelOrderIterator implements Iterator
{
private $topNode = null;
private $curNode = null;
public function __construct($node, $opts)
{
throw new Doctrine_Exception('Not yet implemented');
}
public function rewind()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function valid()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function current()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function key()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function next()
{
throw new Doctrine_Exception('Not yet implemented');
}
}

View File

@ -1,68 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_MaterializedPath_PostOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_MaterializedPath_PostOrderIterator implements Iterator
{
private $topNode = null;
private $curNode = null;
public function __construct($node, $opts)
{
throw new Doctrine_Exception('Not yet implemented');
}
public function rewind()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function valid()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function current()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function key()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function next()
{
throw new Doctrine_Exception('Not yet implemented');
}
}

View File

@ -1,68 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_MaterializedPath_PreOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_MaterializedPath_PreOrderIterator implements Iterator
{
private $topNode = null;
private $curNode = null;
public function __construct($node, $opts)
{
throw new Doctrine_Exception('Not yet implemented');
}
public function rewind()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function valid()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function current()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function key()
{
throw new Doctrine_Exception('Not yet implemented');
}
public function next()
{
throw new Doctrine_Exception('Not yet implemented');
}
}

View File

@ -1,998 +0,0 @@
<?php
/*
* $Id: NestedSet.php 1382 2007-05-17 19:52:50Z Jonathan.Wage $
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_NestedSet
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision: 1382 $
* @author Joe Simms <joe.simms@websites4.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Interface
{
/**
* test if node has previous sibling
*
* @return bool
*/
public function hasPrevSibling()
{
return $this->isValidNode($this->getPrevSibling());
}
/**
* test if node has next sibling
*
* @return bool
*/
public function hasNextSibling()
{
return $this->isValidNode($this->getNextSibling());
}
/**
* test if node has children
*
* @return bool
*/
public function hasChildren()
{
return (($this->getRightValue() - $this->getLeftValue() ) >1 );
}
/**
* test if node has parent
*
* @return bool
*/
public function hasParent()
{
return !$this->isRoot();
}
/**
* gets record of prev sibling or empty record
*
* @return object Doctrine_Record
*/
public function getPrevSibling()
{
$q = $this->_tree->getBaseQuery();
$q = $q->where('base.rgt = ?', $this->getLeftValue() - 1);
$q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
$result = $q->execute();
if (count($result) <= 0) {
return false;
}
if ($result instanceof Doctrine_Collection) {
$sibling = $result->getFirst();
} else if (is_array($result)) {
$sibling = array_shift($result);
}
return $sibling;
}
/**
* gets record of next sibling or empty record
*
* @return object Doctrine_Record
*/
public function getNextSibling()
{
$q = $this->_tree->getBaseQuery();
$q = $q->where('base.lft = ?', $this->getRightValue() + 1);
$q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
$result = $q->execute();
if (count($result) <= 0) {
return false;
}
if ($result instanceof Doctrine_Collection) {
$sibling = $result->getFirst();
} else if (is_array($result)) {
$sibling = array_shift($result);
}
return $sibling;
}
/**
* gets siblings for node
*
* @return array array of sibling Doctrine_Record objects
*/
public function getSiblings($includeNode = false)
{
$parent = $this->getParent();
$siblings = array();
if ($parent->exists()) {
foreach ($parent->getNode()->getChildren() as $child) {
if ($this->isEqualTo($child) && !$includeNode) {
continue;
}
$siblings[] = $child;
}
}
return $siblings;
}
/**
* gets record of first child or empty record
*
* @return object Doctrine_Record
*/
public function getFirstChild()
{
$q = $this->_tree->getBaseQuery();
$q->where('base.lft = ?', $this->getLeftValue() + 1);
$this->_tree->returnQueryWithRootId($q, $this->getRootValue());
$result = $q->execute();
if (count($result) <= 0) {
return false;
}
if ($result instanceof Doctrine_Collection) {
$child = $result->getFirst();
} else if (is_array($result)) {
$child = array_shift($result);
}
return $child;
}
/**
* gets record of last child or empty record
*
* @return object Doctrine_Record
*/
public function getLastChild()
{
$q = $this->_tree->getBaseQuery();
$q->where('base.rgt = ?', $this->getRightValue() - 1);
$this->_tree->returnQueryWithRootId($q, $this->getRootValue());
$result = $q->execute();
if (count($result) <= 0) {
return false;
}
if ($result instanceof Doctrine_Collection) {
$child = $result->getFirst();
} else if (is_array($result)) {
$child = array_shift($result);
}
return $child;
}
/**
* gets children for node (direct descendants only)
*
* @return mixed The children of the node or FALSE if the node has no children.
*/
public function getChildren()
{
return $this->getDescendants(1);
}
/**
* gets descendants for node (direct descendants only)
*
* @return mixed The descendants of the node or FALSE if the node has no descendants.
* @todo Currently all descendants are fetched, no matter the depth. Maybe there is a better
* solution with less overhead.
*/
public function getDescendants($depth = null, $includeNode = false)
{
$q = $this->_tree->getBaseQuery();
$params = array($this->record->get('lft'), $this->record->get('rgt'));
if ($includeNode) {
$q->where("base.lft >= ? AND base.rgt <= ?", $params)->orderBy("base.lft asc");
} else {
$q->where("base.lft > ? AND base.rgt < ?", $params)->orderBy("base.lft asc");
}
if ($depth !== null) {
$q->addWhere("base.level <= ?", $this->record['level'] + $depth);
}
$q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
$result = $q->execute();
if (count($result) <= 0) {
return false;
}
return $result;
}
/**
* gets record of parent or empty record
*
* @return object Doctrine_Record
*/
public function getParent()
{
$q = $this->_tree->getBaseQuery();
$q->where("base.lft < ? AND base.rgt > ?", array($this->getLeftValue(), $this->getRightValue()))
->orderBy("base.rgt asc");
$q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
$result = $q->execute();
if (count($result) <= 0) {
return false;
}
if ($result instanceof Doctrine_Collection) {
$parent = $result->getFirst();
} else if (is_array($result)) {
$parent = array_shift($result);
}
return $parent;
}
/**
* gets ancestors for node
*
* @param integer $deth The depth 'upstairs'.
* @return mixed The ancestors of the node or FALSE if the node has no ancestors (this
* basically means it's a root node).
*/
public function getAncestors($depth = null)
{
$q = $this->_tree->getBaseQuery();
$q->where("base.lft < ? AND base.rgt > ?", array($this->getLeftValue(), $this->getRightValue()))
->orderBy("base.lft asc");
if ($depth !== null) {
$q->addWhere("base.level >= ?", $this->record['level'] - $depth);
}
$q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
$ancestors = $q->execute();
if (count($ancestors) <= 0) {
return false;
}
return $ancestors;
}
/**
* gets path to node from root, uses record::toString() method to get node names
*
* @param string $seperator path seperator
* @param bool $includeNode whether or not to include node at end of path
* @return string string representation of path
*/
public function getPath($seperator = ' > ', $includeRecord = false)
{
$path = array();
$ancestors = $this->getAncestors();
foreach ($ancestors as $ancestor) {
$path[] = $ancestor->__toString();
}
if ($includeRecord) {
$path[] = $this->getRecord()->__toString();
}
return implode($seperator, $path);
}
/**
* gets number of children (direct descendants)
*
* @return int
*/
public function getNumberChildren()
{
$count = 0;
$children = $this->getChildren();
while ($children->next()) {
$count++;
}
return $count;
}
/**
* gets number of descendants (children and their children)
*
* @return int
*/
public function getNumberDescendants()
{
return ($this->getRightValue() - $this->getLeftValue() - 1) / 2;
}
/**
* inserts node as parent of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public function insertAsParentOf(Doctrine_Record $dest)
{
// cannot insert a node that has already has a place within the tree
if ($this->isValidNode()) {
return false;
}
// cannot insert as parent of root
if ($dest->getNode()->isRoot()) {
return false;
}
$newRoot = $dest->getNode()->getRootValue();
$this->shiftRLValues($dest->getNode()->getLeftValue(), 1, $newRoot);
$this->shiftRLValues($dest->getNode()->getRightValue() + 2, 1, $newRoot);
$newLeft = $dest->getNode()->getLeftValue();
$newRight = $dest->getNode()->getRightValue() + 2;
$this->record['level'] = $dest['level'] - 1;
$this->insertNode($newLeft, $newRight, $newRoot);
return true;
}
/**
* inserts node as previous sibling of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public function insertAsPrevSiblingOf(Doctrine_Record $dest)
{
// cannot insert a node that has already has a place within the tree
if ($this->isValidNode())
return false;
$newLeft = $dest->getNode()->getLeftValue();
$newRight = $dest->getNode()->getLeftValue() + 1;
$newRoot = $dest->getNode()->getRootValue();
$this->shiftRLValues($newLeft, 2, $newRoot);
$this->record['level'] = $dest['level'];
$this->insertNode($newLeft, $newRight, $newRoot);
// update destination left/right values to prevent a refresh
// $dest->getNode()->setLeftValue($dest->getNode()->getLeftValue() + 2);
// $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
return true;
}
/**
* inserts node as next sibling of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public function insertAsNextSiblingOf(Doctrine_Record $dest)
{
// cannot insert a node that has already has a place within the tree
if ($this->isValidNode())
return false;
$newLeft = $dest->getNode()->getRightValue() + 1;
$newRight = $dest->getNode()->getRightValue() + 2;
$newRoot = $dest->getNode()->getRootValue();
$this->shiftRLValues($newLeft, 2, $newRoot);
$this->record['level'] = $dest['level'];
$this->insertNode($newLeft, $newRight, $newRoot);
// update destination left/right values to prevent a refresh
// no need, node not affected
return true;
}
/**
* inserts node as first child of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public function insertAsFirstChildOf(Doctrine_Record $dest)
{
// cannot insert a node that has already has a place within the tree
if ($this->isValidNode())
return false;
$newLeft = $dest->getNode()->getLeftValue() + 1;
$newRight = $dest->getNode()->getLeftValue() + 2;
$newRoot = $dest->getNode()->getRootValue();
$this->shiftRLValues($newLeft, 2, $newRoot);
$this->record['level'] = $dest['level'] + 1;
$this->insertNode($newLeft, $newRight, $newRoot);
// update destination left/right values to prevent a refresh
// $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
return true;
}
/**
* inserts node as last child of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public function insertAsLastChildOf(Doctrine_Record $dest)
{
// cannot insert a node that has already has a place within the tree
if ($this->isValidNode())
return false;
$newLeft = $dest->getNode()->getRightValue();
$newRight = $dest->getNode()->getRightValue() + 1;
$newRoot = $dest->getNode()->getRootValue();
$this->shiftRLValues($newLeft, 2, $newRoot);
$this->record['level'] = $dest['level'] + 1;
$this->insertNode($newLeft, $newRight, $newRoot);
// update destination left/right values to prevent a refresh
// $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
return true;
}
/**
* Accomplishes moving of nodes between different trees.
* Used by the move* methods if the root value of the two nodes are different.
*
* @param Doctrine_Record $dest
* @param unknown_type $newLeftValue
* @param unknown_type $moveType
* @todo Better exception handling/wrapping
*/
private function _moveBetweenTrees(Doctrine_Record $dest, $newLeftValue, $moveType)
{
$conn = $this->record->getTable()->getConnection();
try {
$conn->beginTransaction();
// Move between trees: Detach from old tree & insert into new tree
$newRoot = $dest->getNode()->getRootValue();
$oldRoot = $this->getRootValue();
$oldLft = $this->getLeftValue();
$oldRgt = $this->getRightValue();
$oldLevel = $this->record['level'];
// Prepare target tree for insertion, make room
$this->shiftRlValues($newLeftValue, $oldRgt - $oldLft - 1, $newRoot);
// Set new root id for this node
$this->setRootValue($newRoot);
$this->record->save();
// Close gap in old tree
$first = $oldRgt + 1;
$delta = $oldLft - $oldRgt - 1;
$this->shiftRLValues($first, $delta, $oldRoot);
// Insert this node as a new node
$this->setRightValue(0);
$this->setLeftValue(0);
switch ($moveType) {
case 'moveAsPrevSiblingOf':
$this->insertAsPrevSiblingOf($dest);
break;
case 'moveAsFirstChildOf':
$this->insertAsFirstChildOf($dest);
break;
case 'moveAsNextSiblingOf':
$this->insertAsNextSiblingOf($dest);
break;
case 'moveAsLastChildOf':
$this->insertAsLastChildOf($dest);
break;
default:
throw new Exception("Unknown move operation: $moveType.");
}
$diff = $oldRgt - $oldLft;
$this->setRightValue($this->getLeftValue() + ($oldRgt - $oldLft));
$this->record->save();
$newLevel = $this->record['level'];
$levelDiff = $newLevel - $oldLevel;
// Relocate descendants of the node
$diff = $this->getLeftValue() - $oldLft;
$componentName = $this->record->getTable()->getComponentName();
$rootColName = $this->record->getTable()->getTree()->getAttribute('rootColumnName');
// Update lft/rgt/root/level for all descendants
$q = new Doctrine_Query($conn);
$q = $q->update($componentName)
->set($componentName . '.lft', 'lft + ' . $diff)
->set($componentName . '.rgt', 'rgt + ' . $diff)
->set($componentName . '.level', 'level + ' . $levelDiff)
->set($componentName . '.' . $rootColName, $newRoot)
->where($componentName . '.lft > ? AND ' . $componentName . '.rgt < ?',
array($oldLft, $oldRgt));
$q = $this->_tree->returnQueryWithRootId($q, $oldRoot);
$q->execute();
$conn->commit();
} catch (Exception $e) {
$conn->rollback();
throw $e;
}
}
/**
* moves node as prev sibling of dest record
*
*/
public function moveAsPrevSiblingOf(Doctrine_Record $dest)
{
if ($dest->getNode()->getRootValue() != $this->getRootValue()) {
// Move between trees
$this->_moveBetweenTrees($dest, $dest->getNode()->getLeftValue(), __FUNCTION__);
} else {
// Move within the tree
$oldLevel = $this->record['level'];
$this->record['level'] = $dest['level'];
$this->updateNode($dest->getNode()->getLeftValue(), $this->record['level'] - $oldLevel);
}
}
/**
* moves node as next sibling of dest record
*
*/
public function moveAsNextSiblingOf(Doctrine_Record $dest)
{
if ($dest->getNode()->getRootValue() != $this->getRootValue()) {
// Move between trees
$this->_moveBetweenTrees($dest, $dest->getNode()->getRightValue() + 1, __FUNCTION__);
} else {
// Move within tree
$oldLevel = $this->record['level'];
$this->record['level'] = $dest['level'];
$this->updateNode($dest->getNode()->getRightValue() + 1, $this->record['level'] - $oldLevel);
}
}
/**
* moves node as first child of dest record
*
*/
public function moveAsFirstChildOf(Doctrine_Record $dest)
{
if ($dest->getNode()->getRootValue() != $this->getRootValue()) {
// Move between trees
$this->_moveBetweenTrees($dest, $dest->getNode()->getLeftValue() + 1, __FUNCTION__);
} else {
// Move within tree
$oldLevel = $this->record['level'];
$this->record['level'] = $dest['level'] + 1;
$this->updateNode($dest->getNode()->getLeftValue() + 1, $this->record['level'] - $oldLevel);
}
}
/**
* moves node as last child of dest record
*
*/
public function moveAsLastChildOf(Doctrine_Record $dest)
{
if ($dest->getNode()->getRootValue() != $this->getRootValue()) {
// Move between trees
$this->_moveBetweenTrees($dest, $dest->getNode()->getRightValue(), __FUNCTION__);
} else {
// Move within tree
$oldLevel = $this->record['level'];
$this->record['level'] = $dest['level'] + 1;
$this->updateNode($dest->getNode()->getRightValue(), $this->record['level'] - $oldLevel);
}
}
/**
* Makes this node a root node. Only used in multiple-root trees.
*
* @todo Exception handling/wrapping
*/
public function makeRoot($newRootId)
{
// TODO: throw exception instead?
if ($this->getLeftValue() == 1 || !$this->record->getTable()->getTree()->getAttribute('hasManyRoots')) {
return false;
}
$oldRgt = $this->getRightValue();
$oldLft = $this->getLeftValue();
$oldRoot = $this->getRootValue();
$oldLevel = $this->record['level'];
try {
$conn = $this->record->getTable()->getConnection();
$conn->beginTransaction();
// Detach from old tree (close gap in old tree)
$first = $oldRgt + 1;
$delta = $oldLft - $oldRgt - 1;
$this->shiftRLValues($first, $delta, $this->getRootValue());
// Set new lft/rgt/root/level values for root node
$this->setLeftValue(1);
$this->setRightValue($oldRgt - $oldLft + 1);
$this->setRootValue($newRootId);
$this->record['level'] = 0;
// Update descendants lft/rgt/root/level values
$diff = 1 - $oldLft;
$newRoot = $newRootId;
$componentName = $this->record->getTable()->getComponentName();
$rootColName = $this->record->getTable()->getTree()->getAttribute('rootColumnName');
$q = new Doctrine_Query($conn);
$q = $q->update($componentName)
->set($componentName . '.lft', 'lft + ' . $diff)
->set($componentName . '.rgt', 'rgt + ' . $diff)
->set($componentName . '.level', 'level - ' . $oldLevel)
->set($componentName . '.' . $rootColName, $newRoot)
->where($componentName . '.lft > ? AND ' . $componentName . '.rgt < ?',
array($oldLft, $oldRgt));
$q = $this->_tree->returnQueryWithRootId($q, $oldRoot);
$q->execute();
$conn->commit();
} catch (Exception $e) {
$conn->rollback();
throw $e;
}
}
/**
* adds node as last child of record
*
*/
public function addChild(Doctrine_Record $record)
{
$record->getNode()->insertAsLastChildOf($this->getRecord());
}
/**
* determines if node is leaf
*
* @return bool
*/
public function isLeaf()
{
return (($this->getRightValue() - $this->getLeftValue()) == 1);
}
/**
* determines if node is root
*
* @return bool
*/
public function isRoot()
{
return ($this->getLeftValue() == 1);
}
/**
* determines if node is equal to subject node
*
* @return bool
*/
public function isEqualTo(Doctrine_Record $subj)
{
return (($this->getLeftValue() == $subj->getNode()->getLeftValue()) &&
($this->getRightValue() == $subj->getNode()->getRightValue()) &&
($this->getRootValue() == $subj->getNode()->getRootValue())
);
}
/**
* determines if node is child of subject node
*
* @return bool
*/
public function isDescendantOf(Doctrine_Record $subj)
{
return (($this->getLeftValue() > $subj->getNode()->getLeftValue()) &&
($this->getRightValue() < $subj->getNode()->getRightValue()) &&
($this->getRootValue() == $subj->getNode()->getRootValue()));
}
/**
* determines if node is child of or sibling to subject node
*
* @return bool
*/
public function isDescendantOfOrEqualTo(Doctrine_Record $subj)
{
return (($this->getLeftValue() >= $subj->getNode()->getLeftValue()) &&
($this->getRightValue() <= $subj->getNode()->getRightValue()) &&
($this->getRootValue() == $subj->getNode()->getRootValue()));
}
/**
* determines if node is valid
*
* @return bool
*/
public function isValidNode(Doctrine_Record $record = null)
{
if ($record === null) {
return ($this->getRightValue() > $this->getLeftValue());
} else {
return ($record->getNode()->getRightValue() > $record->getNode()->getLeftValue());
}
}
/**
* deletes node and it's descendants
* @todo Delete more efficiently. Wrap in transaction if needed.
*/
public function delete()
{
// TODO: add the setting whether or not to delete descendants or relocate children
$oldRoot = $this->getRootValue();
$q = $this->_tree->getBaseQuery();
$componentName = $this->record->getTable()->getComponentName();
$q = $q->where('base.lft >= ? AND base.rgt <= ?', array($this->getLeftValue(), $this->getRightValue()));
$q = $this->record->getTable()->getTree()->returnQueryWithRootId($q, $oldRoot);
$coll = $q->execute();
$coll->delete();
$first = $this->getRightValue() + 1;
$delta = $this->getLeftValue() - $this->getRightValue() - 1;
$this->shiftRLValues($first, $delta, $oldRoot);
return true;
}
/**
* sets node's left and right values and save's it
*
* @param int $destLeft node left value
* @param int $destRight node right value
*/
private function insertNode($destLeft = 0, $destRight = 0, $destRoot = 1)
{
$this->setLeftValue($destLeft);
$this->setRightValue($destRight);
$this->setRootValue($destRoot);
$this->record->save();
}
/**
* move node's and its children to location $destLeft and updates rest of tree
*
* @param int $destLeft destination left value
* @todo Wrap in transaction
*/
private function updateNode($destLeft, $levelDiff)
{
$componentName = $this->record->getTable()->getComponentName();
$left = $this->getLeftValue();
$right = $this->getRightValue();
$rootId = $this->getRootValue();
$treeSize = $right - $left + 1;
// Make room in the new tree
$this->shiftRLValues($destLeft, $treeSize, $rootId);
if ($left >= $destLeft){ // src was shifted too?
$left += $treeSize;
$right += $treeSize;
}
// update level for descendants
$q = new Doctrine_Query();
$q = $q->update($componentName)
->set($componentName . '.level', 'level + ' . $levelDiff)
->where($componentName . '.lft > ? AND ' . $componentName . '.rgt < ?',
array($left, $right));
$q = $this->_tree->returnQueryWithRootId($q, $rootId);
$q->execute();
// now there's enough room next to target to move the subtree
$this->shiftRLRange($left, $right, $destLeft - $left, $rootId);
// correct values after source (close gap in old tree)
$this->shiftRLValues($right + 1, -$treeSize, $rootId);
$this->record->save();
$this->record->refresh();
}
/**
* adds '$delta' to all Left and Right values that are >= '$first'. '$delta' can also be negative.
*
* @param int $first First node to be shifted
* @param int $delta Value to be shifted by, can be negative
*/
private function shiftRlValues($first, $delta, $rootId = 1)
{
$qLeft = new Doctrine_Query();
$qRight = new Doctrine_Query();
// shift left columns
$componentName = $this->record->getTable()->getComponentName();
$qLeft = $qLeft->update($componentName)
->set($componentName . '.lft', 'lft + ' . $delta)
->where($componentName . '.lft >= ?', $first);
$qLeft = $this->record->getTable()->getTree()->returnQueryWithRootId($qLeft, $rootId);
$resultLeft = $qLeft->execute();
// shift right columns
$resultRight = $qRight->update($componentName)
->set($componentName . '.rgt', 'rgt + ' . $delta)
->where($componentName . '.rgt >= ?', $first);
$qRight = $this->record->getTable()->getTree()->returnQueryWithRootId($qRight, $rootId);
$resultRight = $qRight->execute();
}
/**
* adds '$delta' to all Left and Right values that are >= '$first' and <= '$last'.
* '$delta' can also be negative.
*
* @param int $first First node to be shifted (L value)
* @param int $last Last node to be shifted (L value)
* @param int $delta Value to be shifted by, can be negative
*/
private function shiftRlRange($first, $last, $delta, $rootId = 1)
{
$qLeft = new Doctrine_Query();
$qRight = new Doctrine_Query();
// shift left column values
$componentName = $this->record->getTable()->getComponentName();
$qLeft = $qLeft->update($componentName)
->set($componentName . '.lft', 'lft + ' . $delta)
->where($componentName . '.lft >= ? AND ' . $componentName . '.lft <= ?', array($first, $last));
$qLeft = $this->record->getTable()->getTree()->returnQueryWithRootId($qLeft, $rootId);
$resultLeft = $qLeft->execute();
// shift right column values
$qRight = $qRight->update($componentName)
->set($componentName . '.rgt', 'rgt + ' . $delta)
->where($componentName . '.rgt >= ? AND ' . $componentName . '.rgt <= ?', array($first, $last));
$qRight = $this->record->getTable()->getTree()->returnQueryWithRootId($qRight, $rootId);
$resultRight = $qRight->execute();
}
/**
* gets record's left value
*
* @return int
*/
public function getLeftValue()
{
return $this->record->get('lft');
}
/**
* sets record's left value
*
* @param int
*/
public function setLeftValue($lft)
{
$this->record->set('lft', $lft);
}
/**
* gets record's right value
*
* @return int
*/
public function getRightValue()
{
return $this->record->get('rgt');
}
/**
* sets record's right value
*
* @param int
*/
public function setRightValue($rgt)
{
$this->record->set('rgt', $rgt);
}
/**
* gets level (depth) of node in the tree
*
* @return int
*/
public function getLevel()
{
if ( ! isset($this->level)) {
$componentName = $this->record->getTable()->getComponentName();
$q = $this->_tree->getBaseQuery();
$q = $q->where('base.lft < ? AND base.rgt > ?', array($this->getLeftValue(), $this->getRightValue()));
$q = $this->_tree->returnQueryWithRootId($q, $this->getRootValue());
$coll = $q->execute();
$this->level = count($coll) ? count($coll) : 0;
}
return $this->level;
}
/**
* sets node's level
*
* @param int
*/
public function setLevel($level)
{
$this->level = $level;
}
/**
* get records root id value
*
*/
public function getRootValue()
{
if ($this->_tree->getAttribute('hasManyRoots')) {
return $this->record->get($this->_tree->getAttribute('rootColumnName'));
}
return 1;
}
/**
* sets records root id value
*
* @param int
*/
public function setRootValue($value)
{
if ($this->_tree->getAttribute('hasManyRoots')) {
$this->record->set($this->_tree->getAttribute('rootColumnName'), $value);
}
}
}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_NestedSet_LevelOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_NestedSet_LevelOrderIterator implements Iterator
{}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_NestedSet_PostOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_NestedSet_PostOrderIterator implements Iterator
{}

View File

@ -1,184 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Node_NestedSet_PreOrderIterator
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Node_NestedSet_PreOrderIterator implements Iterator
{
/**
* @var Doctrine_Collection $collection
*/
protected $collection;
/**
* @var array $keys
*/
protected $keys;
/**
* @var mixed $key
*/
protected $key;
/**
* @var integer $index
*/
protected $index;
/**
* @var integer $index
*/
protected $prevIndex;
/**
* @var integer $index
*/
protected $traverseLevel;
/**
* @var integer $count
*/
protected $count;
public function __construct($record, $opts)
{
$componentName = $record->getTable()->getComponentName();
$q = $record->getTable()->createQuery();
$params = array($record->get('lft'), $record->get('rgt'));
if (isset($opts['include_record']) && $opts['include_record']) {
$query = $q->where("$componentName.lft >= ? AND $componentName.rgt <= ?", $params)->orderBy('lft asc');
} else {
$query = $q->where("$componentName.lft > ? AND $componentName.rgt < ?", $params)->orderBy('lft asc');
}
$query = $record->getTable()->getTree()->returnQueryWithRootId($query, $record->getNode()->getRootValue());
$this->maxLevel = isset($opts['depth']) ? ($opts['depth'] + $record->getNode()->getLevel()) : 0;
$this->options = $opts;
$this->collection = isset($opts['collection']) ? $opts['collection'] : $query->execute();
$this->keys = $this->collection->getKeys();
$this->count = $this->collection->count();
$this->index = -1;
$this->level = $record->getNode()->getLevel();
$this->prevLeft = $record->getNode()->getLeftValue();
echo $this->maxDepth;
// clear the table identity cache
$record->getTable()->clear();
}
/**
* rewinds the iterator
*
* @return void
*/
public function rewind()
{
$this->index = -1;
$this->key = null;
}
/**
* returns the current key
*
* @return integer
*/
public function key()
{
return $this->key;
}
/**
* returns the current record
*
* @return Doctrine_Record
*/
public function current()
{
$record = $this->collection->get($this->key);
$record->getNode()->setLevel($this->level);
return $record;
}
/**
* advances the internal pointer
*
* @return void
*/
public function next()
{
while ($current = $this->advanceIndex()) {
if ($this->maxLevel && ($this->level > $this->maxLevel)) {
continue;
}
return $current;
}
return false;
}
/**
* @return boolean whether or not the iteration will continue
*/
public function valid()
{
return ($this->index < $this->count);
}
public function count()
{
return $this->count;
}
private function updateLevel()
{
if ( ! (isset($this->options['include_record']) && $this->options['include_record'] && $this->index == 0)) {
$left = $this->collection->get($this->key)->getNode()->getLeftValue();
$this->level += $this->prevLeft - $left + 2;
$this->prevLeft = $left;
}
}
private function advanceIndex()
{
$this->index++;
$i = $this->index;
if (isset($this->keys[$i])) {
$this->key = $this->keys[$i];
$this->updateLevel();
return $this->current();
}
return false;
}
}

View File

@ -1,72 +0,0 @@
REMEMBER
--------
If performing batch tree manipulation tasks, then remember to refresh your records (see record::refresh()), as any transformations of the tree are likely to affect all instances of records that you have in your scope. (zYne, is there any way of automating this?)
If you are inserting or moving a node within the tree, you must use the appropriate function to place the node in it's destination. Note: you cannot save a new record without inserting it into the tree.
You can save an already existing node using record::save() without affecting the tree. Never set the tree specific record attributes manually.
If you wish to delete a record, you MUST delete the node and not the record, using $record->deleteNode() or $record->getNode()->delete(). Deleting a node, will delete all its descendants.
The difference between descendants and children is that descendants include children of children whereas children are direct descendants of their parent (real children not gran children and great gran children etc etc).
The most effective way to traverse a tree from the root node, is to use the tree::fetchTree() method:
$tree = $manager->getTable('Model')->getTree()->fetchTree();
It will by default include the root node in the tree and will return an iterator to traverse the tree.
To traverse a tree from a given node, it will normally cost 3 queries, one to fetch the starting node, one to fetch the branch from this node, and one to determine the level of the start node, the traversal algorithm with then determine the level of each subsequent node for you.
EXAMPLES
--------
See EXAMPLE.tree.php for very draft examples on how to use the tree interface within doctrine (note that the interface is independent of the implementation, so when other implementations are added, you should be able to switch implementation and your code still work). This should be run in a browser and relevant database settings altered as appropriate.
MORE INFO
---------
For more info on storing hierarchical data in databases, the various implementations and tree traversal see these articles:
http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
http://www.sitepoint.com/article/hierarchical-data-database
http://en.wikipedia.org/wiki/Tree_traversal
TO DO
-----
Discuss adding __call() to record to allow the node methods to be called directly from the record, although i know we are trying to avoid introspection.
Discuss adding a level column to the database to store levels (will reduce traversing nodes by one query, and allow us to implement the LevelOrder Traversal of the Tree with one query, but updating tree may be more costly).
Maybe add tree configuration to allow the above to be configurable as well as other options such as:
-on deletion of a node, move descendants, unassign descendants or delete descendants
-allowing the ability to save nodes that are not assigned in the tree (set lft=0, rgt=0, retrieve with tree::fetchUnassigned)
-auto refreshing objects left and right values used in tree transformations
Return getSiblings and getAncestors as Iterators
NOTES FOR ZYNE
--------------
IMHO, i think that the Ajacency list should be moved into a tree structure, the table definitions and setUp can be set in the class Doctrine_Tree_AdjacencyList, then they simply have to call actsAsTree('AdjacencyList') in their setTableDefinition, although to be honest, with nestedset implemented i cannot think why i would want to use adjacency list anyhow !
Doctrine_Query_Set, Doctrine_Query_Where
----------------------------------------
Not too sure if problem with my query syntax or with Doctrine, but i followed examples in docs
In set and where, if not Component supplied, then alias is empty, so resultant query would be something like:
UPDATE menu m SET .lft = lft + 2
WHERE lft >= ?
Notice the invalid resultant syntax .lft, as the alias was not set, so added check to determine if alias isset.
CHANGELOG
---------
Doctrine_Record
---------------
added methods:
getNode()
deleteNode()
amended:
actsAsTree()
Doctrine_Table
--------------
amended constructor to call tree::setUp()
added setTree(), to call tree::setTableDefinition() and setup Tree in Table

View File

@ -1,110 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Tree
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Tree
{
/**
* @param object $table reference to associated Doctrine_Table instance
*/
protected $table;
/**
* @param array $options
*/
protected $options = array();
/**
* constructor, creates tree with reference to table and any options
*
* @param object $table instance of Doctrine_Table
* @param array $options options
*/
public function __construct(Doctrine_Table $table, $options)
{
$this->table = $table;
$this->options = $options;
}
/**
* Used to define table attributes required for the given implementation
*
* @throws Doctrine_Tree_Exception if table attributes have not been defined
*/
public function setTableDefinition()
{
throw new Doctrine_Tree_Exception('Table attributes have not been defined for this Tree implementation.');
}
/**
* this method is used for setting up relations and attributes and should be used by specific implementations
*
*/
public function setUp()
{
}
/**
* factory method to return tree instance based upon chosen implementation
*
* @param object $table instance of Doctrine_Table
* @param string $impName implementation (NestedSet, AdjacencyList, MaterializedPath)
* @param array $options options
* @return object $options instance of Doctrine_Node
* @throws Doctrine_Exception if class does not extend Doctrine_Tree
*/
public static function factory(Doctrine_Table $table, $implName, $options = array())
{
$class = 'Doctrine_Tree_' . $implName;
if ( ! class_exists($class)) {
throw new Doctrine_Exception('The chosen class must extend Doctrine_Tree');
}
return new $class($table, $options);
}
/**
* gets tree attribute value
*
*/
public function getAttribute($name)
{
return isset($this->options[$name]) ? $this->options[$name] : null;
}
/**
* sets tree attribute value
*
* @param mixed
*/
public function setAttribute($name, $value)
{
$this->options[$name] = $value;
}
}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Tree_AdjacencyList
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Tree_AdjacencyList extends Doctrine_Tree implements Doctrine_Tree_Interface
{}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Tree_Exception
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
*/
class Doctrine_Tree_Exception extends Doctrine_Exception
{}

View File

@ -1,65 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Tree_Interface
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
interface Doctrine_Tree_Interface {
/**
* creates root node from given record or from a new record
*
* @param object $record instance of Doctrine_Record
*/
public function createRoot(Doctrine_Record $record = null);
/**
* returns root node
*
* @return object $record instance of Doctrine_Record
*/
public function findRoot($root_id = 1);
/**
* optimised method to returns iterator for traversal of the entire tree from root
*
* @param array $options options
* @return object $iterator instance of Doctrine_Node_<Implementation>_PreOrderIterator
*/
public function fetchTree($options = array());
/**
* optimised method that returns iterator for traversal of the tree from the given record primary key
*
* @param mixed $pk primary key as used by table::find() to locate node to traverse tree from
* @param array $options options
* @return iterator instance of Doctrine_Node_<Implementation>_PreOrderIterator
*/
public function fetchBranch($pk, $options = array());
}

View File

@ -1,34 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Tree_MaterializedPath
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Tree_MaterializedPath extends Doctrine_Tree implements Doctrine_Tree_Interface
{}

View File

@ -1,342 +0,0 @@
<?php
/*
* $Id: NestedSet.php 1600 2007-06-07 19:17:56Z romanb $
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Tree_NestedSet
*
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision: 1600 $
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Interface
{
private $_baseQuery;
/**
* constructor, creates tree with reference to table and sets default root options
*
* @param object $table instance of Doctrine_Table
* @param array $options options
*/
public function __construct(Doctrine_Table $table, $options)
{
// set default many root attributes
$options['hasManyRoots'] = isset($options['hasManyRoots']) ? $options['hasManyRoots'] : false;
if($options['hasManyRoots'])
$options['rootColumnName'] = isset($options['rootColumnName']) ? $options['rootColumnName'] : 'root_id';
parent::__construct($table, $options);
}
/**
* used to define table attributes required for the NestetSet implementation
* adds lft and rgt columns for corresponding left and right values
*
*/
public function setTableDefinition()
{
if ($root = $this->getAttribute('rootColumnName')) {
$this->table->setColumn($root, 'integer', 4);
}
$this->table->setColumn('lft', 'integer', 4);
$this->table->setColumn('rgt', 'integer', 4);
$this->table->setColumn('level', 'integer', 2);
}
/**
* creates root node from given record or from a new record
*
* @param object $record instance of Doctrine_Record
*/
public function createRoot(Doctrine_Record $record = null)
{
if ( ! $record) {
$record = $this->table->create();
}
// if tree is many roots, and no root id has been set, then get next root id
if ($root = $this->getAttribute('hasManyRoots') && $record->getNode()->getRootValue() <= 0) {
$record->getNode()->setRootValue($this->getNextRootId());
}
$record->set('lft', '1');
$record->set('rgt', '2');
$record->save();
return $record;
}
/**
* returns root node
*
* @return object $record instance of Doctrine_Record
* @deprecated Use fetchRoot()
*/
public function findRoot($rootId = 1)
{
return $this->fetchRoot($rootId);
}
/**
* Fetches a/the root node.
*
* @param integer $rootId
*/
public function fetchRoot($rootId = 1)
{
$q = $this->getBaseQuery();
$q = $q->where('base.lft = ?', 1);
// if tree has many roots, then specify root id
$q = $this->returnQueryWithRootId($q, $rootId);
$data = $q->execute();
if (count($data) <= 0) {
return false;
}
if ($data instanceof Doctrine_Collection) {
$root = $data->getFirst();
$root['level'] = 0;
} else if (is_array($data)) {
$root = array_shift($data);
$root['level'] = 0;
} else {
throw new Doctrine_Tree_Exception("Unexpected data structure returned.");
}
return $root;
}
/**
* Fetches a tree.
*
* @param array $options Options
* @return mixed The tree or FALSE if the tree could not be found.
*/
public function fetchTree($options = array())
{
// fetch tree
$q = $this->getBaseQuery();
$componentName = $this->table->getComponentName();
$q = $q->addWhere("base.lft >= ?", 1);
// if tree has many roots, then specify root id
$rootId = isset($options['root_id']) ? $options['root_id'] : '1';
if (is_array($rootId)) {
$q->orderBy("base." . $this->getAttribute('rootColumnName') . ", base.lft ASC");
} else {
$q->orderBy("base.lft ASC");
}
$q = $this->returnQueryWithRootId($q, $rootId);
$tree = $q->execute();
if (count($tree) <= 0) {
return false;
}
return $tree;
}
/**
* Fetches a branch of a tree.
*
* @param mixed $pk primary key as used by table::find() to locate node to traverse tree from
* @param array $options Options.
* @return mixed The branch or FALSE if the branch could not be found.
* @todo Only fetch the lft and rgt values of the initial record. more is not needed.
*/
public function fetchBranch($pk, $options = array())
{
$record = $this->table->find($pk);
if ( ! ($record instanceof Doctrine_Record) || !$record->exists()) {
// TODO: if record doesn't exist, throw exception or similar?
return false;
}
//$depth = isset($options['depth']) ? $options['depth'] : null;
$q = $this->getBaseQuery();
$params = array($record->get('lft'), $record->get('rgt'));
$q->where("base.lft >= ? AND base.rgt <= ?", $params)->orderBy("base.lft asc");
$q = $this->returnQueryWithRootId($q, $record->getNode()->getRootValue());
return $q->execute();
}
/**
* Fetches all root nodes. If the tree has only one root this is the same as
* fetchRoot().
*
* @return mixed The root nodes.
*/
public function fetchRoots()
{
$q = $this->getBaseQuery();
$q = $q->where('base.lft = ?', 1);
return $q->execute();
}
/**
* calculates the next available root id
*
* @return integer
*/
public function getNextRootId()
{
return $this->getMaxRootId() + 1;
}
/**
* calculates the current max root id
*
* @return integer
*/
public function getMaxRootId()
{
$component = $this->table->getComponentName();
$column = $this->getAttribute('rootColumnName');
// cannot get this dql to work, cannot retrieve result using $coll[0]->max
//$dql = "SELECT MAX(c.$column) FROM $component c";
$dql = 'SELECT c.' . $column . ' FROM ' . $component . ' c ORDER BY c.' . $column . ' DESC LIMIT 1';
$coll = $this->table->getConnection()->query($dql);
$max = $coll[0]->get($column);
$max = !is_null($max) ? $max : 0;
return $max;
}
/**
* returns parsed query with root id where clause added if applicable
*
* @param object $query Doctrine_Query
* @param integer $root_id id of destination root
* @return object Doctrine_Query
*/
public function returnQueryWithRootId($query, $rootId = 1)
{
if ($root = $this->getAttribute('rootColumnName')) {
if (is_array($rootId)) {
$query->addWhere($root . ' IN (' . implode(',', array_fill(0, count($rootId), '?')) . ')',
$rootId);
} else {
$query->addWhere($root . ' = ?', $rootId);
}
}
return $query;
}
/**
* Enter description here...
*
* @param array $options
* @return unknown
*/
public function getBaseQuery()
{
if ( ! isset($this->_baseQuery)) {
$this->_baseQuery = $this->_createBaseQuery();
}
return clone $this->_baseQuery;
}
/**
* Enter description here...
*
*/
private function _createBaseQuery()
{
$q = new Doctrine_Query();
$q->select("base.*")->from($this->table->getComponentName() . " base");
return $q;
}
/**
* Enter description here...
*
* @param Doctrine_Query $query
*/
public function setBaseQuery(Doctrine_Query $query)
{
$query->addSelect("base.lft, base.rgt, base.level");
if ($this->getAttribute('rootColumnName')) {
$query->addSelect("base." . $this->getAttribute('rootColumnName'));
}
$this->_baseQuery = $query;
}
/**
* Enter description here...
*
*/
public function resetBaseQuery()
{
$this->_baseQuery = null;
}
/**
* Enter description here...
*
* @param unknown_type $graph
*/
/*
public function computeLevels($tree)
{
$right = array();
$isArray = is_array($tree);
$rootColumnName = $this->getAttribute('rootColumnName');
for ($i = 0, $count = count($tree); $i < $count; $i++) {
if ($rootColumnName && $i > 0 && $tree[$i][$rootColumnName] != $tree[$i-1][$rootColumnName]) {
$right = array();
}
if (count($right) > 0) {
while (count($right) > 0 && $right[count($right)-1] < $tree[$i]['rgt']) {
//echo count($right);
array_pop($right);
}
}
if ($isArray) {
$tree[$i]['level'] = count($right);
} else {
$tree[$i]->getNode()->setLevel(count($right));
}
$right[] = $tree[$i]['rgt'];
}
return $tree;
}
*/
}

View File

@ -1,40 +0,0 @@
Outline of the changes to the NestedSet
Structural changes:
In addition to the lft and rgt columns there's now a column 'level' that gets automatically added to your model
when you use the nestedset. As with the lft and rgt values should never modify this value. All changes to this field
are handled transparently for you when you move nodes around or insert new ones.
General API changes:
Nearly all of the methods of the Node and Tree interfaces now return FALSE if no parent/child/sibling/ancestor(s)/
descendant(s) were found. In addition there have been some additions to certain methods. i.e. getAncestors() now
has a parameter that allows you to retrieve the ancestors up to a certain level.
Fetching relations together with nodes:
This is how you can temporarily set your own query as the base query that is used by the nestedset.
The nestedset implementation now uses the latest DQL syntax. Therefore it now uses a reserved alias
'base' that identifies the tree component. Through that alias you can even select which fields you
need of the nodes themselves, in addition to the fields you need from related components.
Note that you dont need to specify the special columns 'lft', 'rgt' and 'level' in any of your
queries. These are always added automatically since they're essential for the tree structure.
Example:
$query->select("base.name, le.topic, a.name")->from("VForum_Model_ForumNode base")
->leftJoin("base.lastEntry le")
->leftJoin("le.author a")
->setHydrationMode(Doctrine_Query::HYDRATE_ARRAY);
$treeMngr = $conn->getTable('VForum_Model_ForumNode')->getTree();
$treeMngr->setBaseQuery($query);
$tree = $tree->fetchTree();
$treeMngr->resetBaseQuery();
This example shows that even array fetching is possible. And since the level is now stored
in the database and is a regular field of every record you can access it like every other field
($record['level']), regardless of the hydration mode used (objects/arrays).
Note that you can't modify clauses like where or orderby. These will be overridden by the appropriate method
you're calling. i.e. if you call getDescendants() the WHERE part results from the fact that you
want the descendants and the ORDER BY part is always used to retrieve the nodes in the order they appear in the tree,
so that you can easily traverse and display the tree structure.

View File

@ -1029,6 +1029,7 @@ class Doctrine_Hydrate extends Doctrine_Locator_Injectable implements Serializab
$rootMap = reset($this->_aliasMap);
$rootAlias = key($this->_aliasMap);
$componentName = $rootMap['table']->getComponentName();
$isSimpleQuery = count($this->_aliasMap) <= 1;
if ($hydrationMode === null) {
$hydrationMode = $this->_hydrationMode;
@ -1107,7 +1108,8 @@ class Doctrine_Hydrate extends Doctrine_Locator_Injectable implements Serializab
$oneToOne = false;
$index = $driver->search($element, $array);
$index = $isSimpleQuery ? false : $driver->search($element, $array);
if ($index === false) {
$event->set('data', $element);
$listeners[$componentName]->postHydrate($event);
@ -1157,7 +1159,7 @@ class Doctrine_Hydrate extends Doctrine_Locator_Injectable implements Serializab
// append element
if (isset($identifiable[$alias])) {
$index = $driver->search($element, $prev[$parent][$componentAlias]);
$index = $isSimpleQuery ? false : $driver->search($element, $prev[$parent][$componentAlias]);
if ($index === false) {
$event->set('data', $element);

View File

@ -531,7 +531,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
// Relocate descendants of the node
$diff = $this->getLeftValue() - $oldLft;
$componentName = $this->_tree->getBaseComponent();
$rootColName = $this->record->getTable()->getTree()->getAttribute('rootColumnName');
$rootColName = $this->_tree->getAttribute('rootColumnName');
// Update lft/rgt/root/level for all descendants
$q = new Doctrine_Query($conn);
@ -628,7 +628,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
public function makeRoot($newRootId)
{
// TODO: throw exception instead?
if ($this->getLeftValue() == 1 || !$this->record->getTable()->getTree()->getAttribute('hasManyRoots')) {
if ($this->getLeftValue() == 1 || ! $this->_tree->getAttribute('hasManyRoots')) {
return false;
}
@ -656,7 +656,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
$diff = 1 - $oldLft;
$newRoot = $newRootId;
$componentName = $this->_tree->getBaseComponent();
$rootColName = $this->record->getTable()->getTree()->getAttribute('rootColumnName');
$rootColName = $this->_tree->getAttribute('rootColumnName');
$q = new Doctrine_Query($conn);
$q = $q->update($componentName)
->set($componentName . '.lft', 'lft + ?', $diff)
@ -773,7 +773,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
$q = $q->addWhere("$baseAlias.lft >= ? AND $baseAlias.rgt <= ?", array($this->getLeftValue(), $this->getRightValue()));
$q = $this->record->getTable()->getTree()->returnQueryWithRootId($q, $oldRoot);
$q = $this->_tree->returnQueryWithRootId($q, $oldRoot);
$coll = $q->execute();
@ -859,7 +859,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
->set($componentName . '.lft', 'lft + ?')
->where($componentName . '.lft >= ?', array($delta, $first));
$qLeft = $this->record->getTable()->getTree()->returnQueryWithRootId($qLeft, $rootId);
$qLeft = $this->_tree->returnQueryWithRootId($qLeft, $rootId);
$resultLeft = $qLeft->execute();
@ -868,7 +868,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
->set($componentName . '.rgt', 'rgt + ?')
->where($componentName . '.rgt >= ?', array($delta, $first));
$qRight = $this->record->getTable()->getTree()->returnQueryWithRootId($qRight, $rootId);
$qRight = $this->_tree->returnQueryWithRootId($qRight, $rootId);
$resultRight = $qRight->execute();
}
@ -892,7 +892,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
->set($componentName . '.lft', 'lft + ?')
->where($componentName . '.lft >= ? AND ' . $componentName . '.lft <= ?', array($delta, $first, $last));
$qLeft = $this->record->getTable()->getTree()->returnQueryWithRootId($qLeft, $rootId);
$qLeft = $this->_tree->returnQueryWithRootId($qLeft, $rootId);
$resultLeft = $qLeft->execute();
@ -901,7 +901,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
->set($componentName . '.rgt', 'rgt + ?')
->where($componentName . '.rgt >= ? AND ' . $componentName . '.rgt <= ?', array($delta, $first, $last));
$qRight = $this->record->getTable()->getTree()->returnQueryWithRootId($qRight, $rootId);
$qRight = $this->_tree->returnQueryWithRootId($qRight, $rootId);
$resultRight = $qRight->execute();
}

Binary file not shown.