...

MPTT Class

marek_mar
02-15-2006, 08:03 PM
This class should handle the management of a MPTT tree.

Manual (sort of):

Make sure you have set the constants:

TABLE_DATA - The name of the table in which the tree data is stored.
FIELD_KEY - The name of the PRIMARY KEY column (the one with row id's).
FIELD_LEFT_ID - The name of the column in which the left id's are stored.
FIELD_RIGHT_ID - The name of the column in which the right id's are stored.
FIELD_LEVEL - The name of the column in which the elemts levels are stored.

Yes, all 4 columns are required in your table setup.

Method description:

mptt:mptt($link = null)
$link - (optional) MySQL link identifier.

mptt:add($parent, $child_num = 0, $misc_data = false)
Add an element to the tree as a child of $parent and as $child_num'th child. If $data is not supplied the insert id will be returned.
$parent - The id of the parent of the added object (0 for root).
$child_num - The new element will be placed as the $child_num'th child of the parent*.
$misc_data - An array containing other data to be added to the tree. If not set the function will return the insert id of the new row.
Returns bool(true) on success unless $misc_data is not set.

mptt::delete($id, $keep_children = false)
Deletes element $id with or without children. If children should be kept they will become children of $id's parent.
$id - The id of the element to delete.
$keep_children - If true the children of the deleted element will move to the parent of the deleted element.
Returns bool(true) on success.

mptt::move($id, $target_id, $child_num = 0)
Move a element (with children) $id, under element $target_id as the $child_num'th child of that element.
$id - The element to move
$target_id - The id of the new parent.
$child_num - The new element will be placed as the $child_num'th child of the parent*.

mptt::copy($id, $parent, $child_num = 0)
Copies element $id (with children) to $parent as the $child_mun'th child.
$id - The element to move
$parent - The id of the new parent.
$child_num - The new element will be placed as the $child_num'th child of the parent*.

mptt::swap($id1, $id2)
Swaps two elements and ONLY the elements!
$id1 and $id2 - Id's of the elements to swap.

mptt::check_consistency(&$errors=array()) // Kind of experimental. But worked in tests.
Check if all of the tree's left and right id's are correct.
$errors - This will hold an array of error messages if any messages accour (null otherwise).
Returns true if there are no errors.

*$child_num is by default 0.
0 means that the element will be inserted as the first child, 1 as the 2nd child and so on.
You can also use negative numbers. -1 would be the last child... pretty much like substr().

Code:
It was too large. Attached.

Example:
SQL & example data:

CREATE TABLE `data` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
`title` varchar(200) NOT NULL,
`left_id` int(10) NOT NULL,
`right_id` int(10) NOT NULL,
`level` mediumint(8) unsigned NOT NULL,
UNIQUE KEY `id` (`id`)
);

# The same example as it was on aesthetic-theory
INSERT INTO `data` VALUES (1, 'Music', 1, 14, 1);
INSERT INTO `data` VALUES (2, 'Ludo', 2, 5, 2);
INSERT INTO `data` VALUES (3, 'self titled', 3, 4, 3);
INSERT INTO `data` VALUES (4, 'weezer', 6, 13, 2);
INSERT INTO `data` VALUES (5, 'pinkerton', 7, 12, 3);
INSERT INTO `data` VALUES (6, 'el scorcho lyrics', 8, 9, 4);
INSERT INTO `data` VALUES (7, 'pink triangle lyrics', 10, 11, 4);
INSERT INTO `data` VALUES (8, 'Foods', 15, 22, 1);
INSERT INTO `data` VALUES (9, 'Meat', 16, 21, 2);
INSERT INTO `data` VALUES (10, 'Steak', 17, 18, 3);
INSERT INTO `data` VALUES (11, 'ribs', 19, 20, 3);


PHP & HTML:

<?php
// Example
?>
<html>
<head>
<title>MPTT</title>
</head>
<body>

<form name="add" action="<?php print $_SERVER['PHP_SELF']; ?>" method="POST">
Parent ID: <input type="text" name="parent" /><br />
As child: <input type="text" name="child_num" value="0" /><br />
Title: <input type="text" name="title" /><br />
<input type="submit" name="add" value="Add" />
</form>

<form name="delete" action="<?php print $_SERVER['PHP_SELF']; ?>" method="POST">
ID: <input type="text" name="id" /><br />
Keep children? <input type="radio" name="keep_children" value="0" checked="checked" />No
<input type="radio" name="keep_children" value="1" />Yes<br />
<input type="submit" name="delete" value="Delete" />
</form>

<form name="move" action="<?php print $_SERVER['PHP_SELF']; ?>" method="POST">
ID: <input type="text" name="id" /><br />
Target: <input type="text" name="target_id" /><br />
As child: <input type="text" name="child_num" value="0" /><br />
<input type="submit" name="move" value="Move" />
</form>

<form name="copy" action="<?php print $_SERVER['PHP_SELF']; ?>" method="POST">
ID: <input type="text" name="id" /><br />
Target: <input type="text" name="target_id" /><br />
As child: <input type="text" name="child_num" value="0" /><br />
<input type="submit" name="copy" value="Copy" />
</form>

<form name="swap" action="<?php print $_SERVER['PHP_SELF']; ?>" method="POST">
ID1: <input type="text" name="id1" /><br />
ID2: <input type="text" name="id2" /><br />
<input type="submit" name="swap" value="Swap" />
</form>
<?php
error_reporting(E_ALL);


// DB constants
define('DB_HOST', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_DATABASE', 'mptt');

// connect to the database
$link = mysql_connect(DB_HOST, DB_USERNAME, DB_PASSWORD) or die (mysql_error());
mysql_select_db(DB_DATABASE) or die (mysql_error());

require 'class_mptt.php';
$mptt = new mptt($link);
if(isset($_POST['add']))
{
$mptt->add($_POST['parent'], $_POST['child_num'], array('title' => $_POST['title']));
}
elseif(isset($_POST['delete']))
{
$mptt->delete($_POST['id'], (bool) $_POST['keep_children']);
}
elseif(isset($_POST['move']))
{
$mptt->move($_POST['id'], $_POST['target_id'], $_POST['child_num']);
}
elseif(isset($_POST['copy']))
{
$mptt->copy($_POST['id'], $_POST['target_id'], $_POST['child_num']);
}
elseif(isset($_POST['swap']))
{
$mptt->swap($_POST['id1'], $_POST['id2']);
}

$sql = 'SELECT * FROM `' . TABLE_DATA . '` WHERE `id` > 0 ORDER BY `left_id` ASC';

$raw_result = mysql_query($sql) or die(mysql_error() . 'here');

while($item = mysql_fetch_array($raw_result))
{
print '<div style="margin-left: ' . $item['level'] . 'em;">' . $item['id'] . ' | ' . $item['title'] . ' - ' . $item['level'] . ' - {' . $item['left_id'] . ', ' . $item['right_id'] . '}</div>' . "\n";
}



if(!$mptt->check_consistency($errors))
{

print 'Tree is NOT consistant!<pre>';
var_dump($errors);
print '</pre>';
}
else
{
print 'Tree is consistant!';
}
?>
</body>
</html>

ralph l mayo
02-15-2006, 09:12 PM
A few cool things about MPTT:

You can get breadcrumbs like at the top of these forum pages (CodingForums.com > :: Server side development > PHP > Post a PHP snippet) for a given id by iterating through something like this (pseudocode):

SELECT title
FROM yourmptttable
WHERE lft < id's lft AND rgt > id's rgt -- all elements that contain id
ORDER BY lft ASC -- follow the tree


Do the opposite to get all substituents, as in all replies to a topic or whatever:

SELECT titlefield
FROM yourmptttable
WHERE lft > id's lft AND r < id's rgt -- all elements contained by id
ORDER BY lft ASC
LIMIT ((id's rgt - id's lft - 1)/2) -- optional

marek_mar
02-17-2006, 06:20 PM
I added two new methods: mptt:copy() and mptt::check_consistency() and fixed a little bug with $child_num.
I updated the first post.

marek_mar
09-10-2006, 02:03 AM
It's about time this got updated. I always tell everyone to use this...

Requires PHP5.1.1 or newer now.
Added: This class now uses the internal_dbal mentioned in this post (http://www.codingforums.com/showthread.php?p=485560#post485560).
Added: Transactions. Transactions offer a more secure way of inserting updating the database, discarding the changes when something goes wrong.
Added class mppt_toolbox. It has some useful methods. This is still being worked on.
Added: mppt_toolbox::calc_levels(). If you had an MPPT tree but don't have the 'level' column then this function will fill in the values.
Added: configurable fields. The values that were previously constants are now configurable just like the config in my session class (http://www.codingforums.com/showthread.php?p=485560#post485560). mppt extends the config class to gain the functionality.
Fixed: mppt::add() would sometimes break the tree.
Moved: mppt::check_consistency() to mppt_toolbox::check_consistency()

HerrSerker
12-19-2007, 12:32 PM
I encountered some problems, when I started to copy or move some trees into another.

I then saw, that you do not order the children in the get_children-Method, but I think, this is crucible for the function to work properly.

So add
some "ORDER BY `left_id`" in your Code

Thanks otherwise for your brilliant effort

ralph l mayo
12-20-2007, 05:58 AM
FYI this class is a huge minefield of race conditions that will go inconsistent eventually. Transactions actually make it worse by widening the window where the initial SELECT parts of the actions get branch dimensions that have changed by the time the INSERT/UPDATE parts are committed with the interpolated values. You basically have to lock the table for reads and writes through the duration of every writing action.

aedrin
01-03-2008, 08:40 PM
You go through the effort of creating a class around this code, but then you still use global constants....

Not to mention that this is using PHP4 OOP syntax.

y2k_2000
09-03-2009, 09:45 AM
Ty very much for your nice piece of code
very useful

I have made some improvements (some useful functions from Cakephp, tree behaviour) wich I wanna share with you (Based in PHP4 version of your class):

childcount()
getpath()
tree()
generatetreelist()

extends DB class, for queries and execution of SQL (http://slaout.linux62.org/php/index.html)

7744

Medved
07-26-2010, 07:13 PM
You should use the class,you are trying ti create, not global constants

Fou-Lu
07-26-2010, 08:09 PM
This code is nearly 5 years old. Class constants were not created until PHP 5.0 object overhaul. Therefore, the only way to create a class controlled constant set is to declare constants at a global scope (which all constants created by define are anyway). Putting it at the top outside of the class definition is much easier to find then inside a constructor for modification purposes.
But yes, in a PHP5.0+ OO variation, you would used class named constants.



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum