Commit 9b53bb89 authored by joesimms's avatar joesimms

joesimms: initial draft for tree support. NestedSet support included,...

joesimms: initial draft for tree support. NestedSet support included, placeholders for other popular implementations also included. Read the README.tree file for more information and changelog to core files. Modified core files have also been included in this commit. hope it works and you like it !
parent f56b11af
<?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("/Users/joesimms/projects/doctrine/trunk/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');
// 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
// maybe change to actually use __toString(), howvever, i wondered if this had any significance ??
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 />';
}
<?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(&$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(&$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(&$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());
$iteratorClass = 'Doctrine_Node_'.$this->record->getTable()->getTreeImplName().'_'.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;
}
} // END class
\ No newline at end of file
......@@ -19,26 +19,15 @@
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Tree_PathModel
* Doctrine_Node_AdjacencyList
*
* the purpose of Doctrine_Tree_PathModel is to provide PathModel tree access
* strategy for all records extending it
*
* @package Doctrine ORM
* @url www.phpdoctrine.com
* @license LGPL
* @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_PathModel extends Doctrine_Record {
public function getLeafNodes() { }
public function getPath() { }
public function getDepth() { }
public function removeNode() { }
public function addNode() { }
}
class Doctrine_Node_AdjacencyList extends Doctrine_Node implements Doctrine_Node_Interface { }
\ No newline at end of file
......@@ -19,35 +19,14 @@
* <http://www.phpdoctrine.com>.
*/
/**
* Doctrine_Tree_NestedSet
* Doctrine_Node_AdjacencyList_LevelOrderIterator
*
* the purpose of Doctrine_Tree_NestedSet is to provide NestedSet tree access
* strategy for all records extending it
*
* @package Doctrine ORM
* @url www.phpdoctrine.com
* @license LGPL
* @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_NestedSet extends Doctrine_Record {
public function getLeafNodes() {
$query = "SELECT ".implode(", ",$this->table->getColumnNames()).
" FROM ".$this->table->getTableName().
" WHERE rgt = lft + 1";
}
public function getPath() { }
public function getDepth() {
$query = "SELECT (COUNT(parent.name) - 1) AS depth
FROM ".$this->table->getTableName()." AS node,".
$this->table->getTableName()." AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name";
}
public function removeNode() { }
public function addNode() { }
}
class Doctrine_Node_AdjacencyList_LevelOrderIterator implements Iterator {}
<?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 {}
<?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 {}
<?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 { }
<?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();
}
\ No newline at end of file
<?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 { }
\ No newline at end of file
<?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');
}
}
<?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');
}
}
<?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');
}
}
<?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
*
* @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 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->record->getTable()->createQuery();
$result = $q->where('rgt = ?', $this->getLeftValue() - 1)->execute()->getFirst();
if(!$result)
$result = $this->record->getTable()->create();
return $result;
}
/**
* gets record of next sibling or empty record
*
* @return object Doctrine_Record
*/
public function getNextSibling()
{
$q = $this->record->getTable()->createQuery();
$result = $q->where('lft = ?', $this->getRightValue() + 1)->execute()->getFirst();
if(!$result)
$result = $this->record->getTable()->create();
return $result;
}
/**
* 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->record->getTable()->createQuery();
$result = $q->where('lft = ?', $this->getLeftValue() + 1)->execute()->getFirst();
if(!$result)
$result = $this->record->getTable()->create();
return $result;
}
/**
* gets record of last child or empty record
*
* @return object Doctrine_Record
*/
public function getLastChild()
{
$q = $this->record->getTable()->createQuery();
$result = $q->where('rgt = ?', $this->getRightValue() - 1)->execute()->getFirst();
if(!$result)
$result = $this->record->getTable()->create();
return $result;
}
/**
* gets children for node (direct descendants only)
*
* @return array array of sibling Doctrine_Record objects
*/
public function getChildren()
{
return $this->getIterator('Pre', array('depth' => 1));
}
/**
* gets descendants for node (direct descendants only)
*
* @return iterator iterator to traverse descendants from node
*/
public function getDescendants()
{
return $this->getIterator();
}
/**
* gets record of parent or empty record
*
* @return object Doctrine_Record
*/
public function getParent()
{
$q = $this->record->getTable()->createQuery();
$parent = $q->where('lft < ? AND rgt > ?', array($this->getLeftValue(), $this->getRightValue()))
->orderBy('rgt asc')
->execute()
->getFirst();
if(!$parent)
$parent = $this->record->getTable()->create();
return $parent;
}
/**
* gets ancestors for node
*
* @return object Doctrine_Collection
*/
public function getAncestors()
{
$q = $this->record->getTable()->createQuery();
$ancestors = $q->where('lft < ? AND rgt > ?', array($this->getLeftValue(), $this->getRightValue()))
->orderBy('lft asc')
->execute();
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
*/
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;
$this->shiftRLValues($dest->getNode()->getLeftValue(), 1);
$this->shiftRLValues($dest->getNode()->getRightValue() + 2, 1);
$newLeft = $dest->getNode()->getLeftValue();
$newRight = $dest->getNode()->getRightValue() + 2;
$this->insertNode($newLeft, $newRight);
return true;
}
/**
* inserts node as previous sibling of dest record
*
* @return bool
*/
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;
$this->shiftRLValues($newLeft, 2);
$this->insertNode($newLeft, $newRight);
// 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
*/
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;
$this->shiftRLValues($newLeft, 2);
$this->insertNode($newLeft, $newRight);
// 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
*/
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;
$this->shiftRLValues($newLeft, 2);
$this->insertNode($newLeft, $newRight);
// 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
*/
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;
$this->shiftRLValues($newLeft, 2);
$this->insertNode($newLeft, $newRight);
// update destination left/right values to prevent a refresh
// $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
return true;
}
/**
* moves node as prev sibling of dest record
*
*/
public function moveAsPrevSiblingOf(Doctrine_Record $dest)
{
$this->updateNode($dest->getNode()->getLeftValue());
}
/**
* moves node as next sibling of dest record
*
*/
public function moveAsNextSiblingOf(Doctrine_Record $dest)
{
$this->updateNode($dest->getNode()->getRightValue() + 1);
}
/**
* moves node as first child of dest record
*
*/
public function moveAsFirstChildOf(Doctrine_Record $dest)
{
$this->updateNode($dest->getNode()->getLeftValue() + 1);
}
/**
* moves node as last child of dest record
*
*/
public function moveAsLastChildOf(Doctrine_Record $dest)
{
$this->updateNode($dest->getNode()->getRightValue());
}
/**
* 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()) and ($this->getRightValue()==$subj->getNode()->getRightValue()));
}
/**
* determines if node is child of subject node
*
* @return bool
*/
public function isDescendantOf(Doctrine_Record $subj)
{
return (($this->getLeftValue()>$subj->getNode()->getLeftValue()) and ($this->getRightValue()<$subj->getNode()->getRightValue()));
}
/**
* 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()) and ($this->getRightValue()<=$subj->getNode()->getRightValue()));
}
/**
* determines if node is valid
*
* @return bool
*/
public function isValidNode()
{
return ($this->getRightValue() > $this->getLeftValue());
}
/**
* deletes node and it's descendants
*
*/
public function delete()
{
// TODO: add the setting whether or not to delete descendants or relocate children
$q = $this->record->getTable()->createQuery();
$componentName = $this->record->getTable()->getComponentName();
$coll = $q->where("$componentName.lft >= ? AND $componentName.rgt <= ?", array($this->getLeftValue(), $this->getRightValue()))->execute();
$coll->delete();
$first = $this->getRightValue() + 1;
$delta = $this->getLeftValue() - $this->getRightValue() - 1;
$this->shiftRLValues($first, $delta);
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)
{
$this->setLeftValue($destLeft);
$this->setRightValue($destRight);
$this->record->save();
}
/**
* move node's and its children to location $destLeft and updates rest of tree
*
* @param int $destLeft destination left value
*/
private function updateNode($destLeft)
{
$left = $this->getLeftValue();
$right = $this->getRightValue();
$treeSize = $right - $left + 1;
$this->shiftRLValues($destLeft, $treeSize);
if($left >= $destLeft){ // src was shifted too?
$left += $treeSize;
$right += $treeSize;
}
// now there's enough room next to target to move the subtree
$this->shiftRLRange($left, $right, $destLeft - $left);
// correct values after source
$this->shiftRLValues($right + 1, -$treeSize);
$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)
{
$qLeft = $this->record->getTable()->createQuery();
$qRight = $this->record->getTable()->createQuery();
// TODO: Wrap in transaction
// shift left columns
$resultLeft = $qLeft->update($this->record->getTable()->getComponentName())
->set('lft', "lft + $delta")
->where('lft >= ?', $first)
->execute();
// shift right columns
$resultRight = $qRight->update($this->record->getTable()->getComponentName())
->set('rgt', "rgt + $delta")
->where('rgt >= ?', $first)
->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)
{
$qLeft = $this->record->getTable()->createQuery();
$qRight = $this->record->getTable()->createQuery();
// TODO : Wrap in transaction
// shift left column values
$result = $qLeft->update($this->record->getTable()->getComponentName())
->set('lft', "lft + $delta")
->where('lft >= ? AND lft <= ?', array($first, $last))
->execute();
// shift right column values
$result = $qRight->update($this->record->getTable()->getComponentName())
->set('rgt', "rgt + $delta")
->where('rgt >= ? AND rgt <= ?', array($first, $last))
->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))
{
$q = $this->record->getTable()->createQuery();
$coll = $q->where('lft < ? AND rgt > ?', array($this->getLeftValue(), $this->getRightValue()))
->execute();
$this->level = $coll->count() ? $coll->count() : 0;
}
return $this->level;
}
/**
* sets node's level
*
* @param int
*/
public function setLevel($level)
{
$this->level = $level;
}
}
\ No newline at end of file
<?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 {}
<?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 {}
<?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();
if(isset($opts['include_record']) && $opts['include_record'])
{
$query = $q->where("$componentName.lft >= ? AND $componentName.rgt <= ?", array($record->get('lft'), $record->get('rgt')))->orderBy('lft asc');
}
else
{
$query = $q->where("$componentName.lft > ? AND $componentName.rgt < ?", array($record->get('lft'), $record->get('rgt')))->orderBy('lft asc');
}
$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;
}
}
<?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::autoload('Doctrine_Query_Part');
/**
* Doctrine_Query
*
* @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_Query_Set extends Doctrine_Query_Part
{
public function parse($dql)
{
$parts = Doctrine_Query::sqlExplode($dql, ',');
$result = array();
foreach ($parts as $part) {
$set = Doctrine_Query::sqlExplode($part, '=');
$e = explode('.', trim($set[0]));
$field = array_pop($e);
$reference = implode('.', $e);
$alias = $this->query->getTableAlias($reference);
$fieldname = $alias ? $alias . '.' . $field : $field;
$result[] = $fieldname . ' = ' . $set[1];
}
return implode(', ', $result);
}
}
<?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::autoload('Doctrine_Query_Condition');
/**
* Doctrine_Query_Where
*
* @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_Query_Where extends Doctrine_Query_Condition
{
/**
* load
* returns the parsed query part
*
* @param string $where
* @return string
*/
public function load($where)
{
$where = trim($where);
$e = Doctrine_Query::sqlExplode($where);
if (count($e) > 1) {
$tmp = $e[0] . ' ' . $e[1];
if (substr($tmp, 0, 6) == 'EXISTS') {
return $this->parseExists($where, true);
} elseif (substr($where, 0, 10) == 'NOT EXISTS') {
return $this->parseExists($where, false);
}
}
if (count($e) < 3) {
$e = Doctrine_Query::sqlExplode($where, array('=', '<', '>', '!='));
}
$r = array_shift($e);
$a = explode('.', $r);
if (count($a) > 1) {
$field = array_pop($a);
$count = count($e);
$slice = array_slice($e, -1, 1);
$value = implode('', $slice);
$operator = trim(substr($where, strlen($r), -strlen($value)));
$reference = implode('.', $a);
$count = count($a);
$pos = strpos($field, '(');
if ($pos !== false) {
$func = substr($field, 0, $pos);
$value = trim(substr($field, ($pos + 1), -1));
$values = Doctrine_Query::sqlExplode($value, ',');
$field = array_pop($a);
$reference = implode('.', $a);
$table = $this->query->load($reference, false);
array_pop($a);
$reference2 = implode('.', $a);
$alias = $this->query->getTableAlias($reference2);
$stack = $this->query->getRelationStack();
$relation = end($stack);
$stack = $this->query->getTableStack();
switch ($func) {
case 'contains':
case 'regexp':
case 'like':
$operator = $this->getOperator($func);
if (empty($relation)) {
throw new Doctrine_Query_Exception('DQL functions contains/regexp/like can only be used for fields of related components');
}
$where = array();
foreach ($values as $value) {
$where[] = $alias.'.'.$relation->getLocal().
' IN (SELECT '.$relation->getForeign().
' FROM '.$relation->getTable()->getTableName().' WHERE '.$field.$operator.$value.')';
}
$where = implode(' AND ', $where);
break;
default:
throw new Doctrine_Query_Exception('Unknown DQL function: '.$func);
}
} else {
$table = $this->query->load($reference, false);
$alias = $this->query->getTableAlias($reference);
$table = $this->query->getTable($alias);
// check if value is enumerated value
$enumIndex = $table->enumIndex($field, trim($value, "'"));
if (substr($value, 0, 1) == '(') {
// trim brackets
$trimmed = Doctrine_Query::bracketTrim($value);
if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
// subquery found
$q = new Doctrine_Query();
$value = '(' . $q->parseQuery($trimmed)->getQuery() . ')';
} elseif (substr($trimmed, 0, 4) == 'SQL:') {
$value = '(' . substr($trimmed, 4) . ')';
} else {
// simple in expression found
$e = Doctrine_Query::sqlExplode($trimmed, ',');
$value = array();
foreach ($e as $part) {
$index = $table->enumIndex($field, trim($part, "'"));
if ($index !== false) {
$value[] = $index;
} else {
$value[] = $this->parseLiteralValue($part);
}
}
$value = '(' . implode(', ', $value) . ')';
}
} else {
if ($enumIndex !== false) {
$value = $enumIndex;
} else {
$value = $this->parseLiteralValue($value);
}
}
switch ($operator) {
case '<':
case '>':
case '=':
case '!=':
if ($enumIndex !== false) {
$value = $enumIndex;
}
default:
$fieldname = $alias ? $alias . '.' . $field : $field;
$where = $fieldname . ' '
. $operator . ' ' . $value;
}
}
}
return $where;
}
/**
* parses an EXISTS expression
*
* @param string $where query where part to be parsed
* @param boolean $negation whether or not to use the NOT keyword
* @return string
*/
public function parseExists($where, $negation)
{
$operator = ($negation) ? 'EXISTS' : 'NOT EXISTS';
$pos = strpos($where, '(');
if ($pos == false)
throw new Doctrine_Query_Exception("Unknown expression, expected '('");
$sub = Doctrine_Query::bracketTrim(substr($where, $pos));
return $operator . ' ('.$this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')';
}
/**
* getOperator
*
* @param string $func
* @return string
*/
public function getOperator($func)
{
switch ($func) {
case 'contains':
$operator = ' = ';
break;
case 'regexp':
$operator = $this->query->getConnection()->getRegexpOperator();
break;
case 'like':
$operator = ' LIKE ';
break;
}
return $operator;
}
/**
* __toString
* return string representation of this object
*
* @return string
*/
public function __toString()
{
return ( ! empty($this->parts))?implode(' AND ', $this->parts):'';
}
}
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
\ No newline at end of file
<?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::autoload('Doctrine_Access');
/**
* Doctrine_Record
* All record classes should inherit this super class
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @package Doctrine
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
*/
abstract class Doctrine_Record extends Doctrine_Access implements Countable, IteratorAggregate, Serializable {
/**
* STATE CONSTANTS
*/
/**
* DIRTY STATE
* a Doctrine_Record is in dirty state when its properties are changed
*/
const STATE_DIRTY = 1;
/**
* TDIRTY STATE
* a Doctrine_Record is in transient dirty state when it is created and some of its fields are modified
* but it is NOT yet persisted into database
*/
const STATE_TDIRTY = 2;
/**
* CLEAN STATE
* a Doctrine_Record is in clean state when all of its properties are loaded from the database
* and none of its properties are changed
*/
const STATE_CLEAN = 3;
/**
* PROXY STATE
* a Doctrine_Record is in proxy state when its properties are not fully loaded
*/
const STATE_PROXY = 4;
/**
* NEW TCLEAN
* a Doctrine_Record is in transient clean state when it is created and none of its fields are modified
*/
const STATE_TCLEAN = 5;
/**
* DELETED STATE
* a Doctrine_Record turns into deleted state when it is deleted
*/
const STATE_DELETED = 6;
/**
* the following protected variables use '_' prefixes, the reason for this is to allow child
* classes call for example $this->id, $this->state for getting the values of columns named 'id' and 'state'
* rather than the values of these protected variables
*/
/**
* @var object Doctrine_Table $table the factory that created this data access object
*/
protected $_table;
/**
* @var integer $id the primary keys of this object
*/
protected $_id = array();
/**
* @var array $data the record data
*/
protected $_data = array();
/**
* @var integer $state the state of this record
* @see STATE_* constants
*/
protected $_state;
/**
* @var array $modified an array containing properties that have been modified
*/
protected $_modified = array();
/**
* @var Doctrine_Validator_ErrorStack error stack object
*/
protected $_errorStack;
/**
* @var Doctrine_Node_<TreeImpl> node object
*/
protected $_node;
/**
* @var array $references an array containing all the references
*/
private $references = array();
/**
* @var array $originals an array containing all the original references
*/
private $originals = array();
/**
* @var integer $index this index is used for creating object identifiers
*/
private static $index = 1;
/**
* @var Doctrine_Null $null a Doctrine_Null object used for extremely fast
* null value testing
*/
private static $null;
/**
* @var integer $oid object identifier, each Record object has a unique object identifier
*/
private $oid;
/**
* constructor
* @param Doctrine_Table|null $table a Doctrine_Table object or null,
* if null the table object is retrieved from current connection
*
* @param boolean $isNewEntry whether or not this record is transient
*
* @throws Doctrine_Connection_Exception if object is created using the new operator and there are no
* open connections
* @throws Doctrine_Record_Exception if the cleanData operation fails somehow
*/
public function __construct($table = null, $isNewEntry = false) {
if(isset($table) && $table instanceof Doctrine_Table) {
$this->_table = $table;
$exists = ( ! $isNewEntry);
} else {
$class = get_class($this);
// get the table of this class
$this->_table = Doctrine_Manager::getInstance()->getConnectionForComponent($class)->getTable(get_class($this));
$exists = false;
}
// Check if the current connection has the records table in its registry
// If not this record is only used for creating table definition and setting up
// relations.
if($this->_table->getConnection()->hasTable($this->_table->getComponentName())) {
$this->oid = self::$index;
self::$index++;
$keys = $this->_table->getPrimaryKeys();
if( ! $exists) {
// listen the onPreCreate event
$this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onPreCreate($this);
} else {
// listen the onPreLoad event
$this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onPreLoad($this);
}
// get the data array
$this->_data = $this->_table->getData();
// get the column count
$count = count($this->_data);
// clean data array
$this->cleanData();
$this->prepareIdentifiers($exists);
if( ! $exists) {
if($count > 0)
$this->_state = Doctrine_Record::STATE_TDIRTY;
else
$this->_state = Doctrine_Record::STATE_TCLEAN;
// set the default values for this record
$this->setDefaultValues();
// listen the onCreate event
$this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onCreate($this);
} else {
$this->_state = Doctrine_Record::STATE_CLEAN;
if($count < $this->_table->getColumnCount()) {
$this->_state = Doctrine_Record::STATE_PROXY;
}
// listen the onLoad event
$this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this);
}
$this->_errorStack = new Doctrine_Validator_ErrorStack();
$repository = $this->_table->getRepository();
$repository->add($this);
}
$this->construct();
}
/**
* initNullObject
*
* @param Doctrine_Null $null
* @return void
*/
public static function initNullObject(Doctrine_Null $null) {
self::$null = $null;
}
/**
* @return Doctrine_Null
*/
public static function getNullObject() {
return self::$null;
}
/**
* setUp
* this method is used for setting up relations and attributes
* it should be implemented by child classes
*
* @return void
*/
public function setUp() { }
/**
* construct
* Empty tempalte method to provide concrete Record classes with the possibility
* to hook into the constructor procedure
*
* @return void
*/
public function construct() { }
/**
* getOID
* returns the object identifier
*
* @return integer
*/
public function getOID() {
return $this->oid;
}
/**
* isValid
*
* @return boolean whether or not this record passes all column validations
*/
public function isValid() {
if( ! $this->_table->getAttribute(Doctrine::ATTR_VLD))
return true;
// Clear the stack from any previous errors.
$this->_errorStack->clear();
// Run validation process
$validator = new Doctrine_Validator();
$validator->validateRecord($this);
$this->validate();
if ($this->_state == self::STATE_TDIRTY || $this->_state == self::STATE_TCLEAN) {
$this->validateOnInsert();
} else {
$this->validateOnUpdate();
}
return $this->_errorStack->count() == 0 ? true : false;
}
/**
* Emtpy template method to provide concrete Record classes with the possibility
* to hook into the validation procedure, doing any custom / specialized
* validations that are neccessary.
*/
protected function validate() {}
/**
* Empty tempalte method to provide concrete Record classes with the possibility
* to hook into the validation procedure only when the record is going to be
* updated.
*/
protected function validateOnUpdate() {}
/**
* Empty tempalte method to provide concrete Record classes with the possibility
* to hook into the validation procedure only when the record is going to be
* inserted into the data store the first time.
*/
protected function validateOnInsert() {}
/**
* getErrorStack
*
* @return Doctrine_Validator_ErrorStack returns the errorStack associated with this record
*/
public function getErrorStack() {
return $this->_errorStack;
}
/**
* setDefaultValues
* sets the default values for records internal data
*
* @param boolean $overwrite whether or not to overwrite the already set values
* @return boolean
*/
public function setDefaultValues($overwrite = false) {
if( ! $this->_table->hasDefaultValues())
return false;
foreach($this->_data as $column => $value) {
$default = $this->_table->getDefaultValueOf($column);
if($default === null)
$default = self::$null;
if($value === self::$null || $overwrite) {
$this->_data[$column] = $default;
$this->_modified[] = $column;
$this->_state = Doctrine_Record::STATE_TDIRTY;
}
}
}
/**
* cleanData
* this method does several things to records internal data
*
* 1. It unserializes array and object typed columns
* 2. Uncompresses gzip typed columns
* 3. Gets the appropriate enum values for enum typed columns
* 4. Initializes special null object pointer for null values (for fast column existence checking purposes)
*
*
* example:
*
* $data = array("name"=>"John","lastname"=> null, "id" => 1,"unknown" => "unknown");
* $names = array("name", "lastname", "id");
* $data after operation:
* $data = array("name"=>"John","lastname" => Object(Doctrine_Null));
*
* here column 'id' is removed since its auto-incremented primary key (read-only)
*
* @throws Doctrine_Record_Exception if unserialization of array/object typed column fails or
* if uncompression of gzip typed column fails
*
* @return integer
*/
private function cleanData($debug = false) {
$tmp = $this->_data;
$this->_data = array();
$count = 0;
foreach($this->_table->getColumnNames() as $name) {
$type = $this->_table->getTypeOf($name);
if( ! isset($tmp[$name])) {
$this->_data[$name] = self::$null;
} else {
switch($type):
case "array":
case "object":
if($tmp[$name] !== self::$null) {
if(is_string($tmp[$name])) {
$value = unserialize($tmp[$name]);
if($value === false)
throw new Doctrine_Record_Exception("Unserialization of $name failed. ".var_dump(substr($tmp[$lower],0,30)."...",true));
} else
$value = $tmp[$name];
$this->_data[$name] = $value;
}
break;
case "gzip":
if($tmp[$name] !== self::$null) {
$value = gzuncompress($tmp[$name]);
if($value === false)
throw new Doctrine_Record_Exception("Uncompressing of $name failed.");
$this->_data[$name] = $value;
}
break;
case "enum":
$this->_data[$name] = $this->_table->enumValue($name, $tmp[$name]);
break;
default:
$this->_data[$name] = $tmp[$name];
endswitch;
$count++;
}
}
return $count;
}
/**
* prepareIdentifiers
* prepares identifiers for later use
*
* @param boolean $exists whether or not this record exists in persistent data store
* @return void
*/
private function prepareIdentifiers($exists = true) {
switch($this->_table->getIdentifierType()):
case Doctrine_Identifier::AUTO_INCREMENT:
case Doctrine_Identifier::SEQUENCE:
$name = $this->_table->getIdentifier();
if($exists) {
if(isset($this->_data[$name]) && $this->_data[$name] !== self::$null)
$this->_id[$name] = $this->_data[$name];
}
unset($this->_data[$name]);
break;
case Doctrine_Identifier::NORMAL:
$this->_id = array();
$name = $this->_table->getIdentifier();
if(isset($this->_data[$name]) && $this->_data[$name] !== self::$null)
$this->_id[$name] = $this->_data[$name];
break;
case Doctrine_Identifier::COMPOSITE:
$names = $this->_table->getIdentifier();
foreach($names as $name) {
if($this->_data[$name] === self::$null)
$this->_id[$name] = null;
else
$this->_id[$name] = $this->_data[$name];
}
break;
endswitch;
}
/**
* serialize
* this method is automatically called when this Doctrine_Record is serialized
*
* @return array
*/
public function serialize() {
$this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onSleep($this);
$vars = get_object_vars($this);
unset($vars['references']);
unset($vars['originals']);
unset($vars['_table']);
$name = $this->_table->getIdentifier();
$this->_data = array_merge($this->_data, $this->_id);
foreach($this->_data as $k => $v) {
if($v instanceof Doctrine_Record)
unset($vars['_data'][$k]);
elseif($v === self::$null) {
unset($vars['_data'][$k]);
} else {
switch($this->_table->getTypeOf($k)):
case "array":
case "object":
$vars['_data'][$k] = serialize($vars['_data'][$k]);
break;
endswitch;
}
}
return serialize($vars);
}
/**
* unseralize
* this method is automatically called everytime a Doctrine_Record object is unserialized
*
* @param string $serialized Doctrine_Record as serialized string
* @throws Doctrine_Record_Exception if the cleanData operation fails somehow
* @return void
*/
public function unserialize($serialized) {
$manager = Doctrine_Manager::getInstance();
$connection = $manager->getCurrentConnection();
$this->oid = self::$index;
self::$index++;
$this->_table = $connection->getTable(get_class($this));
$array = unserialize($serialized);
foreach($array as $name => $values) {
$this->$name = $values;
}
$this->_table->getRepository()->add($this);
$this->cleanData();
$this->prepareIdentifiers($this->exists());
$this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onWakeUp($this);
}
/**
* getState
* returns the current state of the object
*
* @see Doctrine_Record::STATE_* constants
* @return integer
*/
public function getState() {
return $this->_state;
}
/**
* state
* returns / assigns the state of this record
*
* @param integer|string $state if set, this method tries to set the record state to $state
* @see Doctrine_Record::STATE_* constants
*
* @throws Doctrine_Record_State_Exception if trying to set an unknown state
* @return null|integer
*/
public function state($state = null) {
if($state == null) {
return $this->_state;
}
$err = false;
if(is_integer($state)) {
if($state >= 1 && $state <= 6)
$this->_state = $state;
else
$err = true;
} elseif(is_string($state)) {
$upper = strtoupper($state);
switch($upper) {
case 'DIRTY':
case 'CLEAN':
case 'TDIRTY':
case 'TCLEAN':
case 'PROXY':
case 'DELETED':
$this->_state = constant('Doctrine_Record::STATE_' . $upper);
break;
default:
$err = true;
}
}
if($err)
throw new Doctrine_Record_State_Exception('Unknown record state ' . $state);
}
/**
* refresh
* refresh internal data from the database
*
* @throws Doctrine_Record_Exception When the refresh operation fails (when the database row
* this record represents does not exist anymore)
* @return boolean
*/
final public function refresh() {
$id = $this->obtainIdentifier();
if( ! is_array($id))
$id = array($id);
if(empty($id))
return false;
$id = array_values($id);
$query = $this->_table->getQuery()." WHERE ".implode(" = ? AND ",$this->_table->getPrimaryKeys())." = ?";
$stmt = $this->_table->getConnection()->execute($query,$id);
$this->_data = $stmt->fetch(PDO::FETCH_ASSOC);
if( ! $this->_data)
throw new Doctrine_Record_Exception('Failed to refresh. Record does not exist anymore');
$this->_data = array_change_key_case($this->_data, CASE_LOWER);
$this->_modified = array();
$this->cleanData(true);
$this->prepareIdentifiers();
$this->_state = Doctrine_Record::STATE_CLEAN;
$this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this);
return true;
}
/**
* factoryRefresh
* refreshes the data from outer source (Doctrine_Table)
*
* @throws Doctrine_Record_Exception When the primary key of this record doesn't match the primary key fetched from a collection
* @return void
*/
final public function factoryRefresh() {
$this->_data = $this->_table->getData();
$old = $this->_id;
$this->cleanData();
$this->prepareIdentifiers();
if($this->_id != $old)
throw new Doctrine_Record_Exception("The refreshed primary key doesn't match the one in the record memory.", Doctrine::ERR_REFRESH);
$this->_state = Doctrine_Record::STATE_CLEAN;
$this->_modified = array();
$this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this);
}
/**
* getTable
* returns the table object for this record
*
* @return object Doctrine_Table a Doctrine_Table object
*/
final public function getTable() {
return $this->_table;
}
/**
* getData
* return all the internal data
*
* @return array an array containing all the properties
*/
final public function getData() {
return $this->_data;
}
/**
* rawGet
* returns the value of a property, if the property is not yet loaded
* this method does NOT load it
*
* @param $name name of the property
* @throws Doctrine_Record_Exception if trying to get an unknown property
* @return mixed
*/
public function rawGet($name) {
if( ! isset($this->_data[$name]))
throw new Doctrine_Record_Exception('Unknown property '. $name);
if($this->_data[$name] === self::$null)
return null;
return $this->_data[$name];
}
/**
* load
* loads all the unitialized properties from the database
*
* @return boolean
*/
public function load() {
// only load the data from database if the Doctrine_Record is in proxy state
if($this->_state == Doctrine_Record::STATE_PROXY) {
$this->refresh();
$this->_state = Doctrine_Record::STATE_CLEAN;
return true;
}
return false;
}
/**
* get
* returns a value of a property or a related component
*
* @param mixed $name name of the property or related component
* @param boolean $invoke whether or not to invoke the onGetProperty listener
* @throws Doctrine_Record_Exception if trying to get a value of unknown property / related component
* @return mixed
*/
public function get($name, $invoke = true) {
$listener = $this->_table->getAttribute(Doctrine::ATTR_LISTENER);
$value = self::$null;
$lower = strtolower($name);
if(isset($this->_data[$lower])) {
// check if the property is null (= it is the Doctrine_Null object located in self::$null)
if($this->_data[$lower] === self::$null)
$this->load();
if($this->_data[$lower] === self::$null)
$value = null;
else
$value = $this->_data[$lower];
}
if($value !== self::$null) {
$value = $this->_table->invokeGet($this, $name, $value);
if($invoke && $name !== $this->_table->getIdentifier())
return $this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onGetProperty($this, $name, $value);
else
return $value;
return $value;
}
if(isset($this->_id[$lower]))
return $this->_id[$lower];
if($name === $this->_table->getIdentifier())
return null;
$rel = $this->_table->getRelation($name);
try {
if( ! isset($this->references[$name]))
$this->loadReference($name);
} catch(Doctrine_Table_Exception $e) {
throw new Doctrine_Record_Exception("Unknown property / related component '$name'.");
}
return $this->references[$name];
}
/**
* set
* method for altering properties and Doctrine_Record references
* if the load parameter is set to false this method will not try to load uninitialized record data
*
* @param mixed $name name of the property or reference
* @param mixed $value value of the property or reference
* @param boolean $load whether or not to refresh / load the uninitialized record data
*
* @throws Doctrine_Record_Exception if trying to set a value for unknown property / related component
* @throws Doctrine_Record_Exception if trying to set a value of wrong type for related component
*
* @return Doctrine_Record
*/
public function set($name, $value, $load = true) {
$lower = strtolower($name);
if(isset($this->_data[$lower])) {
if($value instanceof Doctrine_Record) {
$id = $value->getIncremented();
if($id !== null)
$value = $id;
}
if($load)
$old = $this->get($lower, false);
else
$old = $this->_data[$lower];
if($old !== $value) {
$value = $this->_table->invokeSet($this, $name, $value);
$value = $this->_table->getAttribute(Doctrine::ATTR_LISTENER)->onSetProperty($this, $name, $value);
if($value === null)
$value = self::$null;
$this->_data[$lower] = $value;
$this->_modified[] = $lower;
switch($this->_state):
case Doctrine_Record::STATE_CLEAN:
$this->_state = Doctrine_Record::STATE_DIRTY;
break;
case Doctrine_Record::STATE_TCLEAN:
$this->_state = Doctrine_Record::STATE_TDIRTY;
break;
endswitch;
}
} else {
try {
$this->coreSetRelated($name, $value);
} catch(Doctrine_Table_Exception $e) {
throw new Doctrine_Record_Exception("Unknown property / related component '$name'.");
}
}
}
public function coreSetRelated($name, $value) {
$rel = $this->_table->getRelation($name);
// one-to-many or one-to-one relation
if($rel instanceof Doctrine_Relation_ForeignKey ||
$rel instanceof Doctrine_Relation_LocalKey) {
if( ! $rel->isOneToOne()) {
// one-to-many relation found
if( ! ($value instanceof Doctrine_Collection))
throw new Doctrine_Record_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references.");
$value->setReference($this,$rel);
} else {
// one-to-one relation found
if( ! ($value instanceof Doctrine_Record))
throw new Doctrine_Record_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Record when setting one-to-one references.");
if($rel instanceof Doctrine_Relation_LocalKey) {
$this->set($rel->getLocal(), $value, false);
} else {
$value->set($rel->getForeign(), $this, false);
}
}
} elseif($rel instanceof Doctrine_Relation_Association) {
// join table relation found
if( ! ($value instanceof Doctrine_Collection))
throw new Doctrine_Record_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting many-to-many references.");
}
$this->references[$name] = $value;
}
/**
* contains
*
* @param string $name
* @return boolean
*/
public function contains($name) {
$lower = strtolower($name);
if(isset($this->_data[$lower]))
return true;
if(isset($this->_id[$lower]))
return true;
if(isset($this->references[$name]))
return true;
return false;
}
/**
* @param string $name
* @return void
*/
public function __unset($name) {
if(isset($this->_data[$name]))
$this->_data[$name] = array();
// todo: what to do with references ?
}
/**
* applies the changes made to this object into database
* this method is smart enough to know if any changes are made
* and whether to use INSERT or UPDATE statement
*
* this method also saves the related components
*
* @param Doctrine_Connection $conn optional connection parameter
* @return void
*/
public function save(Doctrine_Connection $conn = null) {
if($this->_table->isTree() && !$this->getNode()->isValidNode())
throw new Doctrine_Exception('You must insert the node into the tree before it can be saved');
if ($conn === null) {
$conn = $this->_table->getConnection();
}
$conn->beginTransaction();
$saveLater = $conn->unitOfWork->saveRelated($this);
if ($this->isValid()) {
$conn->save($this);
} else {
$conn->transaction->addInvalid($this);
}
foreach($saveLater as $fk) {
$table = $fk->getTable();
$alias = $this->_table->getAlias($table->getComponentName());
if(isset($this->references[$alias])) {
$obj = $this->references[$alias];
$obj->save();
}
}
// save the MANY-TO-MANY associations
$conn->unitOfWork->saveAssociations($this);
//$this->saveAssociations();
$conn->commit();
}
/**
* replace
* Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
* query, except that if there is already a row in the table with the same
* key field values, the REPLACE query just updates its values instead of
* inserting a new row.
*
* The REPLACE type of query does not make part of the SQL standards. Since
* practically only MySQL and SQLIte implement it natively, this type of
* query isemulated through this method for other DBMS using standard types
* of queries inside a transaction to assure the atomicity of the operation.
*
* @param Doctrine_Connection $conn optional connection parameter
* @throws Doctrine_Connection_Exception if some of the key values was null
* @throws Doctrine_Connection_Exception if there were no key fields
* @throws PDOException if something fails at PDO level
* @return integer number of rows affected
*/
public function replace(Doctrine_Connection $conn = null) {
if ($conn === null) {
$conn = $this->_table->getConnection();
}
return $conn->replace($this->_table->getTableName(), $this->getPrepared(), $this->id);
}
/**
* returns an array of modified fields and associated values
* @return array
*/
public function getModified() {
$a = array();
foreach($this->_modified as $k => $v) {
$a[$v] = $this->_data[$v];
}
return $a;
}
/**
* getPrepared
*
* returns an array of modified fields and values with data preparation
* adds column aggregation inheritance and converts Records into primary key values
*
* @param array $array
* @return array
*/
public function getPrepared(array $array = array()) {
$a = array();
if(empty($array))
$array = $this->_modified;
foreach($array as $k => $v) {
$type = $this->_table->getTypeOf($v);
if($this->_data[$v] === self::$null) {
$a[$v] = null;
continue;
}
switch($type) {
case 'array':
case 'object':
$a[$v] = serialize($this->_data[$v]);
break;
case 'gzip':
$a[$v] = gzcompress($this->_data[$v],5);
break;
case 'boolean':
$a[$v] = (int) $this->_data[$v];
break;
case 'enum':
$a[$v] = $this->_table->enumIndex($v,$this->_data[$v]);
break;
default:
if($this->_data[$v] instanceof Doctrine_Record)
$this->_data[$v] = $this->_data[$v]->getIncremented();
$a[$v] = $this->_data[$v];
}
}
foreach($this->_table->getInheritanceMap() as $k => $v) {
$old = $this->get($k, false);
if((string) $old !== (string) $v || $old === null) {
$a[$k] = $v;
$this->_data[$k] = $v;
}
}
return $a;
}
/**
* count
* this class implements countable interface
*
* @return integer the number of columns in this record
*/
public function count() {
return count($this->_data);
}
/**
* alias for count()
*
* @return integer the number of columns in this record
*/
public function columnCount() {
return $this->count();
}
/**
* toArray
* returns the record as an array
*
* @return array
*/
public function toArray() {
$a = array();
foreach($this as $column => $value) {
$a[$column] = $value;
}
if($this->_table->getIdentifierType() == Doctrine_Identifier::AUTO_INCREMENT) {
$i = $this->_table->getIdentifier();
$a[$i] = $this->getIncremented();
}
return $a;
}
/**
* exists
* returns true if this record is persistent, otherwise false
*
* @return boolean
*/
public function exists() {
return ($this->_state !== Doctrine_Record::STATE_TCLEAN &&
$this->_state !== Doctrine_Record::STATE_TDIRTY);
}
/**
* method for checking existence of properties and Doctrine_Record references
* @param mixed $name name of the property or reference
* @return boolean
*/
public function hasRelation($name) {
if(isset($this->_data[$name]) || isset($this->_id[$name]))
return true;
return $this->_table->hasRelation($name);
}
/**
* getIterator
* @return Doctrine_Record_Iterator a Doctrine_Record_Iterator that iterates through the data
*/
public function getIterator() {
return new Doctrine_Record_Iterator($this);
}
/**
* getOriginals
* returns an original collection of related component
*
* @return Doctrine_Collection|false
*/
public function obtainOriginals($name) {
if(isset($this->originals[$name]))
return $this->originals[$name];
return false;
}
/**
* deletes this data access object and all the related composites
* this operation is isolated by a transaction
*
* this event can be listened by the onPreDelete and onDelete listeners
*
* @return boolean true on success, false on failure
*/
public function delete(Doctrine_Connection $conn = null) {
if ($conn == null) {
$conn = $this->_table->getConnection();
}
return $conn->delete($this);
}
/**
* copy
* returns a copy of this object
*
* @return Doctrine_Record
*/
public function copy() {
$ret = $this->_table->create($this->_data);
$modified = array();
foreach($this->_data as $key => $val)
if (!($val instanceof Doctrine_Null))
$ret->_modified[] = $key;
return $ret;
}
/**
* assignIdentifier
*
* @param integer $id
* @return void
*/
final public function assignIdentifier($id = false) {
if($id === false) {
$this->_id = array();
$this->cleanData();
$this->_state = Doctrine_Record::STATE_TCLEAN;
$this->_modified = array();
} elseif($id === true) {
$this->prepareIdentifiers(false);
$this->_state = Doctrine_Record::STATE_CLEAN;
$this->_modified = array();
} else {
$name = $this->_table->getIdentifier();
$this->_id[$name] = $id;
$this->_state = Doctrine_Record::STATE_CLEAN;
$this->_modified = array();
}
}
/**
* assignOriginals
*
* @param string $alias
* @param Doctrine_Collection $coll
* @return void
*/
public function assignOriginals($alias, Doctrine_Collection $coll) {
$this->originals[$alias] = $coll;
}
/**
* returns the primary keys of this object
*
* @return array
*/
final public function obtainIdentifier() {
return $this->_id;
}
/**
* returns the value of autoincremented primary key of this object (if any)
*
* @return integer
*/
final public function getIncremented() {
$id = current($this->_id);
if($id === false)
return null;
return $id;
}
/**
* getLast
* this method is used internally be Doctrine_Query
* it is needed to provide compatibility between
* records and collections
*
* @return Doctrine_Record
*/
public function getLast() {
return $this;
}
/**
* hasRefence
* @param string $name
* @return boolean
*/
public function hasReference($name) {
return isset($this->references[$name]);
}
/**
* obtainReference
*
* @param string $name
* @throws Doctrine_Record_Exception if trying to get an unknown related component
*/
public function obtainReference($name) {
if(isset($this->references[$name]))
return $this->references[$name];
throw new Doctrine_Record_Exception("Unknown reference $name");
}
/**
* initalizes a one-to-many / many-to-many relation
*
* @param Doctrine_Collection $coll
* @param Doctrine_Relation $connector
* @return boolean
*/
public function initReference(Doctrine_Collection $coll, Doctrine_Relation $connector) {
$alias = $connector->getAlias();
if(isset($this->references[$alias]))
return false;
if( ! $connector->isOneToOne()) {
if( ! ($connector instanceof Doctrine_Relation_Association))
$coll->setReference($this, $connector);
$this->references[$alias] = $coll;
$this->originals[$alias] = clone $coll;
return true;
}
return false;
}
public function lazyInitRelated(Doctrine_Collection $coll, Doctrine_Relation $connector) {
}
/**
* addReference
* @param Doctrine_Record $record
* @param mixed $key
* @return void
*/
public function addReference(Doctrine_Record $record, Doctrine_Relation $connector, $key = null) {
$alias = $connector->getAlias();
$this->references[$alias]->add($record, $key);
$this->originals[$alias]->add($record, $key);
}
/**
* getReferences
* @return array all references
*/
public function getReferences() {
return $this->references;
}
/**
* setRelated
*
* @param string $alias
* @param Doctrine_Access $coll
*/
final public function setRelated($alias, Doctrine_Access $coll) {
$this->references[$alias] = $coll;
$this->originals[$alias] = $coll;
}
/**
* loadReference
* loads a related component
*
* @throws Doctrine_Table_Exception if trying to load an unknown related component
* @param string $name
* @return void
*/
final public function loadReference($name) {
$fk = $this->_table->getRelation($name);
if($fk->isOneToOne()) {
$this->references[$name] = $fk->fetchRelatedFor($this);
} else {
$coll = $fk->fetchRelatedFor($this);
$this->references[$name] = $coll;
$this->originals[$name] = clone $coll;
}
}
/**
* binds One-to-One composite relation
*
* @param string $objTableName
* @param string $fkField
* @return void
*/
final public function ownsOne($componentName, $foreignKey, $localKey = null) {
$this->_table->bind($componentName, $foreignKey, Doctrine_Relation::ONE_COMPOSITE, $localKey);
}
/**
* binds One-to-Many composite relation
*
* @param string $objTableName
* @param string $fkField
* @return void
*/
final public function ownsMany($componentName,$foreignKey, $localKey = null) {
$this->_table->bind($componentName, $foreignKey, Doctrine_Relation::MANY_COMPOSITE, $localKey);
}
/**
* binds One-to-One aggregate relation
*
* @param string $objTableName
* @param string $fkField
* @return void
*/
final public function hasOne($componentName,$foreignKey, $localKey = null) {
$this->_table->bind($componentName, $foreignKey, Doctrine_Relation::ONE_AGGREGATE, $localKey);
}
/**
* binds One-to-Many aggregate relation
*
* @param string $objTableName
* @param string $fkField
* @return void
*/
final public function hasMany($componentName,$foreignKey, $localKey = null) {
$this->_table->bind($componentName, $foreignKey, Doctrine_Relation::MANY_AGGREGATE, $localKey);
}
/**
* setPrimaryKey
* @param mixed $key
*/
final public function setPrimaryKey($key) {
$this->_table->setPrimaryKey($key);
}
/**
* hasColumn
* sets a column definition
*
* @param string $name
* @param string $type
* @param integer $length
* @param mixed $options
* @return void
*/
final public function hasColumn($name, $type, $length = 2147483647, $options = "") {
$this->_table->setColumn($name, $type, $length, $options);
}
/**
* countRelated
*
* @param string $name the name of the related component
* @return integer
*/
public function countRelated($name) {
$rel = $this->_table->getRelation($name);
$componentName = $rel->getTable()->getComponentName();
$alias = $rel->getTable()->getAlias(get_class($this));
$query = new Doctrine_Query();
$query->from($componentName. '(' . 'COUNT(1)' . ')')->where($componentName. '.' .$alias. '.' . $this->getTable()->getIdentifier(). ' = ?');
$array = $query->execute(array($this->getIncremented()));
return $array[0]['COUNT(1)'];
}
/**
* merge
* merges this record with an array of values
*
* @param array $values
* @return void
*/
public function merge(array $values) {
foreach($this->_table->getColumnNames() as $value) {
try {
if(isset($values[$value]))
$this->set($value, $values[$value]);
} catch(Exception $e) {
// silence all exceptions
}
}
}
public function setAttribute($attr, $value) {
$this->_table->setAttribute($attr, $value);
}
public function setTableName($tableName) {
$this->_table->setTableName($tableName);
}
public function setInheritanceMap($map) {
$this->_table->setOption('inheritanceMap', $map);
}
public function setEnumValues($column, $values) {
$this->_table->setEnumValues($column, $values);
}
public function option($name, $value = null) {
if($value == null)
$this->_table->getOption($name);
else
$this->_table->setOption($name, $value);
}
public function hasIndex($name ) {
}
public function actsAsTree($treeImplName, $args = array()) {
$this->_table->setTree($treeImplName, $args);
}
/**
* addListener
*
* @param Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener
* @return Doctrine_Db
*/
public function addListener($listener, $name = null) {
$this->_table->addListener($listener, $name = null);
return $this;
}
/**
* getListener
*
* @return Doctrine_Db_EventListener_Interface|Doctrine_Overloadable
*/
public function getListener() {
return $this->_table->getListener();
}
/**
* setListener
*
* @param Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener
* @return Doctrine_Db
*/
public function setListener($listener) {
$this->_table->setListener($listener);
return $this;
}
/**
* call
*
* @param string|array $callback valid callback
* @param string $column column name
* @param mixed arg1 ... argN optional callback arguments
* @return Doctrine_Record
*/
public function call($callback, $column) {
$args = func_get_args();
array_shift($args);
if(isset($args[0])) {
$column = $args[0];
$args[0] = $this->get($column);
$newvalue = call_user_func_array($callback, $args);
$this->_data[$column] = $newvalue;
}
return $this;
}
/**
* returns a string representation of this object
*/
public function __toString() {
return Doctrine_Lib::getRecordAsString($this);
}
/**
* getter for node assciated with this record
*
* @return mixed if tree returns Doctrine_Node otherwise returns false
*/
public function getNode() {
if(!$this->_table->isTree())
return false;
if(!isset($this->_node))
$this->_node = Doctrine_Node::factory($this, $this->getTable()->getTreeImplName(), $this->getTable()->getTreeOptions());
return $this->_node;
}
/**
* used to delete node from tree - MUST BE USE TO DELETE RECORD IF TABLE ACTS AS TREE
*
*/
public function deleteNode() {
$this->getNode()->delete();
}
}
<?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_Table represents a database table
* each Doctrine_Table holds the information of foreignKeys and associations
*
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @package Doctrine
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @version $Revision$
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
*/
class Doctrine_Table extends Doctrine_Configurable implements Countable {
/**
* @var array $data temporary data which is then loaded into Doctrine_Record::$data
*/
private $data = array();
/**
* @var array $relations an array containing all the Doctrine_Relation objects for this table
*/
private $relations = array();
/**
* @var array $primaryKeys an array containing all primary key column names
*/
private $primaryKeys = array();
/**
* @var mixed $identifier
*/
private $identifier;
/**
* @see Doctrine_Identifier constants
* @var integer $identifierType the type of identifier this table uses
*/
private $identifierType;
/**
* @var string $query cached simple query
*/
private $query;
/**
* @var Doctrine_Connection $conn Doctrine_Connection object that created this table
*/
private $conn;
/**
* @var string $name
*/
private $name;
/**
* @var array $identityMap first level cache
*/
private $identityMap = array();
/**
* @var Doctrine_Table_Repository $repository record repository
*/
private $repository;
/**
* @var array $columns an array of column definitions,
* keys as column names and values as column definitions
*
* the value array has three values:
*
* the column type, eg. 'integer'
* the column length, eg. 11
* the column options/constraints/validators. eg array('notnull' => true)
*
* so the full columns array might look something like the following:
* array(
* 'name' => array('string', 20, array('notnull' => true, 'default' => 'someone')),
* 'age' => array('integer', 11, array('notnull' => true))
* )
*/
protected $columns = array();
/**
* @var array $bound bound relations
*/
private $bound = array();
/**
* @var array $boundAliases bound relation aliases
*/
private $boundAliases = array();
/**
* @var integer $columnCount cached column count, Doctrine_Record uses this column count in when
* determining its state
*/
private $columnCount;
/**
* @var array $parents the parent classes of this component
*/
private $parents = array();
/**
* @var boolean $hasDefaultValues whether or not this table has default values
*/
private $hasDefaultValues;
/**
* @var array $options an array containing all options
*
* -- name name of the component, for example component name of the GroupTable is 'Group'
*
* -- tableName database table name, in most cases this is the same as component name but in some cases
* where one-table-multi-class inheritance is used this will be the name of the inherited table
*
* -- sequenceName Some databases need sequences instead of auto incrementation primary keys,
* you can set specific sequence for your table by calling setOption('sequenceName', $seqName)
* where $seqName is the name of the desired sequence
*
* -- enumMap enum value arrays
*
* -- inheritanceMap inheritanceMap is used for inheritance mapping, keys representing columns and values
* the column values that should correspond to child classes
*/
protected $options = array('name' => null,
'tableName' => null,
'sequenceName' => null,
'inheritanceMap' => array(),
'enumMap' => array(),
);
/**
* the constructor
* @throws Doctrine_Connection_Exception if there are no opened connections
* @throws Doctrine_Table_Exception if there is already an instance of this table
* @return void
*/
public function __construct($name, Doctrine_Connection $conn) {
$this->conn = $conn;
$this->setParent($this->conn);
$this->options['name'] = $name;
if( ! class_exists($name) || empty($name))
throw new Doctrine_Exception("Couldn't find class $name");
$record = new $name($this);
$names = array();
$class = $name;
// get parent classes
do {
if($class == "Doctrine_Record")
break;
$name = $class;
$names[] = $name;
} while($class = get_parent_class($class));
// reverse names
$names = array_reverse($names);
// create database table
if(method_exists($record, 'setTableDefinition')) {
$record->setTableDefinition();
$this->columnCount = count($this->columns);
if(isset($this->columns)) {
// get the declaring class of setTableDefinition method
$method = new ReflectionMethod($this->options['name'], 'setTableDefinition');
$class = $method->getDeclaringClass();
if( ! isset($this->options['tableName']))
$this->options['tableName'] = Doctrine::tableize($class->getName());
switch(count($this->primaryKeys)):
case 0:
$this->columns = array_merge(array('id' =>
array('integer',
20,
array('autoincrement' => true,
'primary' => true
)
)
), $this->columns);
$this->primaryKeys[] = 'id';
$this->identifier = 'id';
$this->identifierType = Doctrine_Identifier::AUTO_INCREMENT;
$this->columnCount++;
break;
default:
if(count($this->primaryKeys) > 1) {
$this->identifier = $this->primaryKeys;
$this->identifierType = Doctrine_Identifier::COMPOSITE;
} else {
foreach($this->primaryKeys as $pk) {
$e = $this->columns[$pk][2];
$found = false;
foreach($e as $option => $value) {
if($found)
break;
$e2 = explode(":",$option);
switch(strtolower($e2[0])):
case "autoincrement":
$this->identifierType = Doctrine_Identifier::AUTO_INCREMENT;
$found = true;
break;
case "seq":
$this->identifierType = Doctrine_Identifier::SEQUENCE;
$found = true;
break;
endswitch;
}
if( ! isset($this->identifierType))
$this->identifierType = Doctrine_Identifier::NORMAL;
$this->identifier = $pk;
}
}
endswitch;
if($this->getAttribute(Doctrine::ATTR_CREATE_TABLES)) {
if(Doctrine::isValidClassname($class->getName())) {
//$dict = new Doctrine_DataDict($this->getConnection()->getDBH());
try {
$columns = array();
foreach($this->columns as $name => $column) {
$definition = $column[2];
$definition['type'] = $column[0];
$definition['length'] = $column[1];
if($definition['type'] == 'enum' && isset($definition['default']))
$definition['default'] = $this->enumIndex($name, $definition['default']);
if($definition['type'] == 'boolean' && isset($definition['default']))
$definition['default'] = (int) $definition['default'];
$columns[$name] = $definition;
}
$this->conn->export->createTable($this->options['tableName'], $columns);
} catch(Exception $e) {
}
}
}
}
} else {
throw new Doctrine_Table_Exception("Class '$name' has no table definition.");
}
// perform tree setup
if($this->isTree())
$this->getTree()->setUp();
$record->setUp();
// save parents
array_pop($names);
$this->parents = $names;
$this->query = "SELECT ".implode(", ",array_keys($this->columns))." FROM ".$this->getTableName();
// check if an instance of this table is already initialized
if( ! $this->conn->addTable($this))
throw new Doctrine_Table_Exception();
$this->repository = new Doctrine_Table_Repository($this);
}
/**
* createQuery
* creates a new Doctrine_Query object and adds the component name
* of this table as the query 'from' part
*
* @return Doctrine_Query
*/
public function createQuery() {
return Doctrine_Query::create()->from($this->getComponentName());
}
/**
* getRepository
*
* @return Doctrine_Table_Repository
*/
public function getRepository() {
return $this->repository;
}
public function setOption($name, $value) {
switch($name) {
case 'name':
case 'tableName':
break;
case 'enumMap':
case 'inheritanceMap':
if( ! is_array($value))
throw new Doctrine_Table_Exception($name.' should be an array.');
break;
}
$this->options[$name] = $value;
}
public function usesInheritanceMap() {
return ( ! empty($this->options['inheritanceMap']));
}
public function getOption($name) {
if(isset($this->options[$name]))
return $this->options[$name];
return null;
}
/**
* setColumn
* @param string $name
* @param string $type
* @param integer $length
* @param mixed $options
* @return void
*/
final public function setColumn($name, $type, $length, $options = array()) {
if(is_string($options))
$options = explode('|', $options);
foreach($options as $k => $option) {
if(is_numeric($k)) {
if( ! empty($option))
$options[$option] = true;
unset($options[$k]);
}
}
$name = strtolower($name);
$this->columns[$name] = array($type,$length,$options);
if(isset($options['primary'])) {
$this->primaryKeys[] = $name;
}
if(isset($options['default'])) {
$this->hasDefaultValues = true;
}
}
/**
* hasDefaultValues
* returns true if this table has default values, otherwise false
*
* @return boolean
*/
public function hasDefaultValues() {
return $this->hasDefaultValues;
}
/**
* getDefaultValueOf
* returns the default value(if any) for given column
*
* @param string $column
* @return mixed
*/
public function getDefaultValueOf($column) {
$column = strtolower($column);
if( ! isset($this->columns[$column]))
throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$column." doesn't exist.");
if(isset($this->columns[$column][2]['default'])) {
return $this->columns[$column][2]['default'];
} else
return null;
}
/**
* @return mixed
*/
final public function getIdentifier() {
return $this->identifier;
}
/**
* @return integer
*/
final public function getIdentifierType() {
return $this->identifierType;
}
/**
* hasColumn
* @return boolean
*/
final public function hasColumn($name) {
return isset($this->columns[$name]);
}
/**
* @param mixed $key
* @return void
*/
final public function setPrimaryKey($key) {
switch(gettype($key)):
case "array":
$this->primaryKeys = array_values($key);
break;
case "string":
$this->primaryKeys[] = $key;
break;
endswitch;
}
/**
* returns all primary keys
* @return array
*/
final public function getPrimaryKeys() {
return $this->primaryKeys;
}
/**
* @return boolean
*/
final public function hasPrimaryKey($key) {
return in_array($key,$this->primaryKeys);
}
/**
* @param $sequence
* @return void
*/
final public function setSequenceName($sequence) {
$this->options['sequenceName'] = $sequence;
}
/**
* @return string sequence name
*/
final public function getSequenceName() {
return $this->options['sequenceName'];
}
/**
* getParents
*/
final public function getParents() {
return $this->parents;
}
/**
* @return boolean
*/
final public function hasInheritanceMap() {
return (empty($this->options['inheritanceMap']));
}
/**
* @return array inheritance map (array keys as fields)
*/
final public function getInheritanceMap() {
return $this->options['inheritanceMap'];
}
/**
* return all composite paths in the form [component1].[component2]. . .[componentN]
* @return array
*/
final public function getCompositePaths() {
$array = array();
$name = $this->getComponentName();
foreach($this->bound as $k=>$a) {
try {
$fk = $this->getRelation($k);
switch($fk->getType()):
case Doctrine_Relation::ONE_COMPOSITE:
case Doctrine_Relation::MANY_COMPOSITE:
$n = $fk->getTable()->getComponentName();
$array[] = $name.".".$n;
$e = $fk->getTable()->getCompositePaths();
if( ! empty($e)) {
foreach($e as $name) {
$array[] = $name.".".$n.".".$name;
}
}
break;
endswitch;
} catch(Doctrine_Table_Exception $e) {
}
}
return $array;
}
/**
* returns all bound relations
*
* @return array
*/
public function getBounds() {
return $this->bound;
}
/**
* returns a bound relation array
*
* @param string $name
* @return array
*/
public function getBound($name) {
if( ! isset($this->bound[$name]))
throw new Doctrine_Table_Exception('Unknown bound '.$name);
return $this->bound[$name];
}
/**
* returns a bound relation array
*
* @param string $name
* @return array
*/
public function getBoundForName($name, $component) {
foreach($this->bound as $k => $bound) {
$e = explode('.', $bound[0]);
if($bound[3] == $name && $e[0] == $component) {
return $this->bound[$k];
}
}
throw new Doctrine_Table_Exception('Unknown bound '.$name);
}
/**
* returns the alias for given component name
*
* @param string $name
* @return string
*/
public function getAlias($name) {
if(isset($this->boundAliases[$name]))
return $this->boundAliases[$name];
return $name;
}
/**
* returns component name for given alias
*
* @param string $alias
* @return string
*/
public function getAliasName($alias) {
if($name = array_search($alias, $this->boundAliases))
return $name;
return $alias;
}
/**
* unbinds all relations
*
* @return void
*/
public function unbindAll() {
$this->bound = array();
$this->relations = array();
$this->boundAliases = array();
}
/**
* unbinds a relation
* returns true on success, false on failure
*
* @param $name
* @return boolean
*/
public function unbind($name) {
if( ! isset($this->bound[$name]))
return false;
unset($this->bound[$name]);
if(isset($this->relations[$name]))
unset($this->relations[$name]);
if(isset($this->boundAliases[$name]))
unset($this->boundAliases[$name]);
return true;
}
/**
* binds a relation
*
* @param string $name
* @param string $field
* @return void
*/
public function bind($name, $field, $type, $localKey) {
if(isset($this->relations[$name]))
unset($this->relations[$name]);
$e = explode(" as ",$name);
$name = $e[0];
if(isset($e[1])) {
$alias = $e[1];
$this->boundAliases[$name] = $alias;
} else
$alias = $name;
$this->bound[$alias] = array($field, $type, $localKey, $name);
}
/**
* getComponentName
* @return string the component name
*/
public function getComponentName() {
return $this->options['name'];
}
/**
* @return Doctrine_Connection
*/
public function getConnection() {
return $this->conn;
}
/**
* hasRelatedComponent
* @return boolean
*/
final public function hasRelatedComponent($name, $component) {
return (strpos($this->bound[$name][0], $component.'.') !== false);
}
/**
* @param string $name component name of which a foreign key object is bound
* @return boolean
*/
final public function hasRelation($name) {
if(isset($this->bound[$name]))
return true;
foreach($this->bound as $k=>$v)
{
if($this->hasRelatedComponent($k, $name))
return true;
}
return false;
}
/**
* getRelation
*
* @param string $name component name of which a foreign key object is bound
* @return Doctrine_Relation
*/
final public function getRelation($name, $recursive = true) {
$original = $name;
if(isset($this->relations[$name]))
return $this->relations[$name];
if(isset($this->bound[$name])) {
$type = $this->bound[$name][1];
$local = $this->bound[$name][2];
list($component, $foreign) = explode(".",$this->bound[$name][0]);
$alias = $name;
$name = $this->bound[$alias][3];
$table = $this->conn->getTable($name);
if($component == $this->options['name'] || in_array($component, $this->parents)) {
// ONE-TO-ONE
if($type == Doctrine_Relation::ONE_COMPOSITE ||
$type == Doctrine_Relation::ONE_AGGREGATE) {
if( ! isset($local))
$local = $table->getIdentifier();
$relation = new Doctrine_Relation_LocalKey($table, $foreign, $local, $type, $alias);
} else
$relation = new Doctrine_Relation_ForeignKey($table, $foreign, $local, $type, $alias);
} elseif($component == $name ||
($component == $alias)) { // && ($name == $this->options['name'] || in_array($name,$this->parents))
if( ! isset($local))
$local = $this->identifier;
// ONE-TO-MANY or ONE-TO-ONE
$relation = new Doctrine_Relation_ForeignKey($table, $local, $foreign, $type, $alias);
} else {
// MANY-TO-MANY
// only aggregate relations allowed
if($type != Doctrine_Relation::MANY_AGGREGATE)
throw new Doctrine_Table_Exception("Only aggregate relations are allowed for many-to-many relations");
$classes = array_merge($this->parents, array($this->options['name']));
foreach(array_reverse($classes) as $class) {
try {
$bound = $table->getBoundForName($class, $component);
break;
} catch(Doctrine_Table_Exception $exc) { }
}
if( ! isset($bound))
throw new Doctrine_Table_Exception("Couldn't map many-to-many relation for "
. $this->options['name'] . " and $name. Components use different join tables.");
if( ! isset($local))
$local = $this->identifier;
$e2 = explode('.', $bound[0]);
$fields = explode('-', $e2[1]);
if($e2[0] != $component)
throw new Doctrine_Table_Exception($e2[0] . ' doesn\'t match ' . $component);
$associationTable = $this->conn->getTable($e2[0]);
if(count($fields) > 1) {
// SELF-REFERENCING THROUGH JOIN TABLE
$this->relations[$e2[0]] = new Doctrine_Relation_ForeignKey($associationTable, $local, $fields[0],Doctrine_Relation::MANY_COMPOSITE, $e2[0]);
$relation = new Doctrine_Relation_Association_Self($table, $associationTable, $fields[0], $fields[1], $type, $alias);
} else {
// auto initialize a new one-to-one relationship for association table
$associationTable->bind($this->getComponentName(), $associationTable->getComponentName(). '.' .$e2[1], Doctrine_Relation::ONE_AGGREGATE, $this->getIdentifier());
$associationTable->bind($table->getComponentName(), $associationTable->getComponentName(). '.' .$foreign, Doctrine_Relation::ONE_AGGREGATE, $table->getIdentifier());
// NORMAL MANY-TO-MANY RELATIONSHIP
$this->relations[$e2[0]] = new Doctrine_Relation_ForeignKey($associationTable, $local, $e2[1], Doctrine_Relation::MANY_COMPOSITE, $e2[0]);
$relation = new Doctrine_Relation_Association($table, $associationTable, $e2[1], $foreign, $type, $alias);
}
}
$this->relations[$alias] = $relation;
return $this->relations[$alias];
}
// load all relations
$this->getRelations();
if($recursive) {
return $this->getRelation($original, false);
} else {
throw new Doctrine_Table_Exception($this->options['name'] . " doesn't have a relation to " . $original);
}
}
/**
* returns an array containing all foreign key objects
*
* @return array
*/
final public function getRelations() {
$a = array();
foreach($this->bound as $k=>$v) {
$this->getRelation($k);
}
return $this->relations;
}
/**
* sets the database table name
*
* @param string $name database table name
* @return void
*/
final public function setTableName($name) {
$this->options['tableName'] = $name;
}
/**
* returns the database table name
*
* @return string
*/
final public function getTableName() {
return $this->options['tableName'];
}
/**
* create
* creates a new record
*
* @param $array an array where keys are field names and values representing field values
* @return Doctrine_Record
*/
public function create(array $array = array()) {
$this->data = $array;
$record = new $this->options['name']($this, true);
$this->data = array();
return $record;
}
/**
* finds a record by its identifier
*
* @param $id database row id
* @return Doctrine_Record|false a record for given database identifier
*/
public function find($id) {
if($id !== null) {
if( ! is_array($id))
$id = array($id);
else
$id = array_values($id);
$query = $this->query." WHERE ".implode(" = ? AND ",$this->primaryKeys)." = ?";
$query = $this->applyInheritance($query);
$params = array_merge($id, array_values($this->options['inheritanceMap']));
$stmt = $this->conn->execute($query,$params);
$this->data = $stmt->fetch(PDO::FETCH_ASSOC);
if($this->data === false)
return false;
return $this->getRecord();
}
return false;
}
/**
* applyInheritance
* @param $where query where part to be modified
* @return string query where part with column aggregation inheritance added
*/
final public function applyInheritance($where) {
if( ! empty($this->options['inheritanceMap'])) {
$a = array();
foreach($this->options['inheritanceMap'] as $field => $value) {
$a[] = $field." = ?";
}
$i = implode(" AND ",$a);
$where .= " AND $i";
}
return $where;
}
/**
* findAll
* returns a collection of records
*
* @return Doctrine_Collection
*/
public function findAll() {
$graph = new Doctrine_Query($this->conn);
$users = $graph->query("FROM ".$this->options['name']);
return $users;
}
/**
* findByDql
* finds records with given DQL where clause
* returns a collection of records
*
* @param string $dql DQL after WHERE clause
* @param array $params query parameters
* @return Doctrine_Collection
*/
public function findBySql($dql, array $params = array()) {
$q = new Doctrine_Query($this->conn);
$users = $q->query("FROM ".$this->options['name']." WHERE ".$dql, $params);
return $users;
}
public function findByDql($dql, array $params = array()) {
return $this->findBySql($dql, $params);
}
/**
* clear
* clears the first level cache (identityMap)
*
* @return void
*/
public function clear() {
$this->identityMap = array();
}
/**
* getRecord
* first checks if record exists in identityMap, if not
* returns a new record
*
* @return Doctrine_Record
*/
public function getRecord() {
$this->data = array_change_key_case($this->data, CASE_LOWER);
$key = $this->getIdentifier();
if( ! is_array($key))
$key = array($key);
foreach($key as $k) {
if( ! isset($this->data[$k]))
throw new Doctrine_Exception("Primary key value for $k wasn't found");
$id[] = $this->data[$k];
}
$id = implode(' ', $id);
if(isset($this->identityMap[$id]))
$record = $this->identityMap[$id];
else {
$record = new $this->options['name']($this);
$this->identityMap[$id] = $record;
}
$this->data = array();
return $record;
}
/**
* @param $id database row id
* @throws Doctrine_Find_Exception
*/
final public function getProxy($id = null) {
if($id !== null) {
$query = "SELECT ".implode(", ",$this->primaryKeys)." FROM ".$this->getTableName()." WHERE ".implode(" = ? && ",$this->primaryKeys)." = ?";
$query = $this->applyInheritance($query);
$params = array_merge(array($id), array_values($this->options['inheritanceMap']));
$this->data = $this->conn->execute($query,$params)->fetch(PDO::FETCH_ASSOC);
if($this->data === false)
return false;
}
return $this->getRecord();
}
/**
* count
*
* @return integer
*/
public function count() {
$a = $this->conn->getDBH()->query("SELECT COUNT(1) FROM ".$this->options['tableName'])->fetch(PDO::FETCH_NUM);
return current($a);
}
/**
* @return Doctrine_Query a Doctrine_Query object
*/
public function getQueryObject() {
$graph = new Doctrine_Query($this->getConnection());
$graph->load($this->getComponentName());
return $graph;
}
/**
* execute
* @param string $query
* @param array $array
* @param integer $limit
* @param integer $offset
*/
public function execute($query, array $array = array(), $limit = null, $offset = null) {
$coll = new Doctrine_Collection($this);
$query = $this->conn->modifyLimitQuery($query,$limit,$offset);
if( ! empty($array)) {
$stmt = $this->conn->getDBH()->prepare($query);
$stmt->execute($array);
} else {
$stmt = $this->conn->getDBH()->query($query);
}
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->closeCursor();
foreach($data as $row) {
$this->data = $row;
$record = $this->getRecord();
$coll->add($record);
}
return $coll;
}
/**
* sets enumerated value array for given field
*
* @param string $field
* @param array $values
* @return void
*/
final public function setEnumValues($field, array $values) {
$this->options['enumMap'][strtolower($field)] = $values;
}
/**
* @param string $field
* @return array
*/
final public function getEnumValues($field) {
if(isset($this->options['enumMap'][$field]))
return $this->options['enumMap'][$field];
else
return array();
}
/**
* enumValue
*
* @param string $field
* @param integer $index
* @return mixed
*/
final public function enumValue($field, $index) {
if ($index instanceof Doctrine_Null)
return $index;
return isset($this->options['enumMap'][$field][$index]) ? $this->options['enumMap'][$field][$index] : $index;
}
/**
* invokeSet
*
* @param mixed $value
*/
public function invokeSet(Doctrine_Record $record, $name, $value) {
if( ! ($this->getAttribute(Doctrine::ATTR_ACCESSORS) & Doctrine::ACCESSOR_SET))
return $value;
$prefix = $this->getAttribute(Doctrine::ATTR_ACCESSOR_PREFIX_SET);
if (!$prefix)
$prefix = 'set';
$method = $prefix . $name;
if(method_exists($record, $method)) {
return $record->$method($value);
}
return $value;
}
/**
* invokeGet
*
* @param mixed $value
*/
public function invokeGet(Doctrine_Record $record, $name, $value) {
if( ! ($this->getAttribute(Doctrine::ATTR_ACCESSORS) & Doctrine::ACCESSOR_GET))
return $value;
$prefix = $this->getAttribute(Doctrine::ATTR_ACCESSOR_PREFIX_GET);
if (!$prefix)
$prefix = 'get';
$method = $prefix . $name;
if(method_exists($record, $method)) {
return $record->$method($value);
}
return $value;
}
/**
* enumIndex
*
* @param string $field
* @param mixed $value
* @return mixed
*/
final public function enumIndex($field, $value) {
if( ! isset($this->options['enumMap'][$field]))
$values = array();
else
$values = $this->options['enumMap'][$field];
return array_search($value, $values);
}
/**
* getDefinitionOf
*
* @return string ValueWrapper class name on success, false on failure
*/
public function getValueWrapperOf($column) {
if(isset($this->columns[$column][2]['wrapper']))
return $this->columns[$column][2]['wrapper'];
return false;
}
/**
* getColumnCount
*
* @return integer the number of columns in this table
*/
final public function getColumnCount() {
return $this->columnCount;
}
/**
* returns all columns and their definitions
*
* @return array
*/
final public function getColumns() {
return $this->columns;
}
/**
* returns an array containing all the column names
*
* @return array
*/
public function getColumnNames() {
return array_keys($this->columns);
}
/**
* getDefinitionOf
*
* @return mixed array on success, false on failure
*/
public function getDefinitionOf($column) {
if(isset($this->columns[$column]))
return $this->columns[$column];
return false;
}
/**
* getTypeOf
*
* @return mixed string on success, false on failure
*/
public function getTypeOf($column) {
if(isset($this->columns[$column]))
return $this->columns[$column][0];
return false;
}
/**
* setData
* doctrine uses this function internally
* users are strongly discouraged to use this function
*
* @param array $data internal data
* @return void
*/
public function setData(array $data) {
$this->data = $data;
}
/**
* returns the maximum primary key value
*
* @return integer
*/
final public function getMaxIdentifier() {
$sql = "SELECT MAX(".$this->getIdentifier().") FROM ".$this->getTableName();
$stmt = $this->conn->getDBH()->query($sql);
$data = $stmt->fetch(PDO::FETCH_NUM);
return isset($data[0])?$data[0]:1;
}
/**
* returns simple cached query
*
* @return string
*/
final public function getQuery() {
return $this->query;
}
/**
* returns internal data, used by Doctrine_Record instances
* when retrieving data from database
*
* @return array
*/
final public function getData() {
return $this->data;
}
/**
* returns a string representation of this object
*
* @return string
*/
public function __toString() {
return Doctrine_Lib::getTableAsString($this);
}
/**
* returns a string representation of this object
*
* @return string
*/
public function setTree($implName, $options) {
$this->_treeImplName = $implName;
$this->_treeOptions = $options;
$this->_tree = Doctrine_Tree::factory($this, $this->_treeImplName, $this->_treeOptions);
// set the table definition for the given tree implementation
$this->_tree->setTableDefinition();
}
/**
* getter for associated tree
*
* @return mixed if tree return instance of Doctrine_Tree, otherwise returns false
*/
public function getTree() {
return isset($this->_treeImplName) ? $this->_tree : false;
}
/**
* determine if table acts as tree
*
* @return mixed if tree return instance of Doctrine_Tree, otherwise returns false
*/
public function isTree() {
return !is_null($this->_treeImplName) ? true : false;
}
/**
* getter for tree implementation name
*
* @return string tree implementation name (NestedSet | AdjacencyList | MaterializedPath)
*/
public function getTreeImplName() {
return $this->_treeImplName;
}
/**
* getter for tree options
*
* @return array tree options
*/
public function getTreeOptions() {
return $this->_treeOptions;
}
}
......@@ -21,23 +21,69 @@
/**
* Doctrine_Tree
*
* the purpose of Doctrine_Tree is to provide tree access
* functionality for all records extending it
*
* @package Doctrine ORM
* @url www.phpdoctrine.com
* @license LGPL
* @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
*/
abstract class Doctrine_Tree extends Doctrine_Record {
protected $table;
abstract public function getLeafNodes();
/**
* @param array $options
*/
protected $options;
abstract public function getPath();
/**
* contructor, creates tree with reference to table and any options
*
* @param object $table instance of Doctrine_Table
* @param array $options options
*/
public function __construct($table, $options)
{
$this->table = $table;
$this->options = $options;
}
abstract public function getDepth();
/**
* Used to define table attributes required for the given implementation
*
*/
public function setTableDefinition()
{
throw new Doctrine_Tree_Exception('Table attributes have not been defined for this Tree implementation.');
}
abstract public function removeNode();
/**
* this method is used for setting up relations and attributes and should be used by specific implementations
*
*/
public function setUp()
{
abstract public function addNode();
}
}
/**
* 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
*/
public static function factory( &$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);
}
} // END class
\ No newline at end of file
<?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 { }
<?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 { }
<?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();
/**
* 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());
}
\ No newline at end of file
<?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 { }
\ No newline at end of file
<?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_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$
* @author Joe Simms <joe.simms@websites4.com>
*/
class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Interface
{
/**
* used to define table attributes required for the NestetSet implementation
* adds lft and rgt columns for corresponding left and right values
*
*/
public function setTableDefinition()
{
$this->table->setColumn("lft","integer",11);
$this->table->setColumn("rgt","integer",11);
}
/**
* 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();
}
$record->set('lft', '1');
$record->set('rgt', '2');
$record->save();
return $record;
}
/**
* returns root node
*
* @return object $record instance of Doctrine_Record
*/
public function findRoot()
{
$q = $this->table->createQuery();
$root = $q->where('lft = ?', 1)
->execute()->getFirst();
// if no record is returned, create record
if(!$root)
{
$root = $this->table->create();
}
// set level to prevent additional query to determine level
$root->getNode()->setLevel(0);
return $root;
}
/**
* optimised method to returns iterator for traversal of the entire tree from root
*
* @param array $options options
* @return object $iterator instance of Doctrine_Node_NestedSet_PreOrderIterator
*/
public function fetchTree($options = array())
{
// fetch tree
$q = $this->table->createQuery();
$tree = $q->where('lft >= ?', 1)
->orderBy('lft asc')
->execute();
$root = $tree->getFirst();
// if no record is returned, create record
if(!$root)
{
$root = $this->table->create();
}
if($root->exists())
{
// set level to prevent additional query
$root->getNode()->setLevel(0);
// default to include root node
$options = array_merge(array('include_record'=>true), $options);
// remove root node from collection if not required
if($options['include_record'] == false)
$tree->remove(0);
// set collection for iterator
$options['collection'] = $tree;
return $root->getNode()->traverse('Pre', $options);
}
}
/**
* 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())
{
$record = $this->table->find($pk);
if($record->exists())
{
$options = array_merge(array('include_record'=>true), $options);
return $record->getNode()->traverse('Pre', $options);
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment