Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
doctrine-dbal
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Tomáš Trávníček
doctrine-dbal
Commits
75613eb4
Commit
75613eb4
authored
Jul 05, 2007
by
romanb
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
First draft of the new nestedset changes. Open for testing and discussion.
parent
411779d1
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
630 additions
and
216 deletions
+630
-216
NestedSet.php
draft/Node/NestedSet.php
+421
-147
NestedSet.php
draft/Tree/NestedSet.php
+169
-69
nestedset_changes.tree
draft/nestedset_changes.tree
+40
-0
No files found.
draft/Node/NestedSet.php
View file @
75613eb4
<?php
/*
* $Id$
* $Id
: NestedSet.php 1382 2007-05-17 19:52:50Z Jonathan.Wage
$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
...
...
@@ -26,8 +26,9 @@
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version
$Revision
$
* @version
$Revision: 1382
$
* @author Joe Simms <joe.simms@websites4.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class
Doctrine_Node_NestedSet
extends
Doctrine_Node
implements
Doctrine_Node_Interface
{
...
...
@@ -78,13 +79,22 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
getPrevSibling
()
{
$q
=
$this
->
record
->
getTable
()
->
createQuery
();
$result
=
$q
->
where
(
'rgt = ?'
,
$this
->
getLeftValue
()
-
1
)
->
execute
()
->
getFirst
();
$q
=
$this
->
_tree
->
getBaseQuery
();
$q
=
$q
->
where
(
'base.rgt = ?'
,
$this
->
getLeftValue
()
-
1
);
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$result
=
$q
->
execute
();
if
(
!
$result
)
$result
=
$this
->
record
->
getTable
()
->
create
();
if
(
count
(
$result
)
<=
0
)
{
return
false
;
}
return
$result
;
if
(
$result
instanceof
Doctrine_Collection
)
{
$sibling
=
$result
->
getFirst
();
}
else
if
(
is_array
(
$result
))
{
$sibling
=
array_shift
(
$result
);
}
return
$sibling
;
}
/**
...
...
@@ -94,13 +104,22 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
getNextSibling
()
{
$q
=
$this
->
record
->
getTable
()
->
createQuery
();
$result
=
$q
->
where
(
'lft = ?'
,
$this
->
getRightValue
()
+
1
)
->
execute
()
->
getFirst
();
$q
=
$this
->
_tree
->
getBaseQuery
();
$q
=
$q
->
where
(
'base.lft = ?'
,
$this
->
getRightValue
()
+
1
);
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$result
=
$q
->
execute
();
if
(
count
(
$result
)
<=
0
)
{
return
false
;
}
if
(
!
$result
)
$result
=
$this
->
record
->
getTable
()
->
create
();
if
(
$result
instanceof
Doctrine_Collection
)
{
$sibling
=
$result
->
getFirst
();
}
else
if
(
is_array
(
$result
))
{
$sibling
=
array_shift
(
$result
);
}
return
$
result
;
return
$
sibling
;
}
/**
...
...
@@ -112,17 +131,14 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
{
$parent
=
$this
->
getParent
();
$siblings
=
array
();
if
(
$parent
->
exists
())
{
foreach
(
$parent
->
getNode
()
->
getChildren
()
as
$child
)
{
if
(
$this
->
isEqualTo
(
$child
)
&&
!
$includeNode
)
if
(
$parent
->
exists
())
{
foreach
(
$parent
->
getNode
()
->
getChildren
()
as
$child
)
{
if
(
$this
->
isEqualTo
(
$child
)
&&
!
$includeNode
)
{
continue
;
}
$siblings
[]
=
$child
;
}
}
return
$siblings
;
}
...
...
@@ -133,13 +149,22 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
getFirstChild
()
{
$q
=
$this
->
record
->
getTable
()
->
createQuery
();
$result
=
$q
->
where
(
'lft = ?'
,
$this
->
getLeftValue
()
+
1
)
->
execute
()
->
getFirst
();
$q
=
$this
->
_tree
->
getBaseQuery
();
$q
->
where
(
'base.lft = ?'
,
$this
->
getLeftValue
()
+
1
);
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$result
=
$q
->
execute
();
if
(
!
$result
)
$result
=
$this
->
record
->
getTable
()
->
create
();
if
(
count
(
$result
)
<=
0
)
{
return
false
;
}
return
$result
;
if
(
$result
instanceof
Doctrine_Collection
)
{
$child
=
$result
->
getFirst
();
}
else
if
(
is_array
(
$result
))
{
$child
=
array_shift
(
$result
);
}
return
$child
;
}
/**
...
...
@@ -149,33 +174,64 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
getLastChild
()
{
$q
=
$this
->
record
->
getTable
()
->
createQuery
();
$result
=
$q
->
where
(
'rgt = ?'
,
$this
->
getRightValue
()
-
1
)
->
execute
()
->
getFirst
();
$q
=
$this
->
_tree
->
getBaseQuery
();
$q
->
where
(
'base.rgt = ?'
,
$this
->
getRightValue
()
-
1
);
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$result
=
$q
->
execute
();
if
(
count
(
$result
)
<=
0
)
{
return
false
;
}
if
(
!
$result
)
$result
=
$this
->
record
->
getTable
()
->
create
();
if
(
$result
instanceof
Doctrine_Collection
)
{
$child
=
$result
->
getFirst
();
}
else
if
(
is_array
(
$result
))
{
$child
=
array_shift
(
$result
);
}
return
$
result
;
return
$
child
;
}
/**
* gets children for node (direct descendants only)
*
* @return
array array of sibling Doctrine_Record objects
* @return
mixed The children of the node or FALSE if the node has no children.
*/
public
function
getChildren
()
{
return
$this
->
get
Iterator
(
'Pre'
,
array
(
'depth'
=>
1
)
);
return
$this
->
get
Descendants
(
1
);
}
/**
* gets descendants for node (direct descendants only)
*
* @return iterator iterator to traverse descendants from node
* @return mixed The descendants of the node or FALSE if the node has no descendants.
* @todo Currently all descendants are fetched, no matter the depth. Maybe there is a better
* solution with less overhead.
*/
public
function
getDescendants
()
public
function
getDescendants
(
$depth
=
null
,
$includeNode
=
false
)
{
return
$this
->
getIterator
();
$q
=
$this
->
_tree
->
getBaseQuery
();
$params
=
array
(
$this
->
record
->
get
(
'lft'
),
$this
->
record
->
get
(
'rgt'
));
if
(
$includeNode
)
{
$q
->
where
(
"base.lft >= ? AND base.rgt <= ?"
,
$params
)
->
orderBy
(
"base.lft asc"
);
}
else
{
$q
->
where
(
"base.lft > ? AND base.rgt < ?"
,
$params
)
->
orderBy
(
"base.lft asc"
);
}
if
(
$depth
!==
null
)
{
$q
->
addWhere
(
"base.level <= ?"
,
$this
->
record
[
'level'
]
+
$depth
);
}
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$result
=
$q
->
execute
();
if
(
count
(
$result
)
<=
0
)
{
return
false
;
}
return
$result
;
}
/**
...
...
@@ -185,15 +241,21 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
getParent
()
{
$q
=
$this
->
record
->
getTable
()
->
createQuery
();
$q
=
$this
->
_tree
->
getBaseQuery
();
$q
->
where
(
"base.lft < ? AND base.rgt > ?"
,
array
(
$this
->
getLeftValue
(),
$this
->
getRightValue
()))
->
orderBy
(
"base.rgt asc"
);
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$result
=
$q
->
execute
();
$parent
=
$q
->
where
(
'lft < ? AND rgt > ?'
,
array
(
$this
->
getLeftValue
(),
$this
->
getRightValue
()))
->
orderBy
(
'rgt asc'
)
->
execute
()
->
getFirst
();
if
(
count
(
$result
)
<=
0
)
{
return
false
;
}
if
(
!
$parent
)
$parent
=
$this
->
record
->
getTable
()
->
create
();
if
(
$result
instanceof
Doctrine_Collection
)
{
$parent
=
$result
->
getFirst
();
}
else
if
(
is_array
(
$result
))
{
$parent
=
array_shift
(
$result
);
}
return
$parent
;
}
...
...
@@ -201,16 +263,23 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
/**
* gets ancestors for node
*
* @return object Doctrine_Collection
* @param integer $deth The depth 'upstairs'.
* @return mixed The ancestors of the node or FALSE if the node has no ancestors (this
* basically means it's a root node).
*/
public
function
getAncestors
()
public
function
getAncestors
(
$depth
=
null
)
{
$q
=
$this
->
record
->
getTable
()
->
createQuery
();
$ancestors
=
$q
->
where
(
'lft < ? AND rgt > ?'
,
array
(
$this
->
getLeftValue
(),
$this
->
getRightValue
()))
->
orderBy
(
'lft asc'
)
->
execute
();
$q
=
$this
->
_tree
->
getBaseQuery
();
$q
->
where
(
"base.lft < ? AND base.rgt > ?"
,
array
(
$this
->
getLeftValue
(),
$this
->
getRightValue
()))
->
orderBy
(
"base.lft asc"
);
if
(
$depth
!==
null
)
{
$q
->
addWhere
(
"base.level >= ?"
,
$this
->
record
[
'level'
]
-
$depth
);
}
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$ancestors
=
$q
->
execute
();
if
(
count
(
$ancestors
)
<=
0
)
{
return
false
;
}
return
$ancestors
;
}
...
...
@@ -225,12 +294,12 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
{
$path
=
array
();
$ancestors
=
$this
->
getAncestors
();
foreach
(
$ancestors
as
$ancestor
)
{
foreach
(
$ancestors
as
$ancestor
)
{
$path
[]
=
$ancestor
->
__toString
();
}
if
(
$includeRecord
)
if
(
$includeRecord
)
{
$path
[]
=
$this
->
getRecord
()
->
__toString
();
}
return
implode
(
$seperator
,
$path
);
}
...
...
@@ -244,9 +313,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
{
$count
=
0
;
$children
=
$this
->
getChildren
();
while
(
$children
->
next
())
{
while
(
$children
->
next
())
{
$count
++
;
}
return
$count
;
...
...
@@ -266,24 +333,26 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
* inserts node as parent of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public
function
insertAsParentOf
(
Doctrine_Record
$dest
)
{
// cannot insert a node that has already has a place within the tree
if
(
$this
->
isValidNode
())
if
(
$this
->
isValidNode
())
{
return
false
;
}
// cannot insert as parent of root
if
(
$dest
->
getNode
()
->
isRoot
())
if
(
$dest
->
getNode
()
->
isRoot
())
{
return
false
;
$this
->
shiftRLValues
(
$dest
->
getNode
()
->
getLeftValue
(),
1
);
$this
->
shiftRLValues
(
$dest
->
getNode
()
->
getRightValue
()
+
2
,
1
);
}
$newRoot
=
$dest
->
getNode
()
->
getRootValue
();
$this
->
shiftRLValues
(
$dest
->
getNode
()
->
getLeftValue
(),
1
,
$newRoot
);
$this
->
shiftRLValues
(
$dest
->
getNode
()
->
getRightValue
()
+
2
,
1
,
$newRoot
);
$newLeft
=
$dest
->
getNode
()
->
getLeftValue
();
$newRight
=
$dest
->
getNode
()
->
getRightValue
()
+
2
;
$newRoot
=
$dest
->
getNode
()
->
getRootValue
();
$this
->
record
[
'level'
]
=
$dest
[
'level'
]
-
1
;
$this
->
insertNode
(
$newLeft
,
$newRight
,
$newRoot
);
return
true
;
...
...
@@ -293,11 +362,12 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
* inserts node as previous sibling of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public
function
insertAsPrevSiblingOf
(
Doctrine_Record
$dest
)
{
// cannot insert a node that has already has a place within the tree
if
(
$this
->
isValidNode
())
if
(
$this
->
isValidNode
())
return
false
;
$newLeft
=
$dest
->
getNode
()
->
getLeftValue
();
...
...
@@ -305,6 +375,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
$newRoot
=
$dest
->
getNode
()
->
getRootValue
();
$this
->
shiftRLValues
(
$newLeft
,
2
,
$newRoot
);
$this
->
record
[
'level'
]
=
$dest
[
'level'
];
$this
->
insertNode
(
$newLeft
,
$newRight
,
$newRoot
);
// update destination left/right values to prevent a refresh
// $dest->getNode()->setLeftValue($dest->getNode()->getLeftValue() + 2);
...
...
@@ -317,11 +388,12 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
* inserts node as next sibling of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public
function
insertAsNextSiblingOf
(
Doctrine_Record
$dest
)
{
// cannot insert a node that has already has a place within the tree
if
(
$this
->
isValidNode
())
if
(
$this
->
isValidNode
())
return
false
;
$newLeft
=
$dest
->
getNode
()
->
getRightValue
()
+
1
;
...
...
@@ -329,6 +401,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
$newRoot
=
$dest
->
getNode
()
->
getRootValue
();
$this
->
shiftRLValues
(
$newLeft
,
2
,
$newRoot
);
$this
->
record
[
'level'
]
=
$dest
[
'level'
];
$this
->
insertNode
(
$newLeft
,
$newRight
,
$newRoot
);
// update destination left/right values to prevent a refresh
...
...
@@ -341,11 +414,12 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
* inserts node as first child of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public
function
insertAsFirstChildOf
(
Doctrine_Record
$dest
)
{
// cannot insert a node that has already has a place within the tree
if
(
$this
->
isValidNode
())
if
(
$this
->
isValidNode
())
return
false
;
$newLeft
=
$dest
->
getNode
()
->
getLeftValue
()
+
1
;
...
...
@@ -353,6 +427,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
$newRoot
=
$dest
->
getNode
()
->
getRootValue
();
$this
->
shiftRLValues
(
$newLeft
,
2
,
$newRoot
);
$this
->
record
[
'level'
]
=
$dest
[
'level'
]
+
1
;
$this
->
insertNode
(
$newLeft
,
$newRight
,
$newRoot
);
// update destination left/right values to prevent a refresh
...
...
@@ -365,11 +440,12 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
* inserts node as last child of dest record
*
* @return bool
* @todo Wrap in transaction
*/
public
function
insertAsLastChildOf
(
Doctrine_Record
$dest
)
{
// cannot insert a node that has already has a place within the tree
if
(
$this
->
isValidNode
())
if
(
$this
->
isValidNode
())
return
false
;
$newLeft
=
$dest
->
getNode
()
->
getRightValue
();
...
...
@@ -377,6 +453,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
$newRoot
=
$dest
->
getNode
()
->
getRootValue
();
$this
->
shiftRLValues
(
$newLeft
,
2
,
$newRoot
);
$this
->
record
[
'level'
]
=
$dest
[
'level'
]
+
1
;
$this
->
insertNode
(
$newLeft
,
$newRight
,
$newRoot
);
// update destination left/right values to prevent a refresh
...
...
@@ -385,13 +462,108 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
return
true
;
}
/**
* Accomplishes moving of nodes between different trees.
* Used by the move* methods if the root value of the two nodes are different.
*
* @param Doctrine_Record $dest
* @param unknown_type $newLeftValue
* @param unknown_type $moveType
* @todo Better exception handling/wrapping
*/
private
function
_moveBetweenTrees
(
Doctrine_Record
$dest
,
$newLeftValue
,
$moveType
)
{
$conn
=
$this
->
record
->
getTable
()
->
getConnection
();
try
{
$conn
->
beginTransaction
();
// Move between trees: Detach from old tree & insert into new tree
$newRoot
=
$dest
->
getNode
()
->
getRootValue
();
$oldRoot
=
$this
->
getRootValue
();
$oldLft
=
$this
->
getLeftValue
();
$oldRgt
=
$this
->
getRightValue
();
$oldLevel
=
$this
->
record
[
'level'
];
// Prepare target tree for insertion, make room
$this
->
shiftRlValues
(
$newLeftValue
,
$oldRgt
-
$oldLft
-
1
,
$newRoot
);
// Set new root id for this node
$this
->
setRootValue
(
$newRoot
);
$this
->
record
->
save
();
// Close gap in old tree
$first
=
$oldRgt
+
1
;
$delta
=
$oldLft
-
$oldRgt
-
1
;
$this
->
shiftRLValues
(
$first
,
$delta
,
$oldRoot
);
// Insert this node as a new node
$this
->
setRightValue
(
0
);
$this
->
setLeftValue
(
0
);
switch
(
$moveType
)
{
case
'moveAsPrevSiblingOf'
:
$this
->
insertAsPrevSiblingOf
(
$dest
);
break
;
case
'moveAsFirstChildOf'
:
$this
->
insertAsFirstChildOf
(
$dest
);
break
;
case
'moveAsNextSiblingOf'
:
$this
->
insertAsNextSiblingOf
(
$dest
);
break
;
case
'moveAsLastChildOf'
:
$this
->
insertAsLastChildOf
(
$dest
);
break
;
default
:
throw
new
Exception
(
"Unknown move operation:
$moveType
."
);
}
$diff
=
$oldRgt
-
$oldLft
;
$this
->
setRightValue
(
$this
->
getLeftValue
()
+
(
$oldRgt
-
$oldLft
));
$this
->
record
->
save
();
$newLevel
=
$this
->
record
[
'level'
];
$levelDiff
=
$newLevel
-
$oldLevel
;
// Relocate descendants of the node
$diff
=
$this
->
getLeftValue
()
-
$oldLft
;
$componentName
=
$this
->
record
->
getTable
()
->
getComponentName
();
$rootColName
=
$this
->
record
->
getTable
()
->
getTree
()
->
getAttribute
(
'rootColumnName'
);
// Update lft/rgt/root/level for all descendants
$q
=
new
Doctrine_Query
(
$conn
);
$q
=
$q
->
update
(
$componentName
)
->
set
(
$componentName
.
'.lft'
,
'lft + '
.
$diff
)
->
set
(
$componentName
.
'.rgt'
,
'rgt + '
.
$diff
)
->
set
(
$componentName
.
'.level'
,
'level + '
.
$levelDiff
)
->
set
(
$componentName
.
'.'
.
$rootColName
,
$newRoot
)
->
where
(
$componentName
.
'.lft > ? AND '
.
$componentName
.
'.rgt < ?'
,
array
(
$oldLft
,
$oldRgt
));
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$oldRoot
);
$q
->
execute
();
$conn
->
commit
();
}
catch
(
Exception
$e
)
{
$conn
->
rollback
();
throw
$e
;
}
}
/**
* moves node as prev sibling of dest record
*
*/
public
function
moveAsPrevSiblingOf
(
Doctrine_Record
$dest
)
{
$this
->
updateNode
(
$dest
->
getNode
()
->
getLeftValue
());
if
(
$dest
->
getNode
()
->
getRootValue
()
!=
$this
->
getRootValue
())
{
// Move between trees
$this
->
_moveBetweenTrees
(
$dest
,
$dest
->
getNode
()
->
getLeftValue
(),
__FUNCTION__
);
}
else
{
// Move within the tree
$oldLevel
=
$this
->
record
[
'level'
];
$this
->
record
[
'level'
]
=
$dest
[
'level'
];
$this
->
updateNode
(
$dest
->
getNode
()
->
getLeftValue
(),
$this
->
record
[
'level'
]
-
$oldLevel
);
}
}
/**
...
...
@@ -400,7 +572,15 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
moveAsNextSiblingOf
(
Doctrine_Record
$dest
)
{
$this
->
updateNode
(
$dest
->
getNode
()
->
getRightValue
()
+
1
);
if
(
$dest
->
getNode
()
->
getRootValue
()
!=
$this
->
getRootValue
())
{
// Move between trees
$this
->
_moveBetweenTrees
(
$dest
,
$dest
->
getNode
()
->
getRightValue
()
+
1
,
__FUNCTION__
);
}
else
{
// Move within tree
$oldLevel
=
$this
->
record
[
'level'
];
$this
->
record
[
'level'
]
=
$dest
[
'level'
];
$this
->
updateNode
(
$dest
->
getNode
()
->
getRightValue
()
+
1
,
$this
->
record
[
'level'
]
-
$oldLevel
);
}
}
/**
...
...
@@ -409,7 +589,15 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
moveAsFirstChildOf
(
Doctrine_Record
$dest
)
{
$this
->
updateNode
(
$dest
->
getNode
()
->
getLeftValue
()
+
1
);
if
(
$dest
->
getNode
()
->
getRootValue
()
!=
$this
->
getRootValue
())
{
// Move between trees
$this
->
_moveBetweenTrees
(
$dest
,
$dest
->
getNode
()
->
getLeftValue
()
+
1
,
__FUNCTION__
);
}
else
{
// Move within tree
$oldLevel
=
$this
->
record
[
'level'
];
$this
->
record
[
'level'
]
=
$dest
[
'level'
]
+
1
;
$this
->
updateNode
(
$dest
->
getNode
()
->
getLeftValue
()
+
1
,
$this
->
record
[
'level'
]
-
$oldLevel
);
}
}
/**
...
...
@@ -418,7 +606,71 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
moveAsLastChildOf
(
Doctrine_Record
$dest
)
{
$this
->
updateNode
(
$dest
->
getNode
()
->
getRightValue
());
if
(
$dest
->
getNode
()
->
getRootValue
()
!=
$this
->
getRootValue
())
{
// Move between trees
$this
->
_moveBetweenTrees
(
$dest
,
$dest
->
getNode
()
->
getRightValue
(),
__FUNCTION__
);
}
else
{
// Move within tree
$oldLevel
=
$this
->
record
[
'level'
];
$this
->
record
[
'level'
]
=
$dest
[
'level'
]
+
1
;
$this
->
updateNode
(
$dest
->
getNode
()
->
getRightValue
(),
$this
->
record
[
'level'
]
-
$oldLevel
);
}
}
/**
* Makes this node a root node. Only used in multiple-root trees.
*
* @todo Exception handling/wrapping
*/
public
function
makeRoot
(
$newRootId
)
{
// TODO: throw exception instead?
if
(
$this
->
getLeftValue
()
==
1
||
!
$this
->
record
->
getTable
()
->
getTree
()
->
getAttribute
(
'hasManyRoots'
))
{
return
false
;
}
$oldRgt
=
$this
->
getRightValue
();
$oldLft
=
$this
->
getLeftValue
();
$oldRoot
=
$this
->
getRootValue
();
$oldLevel
=
$this
->
record
[
'level'
];
try
{
$conn
=
$this
->
record
->
getTable
()
->
getConnection
();
$conn
->
beginTransaction
();
// Detach from old tree (close gap in old tree)
$first
=
$oldRgt
+
1
;
$delta
=
$oldLft
-
$oldRgt
-
1
;
$this
->
shiftRLValues
(
$first
,
$delta
,
$this
->
getRootValue
());
// Set new lft/rgt/root/level values for root node
$this
->
setLeftValue
(
1
);
$this
->
setRightValue
(
$oldRgt
-
$oldLft
+
1
);
$this
->
setRootValue
(
$newRootId
);
$this
->
record
[
'level'
]
=
0
;
// Update descendants lft/rgt/root/level values
$diff
=
1
-
$oldLft
;
$newRoot
=
$newRootId
;
$componentName
=
$this
->
record
->
getTable
()
->
getComponentName
();
$rootColName
=
$this
->
record
->
getTable
()
->
getTree
()
->
getAttribute
(
'rootColumnName'
);
$q
=
new
Doctrine_Query
(
$conn
);
$q
=
$q
->
update
(
$componentName
)
->
set
(
$componentName
.
'.lft'
,
'lft + '
.
$diff
)
->
set
(
$componentName
.
'.rgt'
,
'rgt + '
.
$diff
)
->
set
(
$componentName
.
'.level'
,
'level - '
.
$oldLevel
)
->
set
(
$componentName
.
'.'
.
$rootColName
,
$newRoot
)
->
where
(
$componentName
.
'.lft > ? AND '
.
$componentName
.
'.rgt < ?'
,
array
(
$oldLft
,
$oldRgt
));
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$oldRoot
);
$q
->
execute
();
$conn
->
commit
();
}
catch
(
Exception
$e
)
{
$conn
->
rollback
();
throw
$e
;
}
}
/**
...
...
@@ -437,7 +689,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
isLeaf
()
{
return
((
$this
->
getRightValue
()
-
$this
->
getLeftValue
())
==
1
);
return
((
$this
->
getRightValue
()
-
$this
->
getLeftValue
())
==
1
);
}
/**
...
...
@@ -447,7 +699,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
isRoot
()
{
return
(
$this
->
getLeftValue
()
==
1
);
return
(
$this
->
getLeftValue
()
==
1
);
}
/**
...
...
@@ -457,7 +709,10 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
isEqualTo
(
Doctrine_Record
$subj
)
{
return
((
$this
->
getLeftValue
()
==
$subj
->
getNode
()
->
getLeftValue
())
and
(
$this
->
getRightValue
()
==
$subj
->
getNode
()
->
getRightValue
())
and
(
$this
->
getRootValue
()
==
$subj
->
getNode
()
->
getRootValue
()));
return
((
$this
->
getLeftValue
()
==
$subj
->
getNode
()
->
getLeftValue
())
&&
(
$this
->
getRightValue
()
==
$subj
->
getNode
()
->
getRightValue
())
&&
(
$this
->
getRootValue
()
==
$subj
->
getNode
()
->
getRootValue
())
);
}
/**
...
...
@@ -467,7 +722,9 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
isDescendantOf
(
Doctrine_Record
$subj
)
{
return
((
$this
->
getLeftValue
()
>
$subj
->
getNode
()
->
getLeftValue
())
and
(
$this
->
getRightValue
()
<
$subj
->
getNode
()
->
getRightValue
())
and
(
$this
->
getRootValue
()
==
$subj
->
getNode
()
->
getRootValue
()));
return
((
$this
->
getLeftValue
()
>
$subj
->
getNode
()
->
getLeftValue
())
&&
(
$this
->
getRightValue
()
<
$subj
->
getNode
()
->
getRightValue
())
&&
(
$this
->
getRootValue
()
==
$subj
->
getNode
()
->
getRootValue
()));
}
/**
...
...
@@ -477,7 +734,9 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
isDescendantOfOrEqualTo
(
Doctrine_Record
$subj
)
{
return
((
$this
->
getLeftValue
()
>=
$subj
->
getNode
()
->
getLeftValue
())
and
(
$this
->
getRightValue
()
<=
$subj
->
getNode
()
->
getRightValue
())
and
(
$this
->
getRootValue
()
==
$subj
->
getNode
()
->
getRootValue
()));
return
((
$this
->
getLeftValue
()
>=
$subj
->
getNode
()
->
getLeftValue
())
&&
(
$this
->
getRightValue
()
<=
$subj
->
getNode
()
->
getRightValue
())
&&
(
$this
->
getRootValue
()
==
$subj
->
getNode
()
->
getRootValue
()));
}
/**
...
...
@@ -485,26 +744,30 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*
* @return bool
*/
public
function
isValidNode
()
public
function
isValidNode
(
Doctrine_Record
$record
=
null
)
{
if
(
$record
===
null
)
{
return
(
$this
->
getRightValue
()
>
$this
->
getLeftValue
());
}
else
{
return
(
$record
->
getNode
()
->
getRightValue
()
>
$record
->
getNode
()
->
getLeftValue
());
}
}
/**
* deletes node and it's descendants
*
*
@todo Delete more efficiently. Wrap in transaction if needed.
*/
public
function
delete
()
{
// TODO: add the setting whether or not to delete descendants or relocate children
$q
=
$this
->
record
->
getTable
()
->
creat
eQuery
();
$oldRoot
=
$this
->
getRootValue
();
$q
=
$this
->
_tree
->
getBas
eQuery
();
$componentName
=
$this
->
record
->
getTable
()
->
getComponentName
();
$q
=
$q
->
where
(
"
$componentName
.lft >= ? AND
$componentName
.rgt <= ?"
,
array
(
$this
->
getLeftValue
(),
$this
->
getRightValue
()));
$q
=
$q
->
where
(
'base.lft >= ? AND base.rgt <= ?'
,
array
(
$this
->
getLeftValue
(),
$this
->
getRightValue
()));
$q
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$q
,
$
this
->
getRootValue
()
);
$q
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$q
,
$
oldRoot
);
$coll
=
$q
->
execute
();
...
...
@@ -512,7 +775,7 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
$first
=
$this
->
getRightValue
()
+
1
;
$delta
=
$this
->
getLeftValue
()
-
$this
->
getRightValue
()
-
1
;
$this
->
shiftRLValues
(
$first
,
$delta
);
$this
->
shiftRLValues
(
$first
,
$delta
,
$oldRoot
);
return
true
;
}
...
...
@@ -535,26 +798,39 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
* move node's and its children to location $destLeft and updates rest of tree
*
* @param int $destLeft destination left value
* @todo Wrap in transaction
*/
private
function
updateNode
(
$destLeft
)
private
function
updateNode
(
$destLeft
,
$levelDiff
)
{
$componentName
=
$this
->
record
->
getTable
()
->
getComponentName
();
$left
=
$this
->
getLeftValue
();
$right
=
$this
->
getRightValue
();
$rootId
=
$this
->
getRootValue
();
$treeSize
=
$right
-
$left
+
1
;
$this
->
shiftRLValues
(
$destLeft
,
$treeSize
);
// Make room in the new tree
$this
->
shiftRLValues
(
$destLeft
,
$treeSize
,
$rootId
);
if
(
$left
>=
$destLeft
){
// src was shifted too?
if
(
$left
>=
$destLeft
){
// src was shifted too?
$left
+=
$treeSize
;
$right
+=
$treeSize
;
}
// update level for descendants
$q
=
new
Doctrine_Query
();
$q
=
$q
->
update
(
$componentName
)
->
set
(
$componentName
.
'.level'
,
'level + '
.
$levelDiff
)
->
where
(
$componentName
.
'.lft > ? AND '
.
$componentName
.
'.rgt < ?'
,
array
(
$left
,
$right
));
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$rootId
);
$q
->
execute
();
// now there's enough room next to target to move the subtree
$this
->
shiftRLRange
(
$left
,
$right
,
$destLeft
-
$left
);
$this
->
shiftRLRange
(
$left
,
$right
,
$destLeft
-
$left
,
$rootId
);
// correct values after source
$this
->
shiftRLValues
(
$right
+
1
,
-
$treeSize
);
// correct values after source
(close gap in old tree)
$this
->
shiftRLValues
(
$right
+
1
,
-
$treeSize
,
$rootId
);
$this
->
record
->
save
();
$this
->
record
->
refresh
();
...
...
@@ -566,28 +842,27 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
* @param int $first First node to be shifted
* @param int $delta Value to be shifted by, can be negative
*/
private
function
shiftR
LValues
(
$first
,
$delta
,
$root_i
d
=
1
)
private
function
shiftR
lValues
(
$first
,
$delta
,
$rootI
d
=
1
)
{
$qLeft
=
$this
->
record
->
getTable
()
->
createQuery
();
$qRight
=
$this
->
record
->
getTable
()
->
createQuery
();
// TODO: Wrap in transaction
$qLeft
=
new
Doctrine_Query
();
$qRight
=
new
Doctrine_Query
();
// shift left columns
$qLeft
=
$qLeft
->
update
(
$this
->
record
->
getTable
()
->
getComponentName
())
->
set
(
'lft'
,
"lft +
$delta
"
)
->
where
(
'lft >= ?'
,
$first
);
$componentName
=
$this
->
record
->
getTable
()
->
getComponentName
();
$qLeft
=
$qLeft
->
update
(
$componentName
)
->
set
(
$componentName
.
'.lft'
,
'lft + '
.
$delta
)
->
where
(
$componentName
.
'.lft >= ?'
,
$first
);
$qLeft
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$qLeft
,
$root
_i
d
);
$qLeft
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$qLeft
,
$root
I
d
);
$resultLeft
=
$qLeft
->
execute
();
// shift right columns
$resultRight
=
$qRight
->
update
(
$
this
->
record
->
getTable
()
->
getComponentName
()
)
->
set
(
'rgt'
,
"rgt +
$delta
"
)
->
where
(
'
rgt >= ?'
,
$first
);
$resultRight
=
$qRight
->
update
(
$
componentName
)
->
set
(
$componentName
.
'.rgt'
,
'rgt + '
.
$delta
)
->
where
(
$componentName
.
'.
rgt >= ?'
,
$first
);
$qRight
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$qRight
,
$root
_i
d
);
$qRight
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$qRight
,
$root
I
d
);
$resultRight
=
$qRight
->
execute
();
}
...
...
@@ -600,28 +875,27 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
* @param int $last Last node to be shifted (L value)
* @param int $delta Value to be shifted by, can be negative
*/
private
function
shiftR
LRange
(
$first
,
$last
,
$delta
,
$root_i
d
=
1
)
private
function
shiftR
lRange
(
$first
,
$last
,
$delta
,
$rootI
d
=
1
)
{
$qLeft
=
$this
->
record
->
getTable
()
->
createQuery
();
$qRight
=
$this
->
record
->
getTable
()
->
createQuery
();
// TODO : Wrap in transaction
$qLeft
=
new
Doctrine_Query
();
$qRight
=
new
Doctrine_Query
();
// shift left column values
$qLeft
=
$qLeft
->
update
(
$this
->
record
->
getTable
()
->
getComponentName
())
->
set
(
'lft'
,
"lft +
$delta
"
)
->
where
(
'lft >= ? AND lft <= ?'
,
array
(
$first
,
$last
));
$componentName
=
$this
->
record
->
getTable
()
->
getComponentName
();
$qLeft
=
$qLeft
->
update
(
$componentName
)
->
set
(
$componentName
.
'.lft'
,
'lft + '
.
$delta
)
->
where
(
$componentName
.
'.lft >= ? AND '
.
$componentName
.
'.lft <= ?'
,
array
(
$first
,
$last
));
$qLeft
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$qLeft
,
$root
_i
d
);
$qLeft
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$qLeft
,
$root
I
d
);
$resultLeft
=
$qLeft
->
execute
();
// shift right column values
$qRight
=
$qRight
->
update
(
$
this
->
record
->
getTable
()
->
getComponentName
()
)
->
set
(
'rgt'
,
"rgt +
$delta
"
)
->
where
(
'rgt >= ? AND
rgt <= ?'
,
array
(
$first
,
$last
));
$qRight
=
$qRight
->
update
(
$
componentName
)
->
set
(
$componentName
.
'.rgt'
,
'rgt + '
.
$delta
)
->
where
(
$componentName
.
'.rgt >= ? AND '
.
$componentName
.
'.
rgt <= ?'
,
array
(
$first
,
$last
));
$qRight
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$qRight
,
$root
_i
d
);
$qRight
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$qRight
,
$root
I
d
);
$resultRight
=
$qRight
->
execute
();
}
...
...
@@ -673,18 +947,17 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
getLevel
()
{
if
(
!
isset
(
$this
->
level
))
{
$q
=
$this
->
record
->
getTable
()
->
creat
eQuery
();
$q
=
$q
->
where
(
'
lft < ? AND
rgt > ?'
,
array
(
$this
->
getLeftValue
(),
$this
->
getRightValue
()));
if
(
!
isset
(
$this
->
level
))
{
$componentName
=
$this
->
record
->
getTable
()
->
getComponentName
();
$q
=
$this
->
_tree
->
getBas
eQuery
();
$q
=
$q
->
where
(
'
base.lft < ? AND base.
rgt > ?'
,
array
(
$this
->
getLeftValue
(),
$this
->
getRightValue
()));
$q
=
$this
->
record
->
getTable
()
->
getTree
()
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$q
=
$this
->
_tree
->
returnQueryWithRootId
(
$q
,
$this
->
getRootValue
());
$coll
=
$q
->
execute
();
$this
->
level
=
$coll
->
count
()
?
$coll
->
count
(
)
:
0
;
$this
->
level
=
count
(
$coll
)
?
count
(
$coll
)
:
0
;
}
return
$this
->
level
;
}
...
...
@@ -704,9 +977,9 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
getRootValue
()
{
if
(
$this
->
record
->
getTable
()
->
getTree
()
->
getAttribute
(
'has_many_roots'
))
return
$this
->
record
->
get
(
$this
->
record
->
getTable
()
->
getTree
()
->
getAttribute
(
'root_column_n
ame'
));
if
(
$this
->
_tree
->
getAttribute
(
'hasManyRoots'
))
{
return
$this
->
record
->
get
(
$this
->
_tree
->
getAttribute
(
'rootColumnN
ame'
));
}
return
1
;
}
...
...
@@ -717,7 +990,8 @@ class Doctrine_Node_NestedSet extends Doctrine_Node implements Doctrine_Node_Int
*/
public
function
setRootValue
(
$value
)
{
if
(
$this
->
record
->
getTable
()
->
getTree
()
->
getAttribute
(
'has_many_roots'
))
$this
->
record
->
set
(
$this
->
record
->
getTable
()
->
getTree
()
->
getAttribute
(
'root_column_name'
),
$value
);
if
(
$this
->
_tree
->
getAttribute
(
'hasManyRoots'
))
{
$this
->
record
->
set
(
$this
->
_tree
->
getAttribute
(
'rootColumnName'
),
$value
);
}
}
}
draft/Tree/NestedSet.php
View file @
75613eb4
<?php
/*
* $Id$
* $Id
: NestedSet.php 1600 2007-06-07 19:17:56Z romanb
$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
...
...
@@ -26,11 +26,13 @@
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @since 1.0
* @version $Revision$
* @version $Revision
: 1600
$
* @author Joe Simms <joe.simms@websites4.com>
*/
class
Doctrine_Tree_NestedSet
extends
Doctrine_Tree
implements
Doctrine_Tree_Interface
{
private
$_baseQuery
;
/**
* constructor, creates tree with reference to table and sets default root options
*
...
...
@@ -40,8 +42,9 @@ class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Int
public
function
__construct
(
Doctrine_Table
$table
,
$options
)
{
// set default many root attributes
$options
[
'has_many_roots'
]
=
isset
(
$options
[
'has_many_roots'
])
?
$options
[
'has_many_roots'
]
:
false
;
$options
[
'root_column_name'
]
=
isset
(
$options
[
'root_column_name'
])
?
$options
[
'root_column_name'
]
:
'root_id'
;
$options
[
'hasManyRoots'
]
=
isset
(
$options
[
'hasManyRoots'
])
?
$options
[
'hasManyRoots'
]
:
false
;
if
(
$options
[
'hasManyRoots'
])
$options
[
'rootColumnName'
]
=
isset
(
$options
[
'rootColumnName'
])
?
$options
[
'rootColumnName'
]
:
'root_id'
;
parent
::
__construct
(
$table
,
$options
);
}
...
...
@@ -53,12 +56,13 @@ class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Int
*/
public
function
setTableDefinition
()
{
if
(
$
this
->
getAttribute
(
'has_many_roots
'
))
{
$this
->
table
->
setColumn
(
$
this
->
getAttribute
(
'root_column_name'
),
"integer"
,
11
);
if
(
$
root
=
$this
->
getAttribute
(
'rootColumnName
'
))
{
$this
->
table
->
setColumn
(
$
root
,
'integer'
,
4
);
}
$this
->
table
->
setColumn
(
"lft"
,
"integer"
,
11
);
$this
->
table
->
setColumn
(
"rgt"
,
"integer"
,
11
);
$this
->
table
->
setColumn
(
'lft'
,
'integer'
,
4
);
$this
->
table
->
setColumn
(
'rgt'
,
'integer'
,
4
);
$this
->
table
->
setColumn
(
'level'
,
'integer'
,
2
);
}
/**
...
...
@@ -72,8 +76,8 @@ class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Int
$record
=
$this
->
table
->
create
();
}
// if tree is many roots, then get next root id
if
(
$
this
->
getAttribute
(
'has_many_roots'
)
)
{
// if tree is many roots,
and no root id has been set,
then get next root id
if
(
$
root
=
$this
->
getAttribute
(
'hasManyRoots'
)
&&
$record
->
getNode
()
->
getRootValue
()
<=
0
)
{
$record
->
getNode
()
->
setRootValue
(
$this
->
getNextRootId
());
}
...
...
@@ -89,103 +93,110 @@ class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Int
* returns root node
*
* @return object $record instance of Doctrine_Record
* @deprecated Use fetchRoot()
*/
public
function
findRoot
(
$rootId
=
1
)
{
$q
=
$this
->
table
->
createQuery
();
$q
=
$q
->
where
(
'lft = ?'
,
1
);
return
$this
->
fetchRoot
(
$rootId
);
}
/**
* Fetches a/the root node.
*
* @param integer $rootId
*/
public
function
fetchRoot
(
$rootId
=
1
)
{
$q
=
$this
->
getBaseQuery
();
$q
=
$q
->
where
(
'base.lft = ?'
,
1
);
// if tree has many roots, then specify root id
$q
=
$this
->
returnQueryWithRootId
(
$q
,
$rootId
);
$data
=
$q
->
execute
();
$root
=
$q
->
execute
()
->
getFirst
();
// if no record is returned, create record
if
(
!
$root
)
{
$root
=
$this
->
table
->
create
();
if
(
count
(
$data
)
<=
0
)
{
return
false
;
}
// set level to prevent additional query to determine level
$root
->
getNode
()
->
setLevel
(
0
);
if
(
$data
instanceof
Doctrine_Collection
)
{
$root
=
$data
->
getFirst
();
$root
[
'level'
]
=
0
;
}
else
if
(
is_array
(
$data
))
{
$root
=
array_shift
(
$data
);
$root
[
'level'
]
=
0
;
}
else
{
throw
new
Doctrine_Tree_Exception
(
"Unexpected data structure returned."
);
}
return
$root
;
}
/**
*
optimised method to returns iterator for traversal of the entire tree from root
*
Fetches a tree.
*
* @param array $options
o
ptions
* @return
object $iterator instance of Doctrine_Node_NestedSet_PreOrderIterator
* @param array $options
O
ptions
* @return
mixed The tree or FALSE if the tree could not be found.
*/
public
function
fetchTree
(
$options
=
array
())
{
// fetch tree
$q
=
$this
->
table
->
createQuery
();
$q
=
$this
->
getBaseQuery
();
$componentName
=
$this
->
table
->
getComponentName
();
$q
=
$q
->
where
(
'lft >= ?'
,
1
)
->
orderBy
(
'lft asc'
);
$q
=
$q
->
addWhere
(
"base.lft >= ?"
,
1
);
// if tree has many roots, then specify root id
$rootId
=
isset
(
$options
[
'root_id'
])
?
$options
[
'root_id'
]
:
'1'
;
$q
=
$this
->
returnQueryWithRootId
(
$q
,
$rootId
);
$tree
=
$q
->
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
);
if
(
is_array
(
$rootId
))
{
$q
->
orderBy
(
"base."
.
$this
->
getAttribute
(
'rootColumnName'
)
.
", base.lft ASC"
);
}
else
{
$q
->
orderBy
(
"base.lft ASC"
);
}
// set collection for iterator
$options
[
'collection'
]
=
$tree
;
$q
=
$this
->
returnQueryWithRootId
(
$q
,
$rootId
);
$tree
=
$q
->
execute
()
;
return
$root
->
getNode
()
->
traverse
(
'Pre'
,
$options
);
if
(
count
(
$tree
)
<=
0
)
{
return
false
;
}
// TODO: no default return value or exception thrown?
return
$tree
;
}
/**
*
optimised method that returns iterator for traversal of the tree from the given record primary key
*
Fetches a branch of a tree.
*
* @param mixed $pk primary key as used by table::find() to locate node to traverse tree from
* @param array $options options
* @return iterator instance of Doctrine_Node_<Implementation>_PreOrderIterator
* @param array $options Options.
* @return mixed The branch or FALSE if the branch could not be found.
* @todo Only fetch the lft and rgt values of the initial record. more is not needed.
*/
public
function
fetchBranch
(
$pk
,
$options
=
array
())
{
$record
=
$this
->
table
->
find
(
$pk
);
if
(
$record
->
exists
())
{
$options
=
array_merge
(
array
(
'include_record'
=>
true
),
$options
);
return
$record
->
getNode
()
->
traverse
(
'Pre'
,
$options
)
;
if
(
!
(
$record
instanceof
Doctrine_Record
)
||
!
$record
->
exists
())
{
// TODO: if record doesn't exist, throw exception or similar?
return
false
;
}
//$depth = isset($options['depth']) ? $options['depth'] : null;
// TODO: if record doesn't exist, throw exception or similar?
$q
=
$this
->
getBaseQuery
();
$params
=
array
(
$record
->
get
(
'lft'
),
$record
->
get
(
'rgt'
));
$q
->
where
(
"base.lft >= ? AND base.rgt <= ?"
,
$params
)
->
orderBy
(
"base.lft asc"
);
$q
=
$this
->
returnQueryWithRootId
(
$q
,
$record
->
getNode
()
->
getRootValue
());
return
$q
->
execute
();
}
/**
* fetch root nodes
* Fetches all root nodes. If the tree has only one root this is the same as
* fetchRoot().
*
* @return
collection Doctrine_Collection
* @return
mixed The root nodes.
*/
public
function
fetchRoots
()
{
$q
=
$this
->
table
->
creat
eQuery
();
$q
=
$q
->
where
(
'lft = ?'
,
1
);
$q
=
$this
->
getBas
eQuery
();
$q
=
$q
->
where
(
'
base.
lft = ?'
,
1
);
return
$q
->
execute
();
}
...
...
@@ -207,7 +218,7 @@ class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Int
public
function
getMaxRootId
()
{
$component
=
$this
->
table
->
getComponentName
();
$column
=
$this
->
getAttribute
(
'root_column_n
ame'
);
$column
=
$this
->
getAttribute
(
'rootColumnN
ame'
);
// cannot get this dql to work, cannot retrieve result using $coll[0]->max
//$dql = "SELECT MAX(c.$column) FROM $component c";
...
...
@@ -232,10 +243,99 @@ class Doctrine_Tree_NestedSet extends Doctrine_Tree implements Doctrine_Tree_Int
*/
public
function
returnQueryWithRootId
(
$query
,
$rootId
=
1
)
{
if
(
$this
->
getAttribute
(
'has_many_roots'
))
{
$query
->
addWhere
(
$this
->
getAttribute
(
'root_column_name'
)
.
' = ?'
,
$rootId
);
if
(
$root
=
$this
->
getAttribute
(
'rootColumnName'
))
{
if
(
is_array
(
$rootId
))
{
$query
->
addWhere
(
$root
.
' IN ('
.
implode
(
','
,
array_fill
(
0
,
count
(
$rootId
),
'?'
))
.
')'
,
$rootId
);
}
else
{
$query
->
addWhere
(
$root
.
' = ?'
,
$rootId
);
}
}
return
$query
;
}
/**
* Enter description here...
*
* @param array $options
* @return unknown
*/
public
function
getBaseQuery
()
{
if
(
!
isset
(
$this
->
_baseQuery
))
{
$this
->
_baseQuery
=
$this
->
_createBaseQuery
();
}
return
clone
$this
->
_baseQuery
;
}
/**
* Enter description here...
*
*/
private
function
_createBaseQuery
()
{
$q
=
new
Doctrine_Query
();
$q
->
select
(
"base.*"
)
->
from
(
$this
->
table
->
getComponentName
()
.
" base"
);
return
$q
;
}
/**
* Enter description here...
*
* @param Doctrine_Query $query
*/
public
function
setBaseQuery
(
Doctrine_Query
$query
)
{
$query
->
addSelect
(
"base.lft, base.rgt, base.level"
);
if
(
$this
->
getAttribute
(
'rootColumnName'
))
{
$query
->
addSelect
(
"base."
.
$this
->
getAttribute
(
'rootColumnName'
));
}
$this
->
_baseQuery
=
$query
;
}
/**
* Enter description here...
*
*/
public
function
resetBaseQuery
()
{
$this
->
_baseQuery
=
null
;
}
/**
* Enter description here...
*
* @param unknown_type $graph
*/
/*
public function computeLevels($tree)
{
$right = array();
$isArray = is_array($tree);
$rootColumnName = $this->getAttribute('rootColumnName');
for ($i = 0, $count = count($tree); $i < $count; $i++) {
if ($rootColumnName && $i > 0 && $tree[$i][$rootColumnName] != $tree[$i-1][$rootColumnName]) {
$right = array();
}
if (count($right) > 0) {
while (count($right) > 0 && $right[count($right)-1] < $tree[$i]['rgt']) {
//echo count($right);
array_pop($right);
}
}
if ($isArray) {
$tree[$i]['level'] = count($right);
} else {
$tree[$i]->getNode()->setLevel(count($right));
}
$right[] = $tree[$i]['rgt'];
}
return $tree;
}
*/
}
draft/nestedset_changes.tree
0 → 100644
View file @
75613eb4
Outline of the changes to the NestedSet
Structural changes:
In addition to the lft and rgt columns there's now a column 'level' that gets automatically added to your model
when you use the nestedset. As with the lft and rgt values should never modify this value. All changes to this field
are handled transparently for you when you move nodes around or insert new ones.
General API changes:
Nearly all of the methods of the Node and Tree interfaces now return FALSE if no parent/child/sibling/ancestor(s)/
descendant(s) were found. In addition there have been some additions to certain methods. i.e. getAncestors() now
has a parameter that allows you to retrieve the ancestors up to a certain level.
Fetching relations together with nodes:
This is how you can temporarily set your own query as the base query that is used by the nestedset.
The nestedset implementation now uses the latest DQL syntax. Therefore it now uses a reserved alias
'base' that identifies the tree component. Through that alias you can even select which fields you
need of the nodes themselves, in addition to the fields you need from related components.
Note that you dont need to specify the special columns 'lft', 'rgt' and 'level' in any of your
queries. These are always added automatically since they're essential for the tree structure.
Example:
$query->select("base.name, le.topic, a.name")->from("VForum_Model_ForumNode base")
->leftJoin("base.lastEntry le")
->leftJoin("le.author a")
->setHydrationMode(Doctrine_Query::HYDRATE_ARRAY);
$tree = $conn->getTable('VForum_Model_Category')->getTree();
$tree->setBaseQuery($query);
$tree = $tree->fetchTree();
$tree->resetBaseQuery();
This example shows that even array fetching is possible. And since the level is now stored
in the database and is a regular field of every record you can access it like every other field
($record['level']), regardless of the hydration mode used (objects/arrays).
Note that you can't modify clauses like where or orderby. These will be overridden by the appropriate method
you're calling. i.e. if you call getDescendants() the WHERE part results from the fact that you
want the descendants and the ORDER BY part is always used to retrieve the nodes in the order they appear in the tree,
so that you can easily traverse and display the tree structure.
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment