RE: [PHP] "Nested Set Model" or "modified preorder tree traversal" [SOLVED]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



UGH!!!!!! LAME! This list does not allow attachments apparently. *sigh*.

Sorry, here it is as a giant "paste" then...

------------------------------------->8 snip
8<--------------------------------
<?php
/*
# This is all pretty much copy/paste code from this URL:
# http://dev.mysql.com/tech-resources/articles/hierarchical-data.html

  here are some other references:
  
  http://www.sitepoint.com/article/hierarchical-data-database
 
http://www.zend.com/zend/tut/tutorial-ferrara2.php?article=tutorial-ferrara2
&id=3453&open=1&anc=0&view=1
  http://simon.incutio.com/archive/2003/06/19/storingTrees
  http://istherelifeafter.com/joecelko.html
  http://www.codeproject.com/cs/database/Trees_in_SQL_databases.asp
  
  and here's an ASP technique that looks interesting
  http://www.4guysfromrolla.com/webtech/101202-1.shtml
  
  and it appears that Rails already has acts_as_nested_set:
 
http://www.nabble.com/acts_as_tree-with-Modified-Preorder-Traversal--t929560
.html
*/

require_once('../includes/db.inc.php');

//TODO: [dv] There needs to be a function to MOVE a category to another
spot. 
//			 especially since we have a way to delete and move
children up.
//			 This could be a hybrid of insert_category_after() I
think.

/**
* Delete a category (and optionally all of it's children) from the tree
*
* @access 	public
* @return 	void
* @param 	mixed $category the string name or category ID of the
category to delete.
* @param	boolean $deleteChildren (false) otherwise move all the
subcategories up to the level of the deleted category
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.1
* @date    	03/01/06
*/
function delete_category($category = null, $deleteChildren = false)
{
	if (is_null($category)) return false;
	
	SQL_QUERY('LOCK TABLE categories WRITE');
	
	if (is_leaf_node($category))
	{
		$sth = SQL_QUERY("SELECT @myLeft := lft, @myRight := rgt,
@myWidth := rgt - lft + 1 FROM categories 
						  WHERE ".(
(is_numeric($category)) ? "id = '".$category."'" : "name =
'".SQL_ESCAPE($category)."'" )." LIMIT 1");
		if ($sth && SQL_NUM_ROWS($sth) == 1)
		{
			SQL_QUERY("DELETE FROM categories WHERE lft BETWEEN
@myLeft AND @myRight");
			
			SQL_QUERY('UPDATE categories SET rgt = rgt -
@myWidth WHERE rgt > @myRight');
			SQL_QUERY('UPDATE categories SET lft = lft -
@myWidth WHERE lft > @myRight');
		}
	}
	else
	{
		if ($deleteChildren) //just delete the little bastards
		{
			$sth = SQL_QUERY("SELECT @myLeft := lft, @myRight :=
rgt, @myWidth := rgt - lft + 1 FROM categories 
							  WHERE ".(
(is_numeric($category)) ? "id = '".$category."'" : "name =
'".SQL_ESCAPE($category)."'" )." LIMIT 1");
			if ($sth && SQL_NUM_ROWS($sth) == 1)
			{
				SQL_QUERY("DELETE FROM categories WHERE lft
BETWEEN @myLeft AND @myRight");
				
				SQL_QUERY('UPDATE categories SET rgt = rgt -
@myWidth WHERE rgt > @myRight');
				SQL_QUERY('UPDATE categories SET lft = lft -
@myWidth WHERE lft > @myRight');
			}
		}
		else //move them up to the level of the deleted category
		{
			$sth = SQL_QUERY("SELECT @myLeft := lft, @myRight :=
rgt, @myWidth := rgt - lft + 1 FROM categories 
							  WHERE ".(
(is_numeric($category)) ? "id = '".$category."'" : "name =
'".SQL_ESCAPE($category)."'" )." LIMIT 1");
			if ($sth && SQL_NUM_ROWS($sth) == 1)
			{
				SQL_QUERY("DELETE FROM categories WHERE lft
= @myLeft");
				
				SQL_QUERY('UPDATE categories SET rgt = rgt -
1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight');
				SQL_QUERY('UPDATE categories SET rgt = rgt -
2 WHERE rgt > @myRight');
				SQL_QUERY('UPDATE categories SET lft = lft -
2 WHERE lft > @myRight');
			}
		}
	}
	
	SQL_QUERY('UNLOCK TABLES');
}

/**
* Inserts a new category to the right of an existing category
*
* @access 	public
* @return 	integer ID of the newely inserted category or false.
* @param 	string $newname the name of the new category.
* @param 	mixed $leftnode the name or ID of the immediate left
category in the sibline tree heirarchy.
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.1
* @date    	02/27/06
*/
function insert_category_after($newname = null, $leftnode = null)
{
	//global $OPTION;
	//$OPTION['debug'] = true;
	
	if (is_null($newname) || is_null($leftnode)) return false;
	if (is_numeric($leftnode) && $leftnode < 1) return false;
	
	SQL_QUERY('LOCK TABLE categories WRITE');
	
	if (is_leaf_node($leftnode))
	{
		$sth = SQL_QUERY("SELECT @myLeft := lft FROM categories 
						  WHERE ".(
(is_numeric($leftnode)) ? "id = '".$leftnode."'" : "name =
'".SQL_ESCAPE($leftnode)."'" )." LIMIT 1");
		if ($sth && SQL_NUM_ROWS($sth) == 1)
		{
			SQL_QUERY('UPDATE categories SET rgt = rgt + 2 WHERE
rgt > @myLeft');
			SQL_QUERY('UPDATE categories SET lft = lft + 2 WHERE
lft > @myLeft');
			SQL_QUERY("INSERT INTO categories(name, lft, rgt)
VALUES('".SQL_ESCAPE($newname)."', @myLeft + 1, @myLeft + 2)");
			$id = SQL_INSERT_ID();
		}
	}
	else
	{
		$sth = SQL_QUERY("SELECT @myRight := rgt FROM categories 
						  WHERE ".(
(is_numeric($leftnode)) ? "id = '".$leftnode."'" : "name =
'".SQL_ESCAPE($leftnode)."'" )." LIMIT 1");
		if ($sth && SQL_NUM_ROWS($sth) == 1)
		{
			SQL_QUERY('UPDATE categories SET rgt = rgt + 2 WHERE
rgt > @myRight');
			SQL_QUERY('UPDATE categories SET lft = lft + 2 WHERE
lft > @myRight');
			SQL_QUERY("INSERT INTO categories(name, lft, rgt)
VALUES('".SQL_ESCAPE($newname)."', @myRight + 1, @myRight + 2)");
			$id = SQL_INSERT_ID();
		}
	}
	
	SQL_QUERY('UNLOCK TABLES');
	//$OPTION['debug'] = false;
	return ($id > 0) ? $id : false;
}

/**
* Shows a tally of the number of products in each category
*
* @access 	public
* @return 	array
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.0
* @date    	02/23/06
*/
function get_category_product_tally()
{
	return SQL_QUERY_ARRAY_PAIR("SELECT parent.name,
COUNT(products.name)
								FROM
categories AS node, categories AS parent, products
								WHERE
node.lft BETWEEN parent.lft AND parent.rgt
	
AND node.id = products.category_id
								GROUP BY
parent.name
								ORDER BY
node.lft");
}

/**
* Returns an array of all the categories defined
*
* @access 	public
* @return 	array
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.0
* @date    	02/23/06
*/
function get_categories()
{
	return SQL_QUERY_ARRAY_PAIR("SELECT id, name FROM categories ORDER
BY name");
}

/**
* Returns an array of all the categories in the tree starting from a given
category
*
* @access 	public
* @return 	array
* @param 	string $name the name of the category to start with.
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.0
* @date    	02/23/06
*/
function get_category_tree($name = null)
{
	if (is_null($name)) return false;
	
	return SQL_QUERY_ARRAY_PAIR("SELECT 	node.id, node.name 
								  FROM
categories AS node, categories AS parent 
								  WHERE
node.lft BETWEEN parent.lft AND parent.rgt 
	
AND parent.name = '".SQL_ESCAPE($name)."'
								  ORDER BY
node.lft");
}

/**
* Show the path of categories to a given node.
*
* @access 	public
* @return 	array
* @param 	mixed $category the string name of the category or its ID to
start with.
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.0
* @date    	02/23/06
*/
function get_path($category = 1)
{
	return SQL_QUERY_ARRAY_PAIR("SELECT parent.id, parent.name
								FROM
categories AS node,	categories AS parent
								WHERE
node.lft BETWEEN parent.lft AND parent.rgt
								AND 	".(
(is_numeric($category)) ? "node.id = '".$category."'" : "node.name =
'".SQL_ESCAPE($category)."'" )."
								ORDER BY
node.lft");
}

/**
* Show the products of that category, as well as list its immediate
sub-categories, 
* but not the entire tree of categories beneath it.
*
* @access 	public
* @return 	array
* @param 	mixed $category the string name of the category or its ID to
start with.
* @param 	boolean $showParent (false) Show the parent node.
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.0
* @date    	02/23/06
*/
function get_subcategories($category = 1, $showParent = false)
{
	return SQL_QUERY_ARRAY_PAIR("
				SELECT node.id, node.name,
(COUNT(parent.name) - (sub_tree.depth + 1)) AS depth
				FROM categories AS node,
					categories AS parent,
					categories AS sub_parent,
					(
						SELECT node.id, node.name,
(COUNT(parent.name) - 1) AS depth
						FROM categories AS node,
						categories AS parent
						WHERE node.lft BETWEEN
parent.lft AND parent.rgt
						AND ".(
(is_numeric($category)) ? "node.id = '".$category."'" : "node.name =
'".SQL_ESCAPE($category)."'" )."
						GROUP BY node.name
						ORDER BY node.lft
					)AS sub_tree
				WHERE node.lft BETWEEN parent.lft AND
parent.rgt
					AND node.lft BETWEEN sub_parent.lft
AND sub_parent.rgt
					AND sub_parent.name = sub_tree.name
				GROUP BY node.name
				HAVING depth ".( ($showParent) ? '<=' :
'=')." 1
				ORDER BY node.lft");
}

/**
* Returns an array of all the category names in the tree and their depths
*
* @access 	public
* @return 	array
* @param 	string $name the name of the category to start with.
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.0
* @date    	02/23/06
*/
function get_depths($name = null)
{
	if (is_null($name))
	{
		return SQL_QUERY_ARRAY_PAIR("SELECT node.name,
(COUNT(parent.name) - 1) AS depth
									FROM
categories AS node, categories AS parent
	
WHERE node.lft BETWEEN parent.lft AND parent.rgt
	
GROUP BY node.name
	
ORDER BY node.lft");
	}
	else
	{
		return SQL_QUERY_ARRAY_PAIR("
					SELECT node.name,
(COUNT(parent.name) - (sub_tree.depth + 1)) AS depth
					FROM categories AS node,
						categories AS parent,
						categories AS sub_parent,
						(
							SELECT node.name,
(COUNT(parent.name) - 1) AS depth
							FROM categories AS
node,
							categories AS parent
							WHERE node.lft
BETWEEN parent.lft AND parent.rgt
							AND node.name =
'".SQL_ESCAPE($name)."'
							GROUP BY node.name
							ORDER BY node.lft
						)AS sub_tree
					WHERE node.lft BETWEEN parent.lft
AND parent.rgt
						AND node.lft BETWEEN
sub_parent.lft AND sub_parent.rgt
						AND sub_parent.name =
sub_tree.name
					GROUP BY node.name
					ORDER BY node.lft");
	}
}

/**
* Returns an array of all the leaf nodes
*
* @access 	public
* @return 	array
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.0
* @date    	02/23/06
* @see		is_leaf_node()
*/
function get_leaf_nodes()
{
	return SQL_QUERY_ARRAY_PAIR('SELECT id, name FROM categories WHERE
rgt = lft + 1');
}

/**
* Returns boolean if category is a leaf node
*
* @access 	public
* @return 	boolean
* @param 	mixed $category the string name of the category or its ID.
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.0
* @date    	02/23/06
* @see		get_leaf_nodes()
*/
function is_leaf_node($category = null)
{
	if (!is_null($category))
	{
		if ($sth = SQL_QUERY("SELECT id, name FROM categories WHERE
rgt = lft + 1 
	
AND ".( (is_numeric($category)) ? "id = '".$category."'" : "name =
'".SQL_ESCAPE($category)."'" )))
			return (SQL_NUM_ROWS($sth) == 1);
	}
	
	return false;
}

/**
* Rename a category
*
* @access 	public
* @return 	false if invalid input
* @param 	mixed $category the string name of the category or its ID.
* @param 	string $newname the name of the new category.
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.1
* @date    	03/01/06
*/
function rename_category($category = null, $newname = null)
{
	if (is_null($category) || is_null($newname)) return false;
	
	SQL_QUERY("UPDATE categories SET name = '".SQL_ESCAPE($newname)."'
WHERE ".( (is_numeric($category)) ? "id = '".$category."'" : "name =
'".SQL_ESCAPE($category)."'" )." LIMIT 1", true);
}

/**
* Prints out the tree with indentations in a very basic textual way
*
* @access 	public
* @return 	void
* @param	integer $id the $id of the node to highlite
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.1
* @date    	02/27/06
* @see		print_nested_tree_html()
*/
function print_nested_tree_txt($id = 1)
{
	$sth = SQL_QUERY("SELECT node.id as id, CONCAT( REPEAT( '   ',
(COUNT(parent.name) - 1) ), node.name) AS name
					FROM categories AS node, categories
AS parent
					WHERE node.lft BETWEEN parent.lft
AND parent.rgt
					GROUP BY node.name
					ORDER BY node.lft");
	if ($sth)
	{
		print "\n";
		while($row = SQL_ASSOC_ARRAY($sth))
			print (($row['id'] == $id) ? '['.$row['name'].']' :
$row['name']) . "\n";
	}
}

/**
* Prints out the tree with indentations in a nice HTML way with hyperlinks
and highlight current category
*
* @access 	public
* @return 	void
* @param	integer $id the $id of the node to highlite
* @author 	Daevid Vincent [daevid@xxxxxxxxxx]
* @version 	1.1
* @date    	02/27/06
* @see		print_nested_tree_txt()
*/
function print_nested_tree_html($id = 1)
{
	$sth = SQL_QUERY("SELECT node.id as id, node.name AS name,
(COUNT(parent.name) - 1) as indent
					FROM categories AS node, categories
AS parent
					WHERE node.lft BETWEEN parent.lft
AND parent.rgt
					GROUP BY node.name
					ORDER BY node.lft");
	if ($sth)
		while($row = SQL_ASSOC_ARRAY($sth))
			echo ($row['id'] == $id) 
				 ? 
				 "<font class='category_highlite'>".
str_repeat('&nbsp;&nbsp;', $row['indent']) . $row['name'] . '</font>' 
				 : 
				 str_repeat('&nbsp;&nbsp;',
$row['indent'])."<a href='?cid=".$row['id']."'>".$row['name'].'</a>'
				 ."<br>\n";
}
?>

------------------------------------->8 snip
8<--------------------------------

> -----Original Message-----
> From: Daevid Vincent [mailto:daevid@xxxxxxxxxx] 
> Sent: Wednesday, March 01, 2006 9:46 PM
> To: 'Php-Db'
> Subject:  RE: [PHP] "Nested Set Model" or "modified 
> preorder tree traversal" [SOLVED]
> 
> Since I couldn't find any short and sweet drop in code for 
> this, I made my
> own. Here it is in case anyone else finds it helpful. Should be pretty
> straight forward. I use my own mysql wrapper functions, but 
> you can pretty
> much figure out what they do and S&R your own.
> 
> Hopefully this works as an attachement.
> 
> D.Vin
> 
> > -----Original Message-----
> > From: Daevid Vincent [mailto:daevid@xxxxxxxxxx] 
> > Sent: Wednesday, February 22, 2006 12:42 PM
> > To: 'Curt Zirzow'; php-general@xxxxxxxxxxxxx
> > Subject: RE: [PHP] "Nested Set Model" or "modified preorder 
> > tree traversal"
> > 
> > Peter Brawley on the mySQL list pointed me at: 
> > http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html 
> > 
> > Which uses mySQL 5's procedures and such.
> > I haven't implemented this yet, but it appears to be what I 
> > wanted for the
> > most part.
> > 
> > > -----Original Message-----
> > > From: Curt Zirzow [mailto:czirzow@xxxxxxxxx] 
> > > Sent: Tuesday, February 21, 2006 10:58 PM
> > > To: php-general@xxxxxxxxxxxxx
> > > Subject: Re: [PHP] "Nested Set Model" or "modified preorder 
> > > tree traversal"
> > > 
> > > On Tue, Feb 21, 2006 at 09:38:53PM -0800, Daevid Vincent wrote:
> > > > I've been searching the web for the past few hours trying 
> > > to find a simple
> > > > drop-in class or functions to implement "Nested Set Model" 
> > > or "modified
> > > > preorder tree traversal".
> > > 
> > > I actually fell in love of this method of doing trees when I
> > > discovered it about a year ago, when researching a fast way to do
> > > a parent child retrieval.
> > > 
> > > > 
> > > > I've found several tutorials and related links:
> > > > 
> > > >   
> > > 
> http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
> > > >   http://www.sitepoint.com/article/hierarchical-data-database
> > > >  
> > > > 
> > > http://www.zend.com/zend/tut/tutorial-ferrara2.php?article=tut
> > > orial-ferrara2
> > > > &id=3453&open=1&anc=0&view=1
> > > >   http://simon.incutio.com/archive/2003/06/19/storingTrees
> > > >   http://istherelifeafter.com/joecelko.html
> > > >   
> > http://www.codeproject.com/cs/database/Trees_in_SQL_databases.asp
> > > 
> > > oh i have to add some of these to my list, i didn't have a couple
> > > of them.
> > > 
> > > > 
> > > > I even found some outdated PEAR DB_NestedSet class from 
> > > 2003 that seemed way
> > > > to overly complicated. And some other PHP4_Mysql3MPTTA 
> > > class on PHP Classes
> > > > that was equally complicated and written for ancient PHP4 
> > > and mySQL 3!!
> > > 
> > > yeah, i kind of got that feeling with the PEAR class as well, i
> > > think it was designed that way to be as flexible as possible. I'm
> > > not familiar with the other class, i tend to avoid anything from
> > > PHP Classes.
> > > 
> > > > 
> > > > Hasn't anyone else done this before (recently)? 
> > > > Did everyone just write their own code every time?
> > > > Anyone have some easy to use code that allows for:
> > > > Add, delete, update/rename, select, show tree, bread crumb 
> > > display, etc.
> > > 
> > > It would be nice to have a tool to manage the functionality. There
> > > will always be the issue with any common tool that is built for
> > > this purpose is how do you relate the table that defines 
> the nested
> > > with the table that actually holds the data.
> > > 
> > > I believe that why the PEAR and other classes you came across
> > > seemed to be overly complicated.  I have found that it is 
> easier to
> > > write my own management (as awful as that sounds) for the job at
> > > hand.
> 
> 
> 

-- 
PHP Database Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php


[Index of Archives]     [PHP Home]     [PHP Users]     [Postgresql Discussion]     [Kernel Newbies]     [Postgresql]     [Yosemite News]

  Powered by Linux