Skip to content

Instantly share code, notes, and snippets.

@phalcon
Last active March 15, 2017 15:05
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save phalcon/4758021 to your computer and use it in GitHub Desktop.
Save phalcon/4758021 to your computer and use it in GitHub Desktop.
<?php
class Category extends Phalcon\Mvc\Model
{
public $id;
public $lft;
public $rgt;
public $level;
public $title;
public function initialize()
{
$this->addBehavior(new NestedSetBehavior(array(
'leftAttribute' => 'lft',
'rightAttribute' => 'rgt',
'levelAttribute' => 'level'
)));
}
}
<?php
$root = new Category();
$root->title = 'Cars';
$root->saveNode();
$category1 = new Category();
$category1->title = 'Ford';
$category2 = new Category();
$category2->title = 'Mercedes';
$category3 = new Category();
$category3->title = 'Audi';
$root = Category::findFirst();
$category1->appendTo($root);
<?php
/**
* NestedSetBehavior class file.
*
* @author Alexander Kochetov <creocoder@gmail.com>
* @link https://github.com/yiiext/nested-set-behavior
*/
/**
* Provides nested set functionality for a model.
*
* @version 1.06
* @package yiiext.behaviors.model.trees
*/
class NestedSetBehavior extends Phalcon\Mvc\Model\Behavior
{
private $_owner;
private $_hasManyRoots = false;
private $_rootAttribute = 'root';
private $_leftAttribute = 'lft';
private $_rightAttribute = 'rgt';
private $_levelAttribute = 'level';
private $_ignoreEvent = false;
private $_deleted = false;
private $_id;
private static $_cached;
private static $_c = 0;
public function __construct($options)
{
if (isset($options['rootAttribute'])) {
$this->_rootAttribute = $options['rootAttribute'];
}
if (isset($options['leftAttribute'])) {
$this->_leftAttribute = $options['leftAttribute'];
}
if (isset($options['rightAttribute'])) {
$this->_rightAttribute = $options['rightAttribute'];
}
if (isset($options['levelAttribute'])) {
$this->_levelAttribute = $options['levelAttribute'];
}
}
/**
* Named scope. Gets descendants for node.
* @param int $depth the depth.
* @return Phalcon\Mvc\ModelInterface the owner.
*/
public function descendants($depth=null)
{
$owner = $this->getOwner();
$db = $owner->getDbConnection();
$criteria = $owner->getDbCriteria();
$alias = $db->quoteColumnName($owner->getTableAlias());
$criteria->mergeWith(array(
'condition' => $alias . '.' . $db->quoteColumnName($this->_leftAttribute) . '>' . $owner->{$this->_leftAttribute}.
' AND '.$alias . '.' . $db->quoteColumnName($this->_rightAttribute) . '<' . $owner->{$this->_rightAttribute},
'order'=> $alias . '.' . $db->quoteColumnName($this->_leftAttribute),
));
if ($depth!==null) {
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_levelAttribute).'<='.($owner->{$this->_levelAttribute}+$depth));
}
if($this->_hasManyRoots) {
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount);
$criteria->params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++] = $owner->{$this->_rootAttribute};
}
return $owner;
}
/**
* Named scope. Gets children for node (direct descendants only).
* @return Phalcon\Mvc\ModelInterface the owner.
*/
public function children()
{
return $this->descendants(1);
}
/**
* Named scope. Gets ancestors for node.
* @param int $depth the depth.
* @return Phalcon\Mvc\ModelInterface the owner.
*/
public function ancestors($depth=null)
{
$owner=$this->getOwner();
$db=$owner->getDbConnection();
$criteria=$owner->getDbCriteria();
$alias=$db->quoteColumnName($owner->getTableAlias());
$criteria->mergeWith(array(
'condition'=>$alias.'.'.$db->quoteColumnName($this->_leftAttribute).'<'.$owner->{$this->_leftAttribute}.
' AND '.$alias.'.'.$db->quoteColumnName($this->_rightAttribute).'>'.$owner->{$this->_rightAttribute},
'order'=>$alias.'.'.$db->quoteColumnName($this->_leftAttribute),
));
if($depth!==null)
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_levelAttribute).'>='.($owner->{$this->_levelAttribute}-$depth));
if($this->_hasManyRoots)
{
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount);
$criteria->params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++]=$owner->{$this->_rootAttribute};
}
return $owner;
}
/**
* Named scope. Gets root node(s).
*
* @return Phalcon\Mvc\ModelInterface[] the owner.
*/
public function roots()
{
$owner = $this->getOwner();
return $owner::find($this->_leftAttribute . ' = 1');
}
/**
* Named scope. Gets parent of node.
*
* @return Phalcon\Mvc\ModelInterface the owner.
*/
public function parent()
{
$owner = $this->getOwner();
$db = $owner->getDbConnection();
$criteria = $owner->getDbCriteria();
$alias = $db->quoteColumnName($owner->getTableAlias());
$criteria->mergeWith(array(
'condition'=>$alias.'.'.$db->quoteColumnName($this->_leftAttribute).'<'.$owner->{$this->_leftAttribute}.
' AND '.$alias.'.'.$db->quoteColumnName($this->_rightAttribute).'>'.$owner->{$this->_rightAttribute},
'order'=>$alias.'.'.$db->quoteColumnName($this->_rightAttribute),
));
if($this->_hasManyRoots)
{
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount);
$criteria->params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++]=$owner->{$this->_rootAttribute};
}
return $owner;
}
/**
* Named scope. Gets previous sibling of node.
* @return Phalcon\Mvc\ModelInterface the owner.
*/
public function prev()
{
$owner=$this->getOwner();
$db=$owner->getDbConnection();
$criteria=$owner->getDbCriteria();
$alias=$db->quoteColumnName($owner->getTableAlias());
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_rightAttribute).'='.($owner->{$this->_leftAttribute}-1));
if($this->_hasManyRoots)
{
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount);
$criteria->params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++]=$owner->{$this->_rootAttribute};
}
return $owner;
}
/**
* Named scope. Gets next sibling of node.
* @return Phalcon\Mvc\ModelInterface the owner.
*/
public function next()
{
$owner=$this->getOwner();
$db = $owner->getDbConnection();
$criteria = $owner->getDbCriteria();
$alias = $db->quoteColumnName($owner->getTableAlias());
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_leftAttribute).'='.($owner->{$this->_rightAttribute}+1));
if($this->_hasManyRoots)
{
$criteria->addCondition($alias.'.'.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount);
$criteria->params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++]=$owner->{$this->_rootAttribute};
}
return $owner;
}
/**
* Create root node if multiple-root tree mode. Update node if it's not new.
*
* @param boolean $runValidation whether to perform validation.
* @param boolean $attributes list of attributes.
* @return boolean whether the saving succeeds.
*/
public function save($attributes=null)
{
$owner = $this->getOwner();
if (!$owner->id) {
return $this->makeRoot($attributes);
}
return $owner->update($attributes);
}
/**
* Create root node if multiple-root tree mode. Update node if it's not new.
*
* @param boolean $runValidation whether to perform validation.
* @param boolean $attributes list of attributes.
* @return boolean whether the saving succeeds.
*/
public function saveNode($attributes=null)
{
return $this->save($attributes);
}
/**
* Deletes node and it's descendants.
* @return boolean whether the deletion is successful.
* @throws CDbException
* @throws Exception
*/
public function delete()
{
$owner=$this->getOwner();
if ($owner->getIsNewRecord()) {
throw new Phalcon\Mvc\Model\Exception('The node cannot be deleted because it is new.');
}
if($this->getIsDeletedRecord()) {
throw new Phalcon\Mvc\Model\Exception('The node cannot be deleted because it is already deleted.');
}
$db = $owner->getDbConnection();
if($db->getCurrentTransaction()===null)
$transaction=$db->beginTransaction();
try
{
if($owner->isLeaf())
{
$this->_ignoreEvent=true;
$result=$owner->delete();
$this->_ignoreEvent=false;
}
else
{
$condition=$db->quoteColumnName($this->_leftAttribute).'>='.$owner->{$this->_leftAttribute}.' AND '.
$db->quoteColumnName($this->_rightAttribute).'<='.$owner->{$this->_rightAttribute};
$params=array();
if($this->_hasManyRoots)
{
$condition.=' AND '.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount;
$params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++]=$owner->{$this->_rootAttribute};
}
$result=$owner->deleteAll($condition,$params)>0;
}
if(!$result)
{
if(isset($transaction))
$transaction->rollback();
return false;
}
$this->shiftLeftRight($owner->{$this->_rightAttribute}+1,$owner->{$this->_leftAttribute}-$owner->{$this->_rightAttribute}-1);
if(isset($transaction))
$transaction->commit();
$this->correctCachedOnDelete();
}
catch(Exception $e)
{
if(isset($transaction))
$transaction->rollback();
throw $e;
}
return true;
}
/**
* Deletes node and it's descendants.
* @return boolean whether the deletion is successful.
*/
public function deleteNode()
{
return $this->delete();
}
/**
* Prepends node to target as first child.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @param array $attributes list of attributes.
* @return boolean whether the prepending succeeds.
*/
public function prependTo($target, $attributes=null)
{
return $this->addNode($target,$target->{$this->_leftAttribute}+1, 1, $attributes);
}
/**
* Prepends target to node as first child.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @param boolean $runValidation whether to perform validation.
* @param array $attributes list of attributes.
* @return boolean whether the prepending succeeds.
*/
public function prepend($target, $attributes=null)
{
return $target->prependTo($this->getOwner(), $attributes);
}
/**
* Appends node to target as last child.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @param array $attributes list of attributes.
* @return boolean whether the appending succeeds.
*/
public function appendTo($target, $attributes=null)
{
return $this->addNode($target, $target->{$this->_rightAttribute}, 1, $attributes);
}
/**
* Appends target to node as last child.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @param array $attributes list of attributes.
* @return boolean whether the appending succeeds.
*/
public function append($target, $attributes=null)
{
return $target->appendTo($this->getOwner(), $attributes);
}
/**
* Inserts node as previous sibling of target.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @param array $attributes list of attributes.
* @return boolean whether the inserting succeeds.
*/
public function insertBefore($target, $attributes=null)
{
return $this->addNode($target, $target->{$this->_leftAttribute}, 0, $attributes);
}
/**
* Inserts node as next sibling of target.
* @param Phalcon\Mvc\ModelInterface $target the target.
* @param array $attributes list of attributes.
* @return boolean whether the inserting succeeds.
*/
public function insertAfter($target,$runValidation=true,$attributes=null)
{
return $this->addNode($target, $target->{$this->_rightAttribute} + 1, 0, $attributes);
}
/**
* Move node as previous sibling of target.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @return boolean whether the moving succeeds.
*/
public function moveBefore($target)
{
return $this->moveNode($target, $target->{$this->_leftAttribute}, 0);
}
/**
* Move node as next sibling of target.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @return boolean whether the moving succeeds.
*/
public function moveAfter($target)
{
return $this->moveNode($target, $target->{$this->_rightAttribute} + 1, 0);
}
/**
* Move node as first child of target.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @return boolean whether the moving succeeds.
*/
public function moveAsFirst($target)
{
return $this->moveNode($target, $target->{$this->_leftAttribute}+1, 1);
}
/**
* Move node as last child of target.
*
* @param Phalcon\Mvc\ModelInterface $target the target.
* @return boolean whether the moving succeeds.
*/
public function moveAsLast($target)
{
return $this->moveNode($target, $target->{$this->_rightAttribute}, 1);
}
/**
* Move node as new root.
* @return boolean whether the moving succeeds.
* @throws CDbException
* @throws CException
* @throws Exception
*/
public function moveAsRoot()
{
$owner = $this->getOwner();
if (!$this->_hasManyRoots) {
throw new Phalcon\Mvc\Model\Exception('Many roots mode is off.');
}
if ($owner->getIsNewRecord()) {
throw new Phalcon\Mvc\Model\Exception('The node should not be new record.');
}
if ($this->getIsDeletedRecord()) {
throw new Phalcon\Mvc\Model\Exception('The node should not be deleted.');
}
if ($owner->isRoot()) {
throw new Phalcon\Mvc\Model\Exception('The node already is root node.');
}
$db = $owner->getDbConnection();
if($db->getCurrentTransaction()===null)
$transaction=$db->beginTransaction();
try
{
$left=$owner->{$this->_leftAttribute};
$right=$owner->{$this->_rightAttribute};
$levelDelta=1-$owner->{$this->_levelAttribute};
$delta=1-$left;
$owner->updateAll(
array(
$this->_leftAttribute=>new CDbExpression($db->quoteColumnName($this->_leftAttribute).sprintf('%+d',$delta)),
$this->_rightAttribute=>new CDbExpression($db->quoteColumnName($this->_rightAttribute).sprintf('%+d',$delta)),
$this->_levelAttribute=>new CDbExpression($db->quoteColumnName($this->_levelAttribute).sprintf('%+d',$levelDelta)),
$this->_rootAttribute=>$owner->getPrimaryKey(),
),
$db->quoteColumnName($this->_leftAttribute).'>='.$left.' AND '.
$db->quoteColumnName($this->_rightAttribute).'<='.$right.' AND '.
$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount,
array(CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++=>$owner->{$this->_rootAttribute}));
$this->shiftLeftRight($right+1,$left-$right-1);
if(isset($transaction))
$transaction->commit();
$this->correctCachedOnMoveBetweenTrees(1,$levelDelta,$owner->getPrimaryKey());
}
catch(Exception $e)
{
if(isset($transaction))
$transaction->rollback();
throw $e;
}
return true;
}
/**
* Determines if node is descendant of subject node.
* @param Phalcon\Mvc\ModelInterface $subj the subject node.
* @return boolean whether the node is descendant of subject node.
*/
public function isDescendantOf($subj)
{
$owner=$this->getOwner();
$result=($owner->{$this->_leftAttribute}>$subj->{$this->_leftAttribute})
&& ($owner->{$this->_rightAttribute}<$subj->{$this->_rightAttribute});
if($this->_hasManyRoots)
$result=$result && ($owner->{$this->_rootAttribute}===$subj->{$this->_rootAttribute});
return $result;
}
/**
* Determines if node is leaf.
* @return boolean whether the node is leaf.
*/
public function isLeaf()
{
$owner=$this->getOwner();
return $owner->{$this->_rightAttribute}-$owner->{$this->_leftAttribute}===1;
}
/**
* Determines if node is root.
* @return boolean whether the node is root.
*/
public function isRoot()
{
return $this->getOwner()->{$this->_leftAttribute}==1;
}
/**
* Returns if the current node is deleted.
* @return boolean whether the node is deleted.
*/
public function getIsDeletedRecord()
{
return $this->_deleted;
}
/**
* Sets if the current node is deleted.
* @param boolean $value whether the node is deleted.
*/
public function setIsDeletedRecord($value)
{
$this->_deleted=$value;
}
/**
* Handle 'afterConstruct' event of the owner.
* @param CEvent $event event parameter.
*/
public function afterConstruct($event)
{
$owner=$this->getOwner();
self::$_cached[get_class($owner)][$this->_id=self::$_c++]=$owner;
}
/**
* Handle 'afterFind' event of the owner.
* @param CEvent $event event parameter.
*/
public function afterFind($event)
{
$owner=$this->getOwner();
self::$_cached[get_class($owner)][$this->_id=self::$_c++]=$owner;
}
/**
* @param int $key.
* @param int $delta.
*/
private function shiftLeftRight($key, $delta)
{
$owner = $this->getOwner();
foreach(array($this->_leftAttribute, $this->_rightAttribute) as $attribute)
{
$condition = $attribute.'>='.$key;
$params = array();
if($this->_hasManyRoots)
{
//I don't know how to translate this
$condition.=' AND '.$this->_rootAttribute.'='.CDbCriteria::$paramCount;
$params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++] = $owner->{$this->_rootAttribute};
}
//$owner->updateAll(array($attribute=>new CDbExpression($db->quoteColumnName($attribute).sprintf('%+d',$delta))),$condition,$params);
foreach ($owner::find($condition) as $record) {
$record->$attribute = sprintf('%+d',$delta);
$record->save();
}
}
}
/**
* @param Phalcon\Mvc\ModelInterface $target.
* @param int $key.
* @param int $levelUp.
* @param array $attributes.
* @return boolean.
* @throws CDbException
* @throws CException
* @throws Exception
*/
private function addNode($target, $key, $levelUp, $attributes)
{
$owner = $this->getOwner();
if (!$owner->getIsNewRecord()) {
throw new Phalcon\Mvc\Model\Exception('The node cannot be inserted because it is not new.');
}
if ($owner->getIsDeletedRecord()) {
throw new Phalcon\Mvc\Model\Exception('The node cannot be inserted because it is deleted.');
}
if ($target->getIsDeletedRecord()) {
throw new Phalcon\Mvc\Model\Exception('The node cannot be inserted because target node is deleted.');
}
if($owner == $target) {
throw new Phalcon\Mvc\Model\Exception('The target node should not be self.');
}
if(!$levelUp && $target->isRoot()) {
throw new Phalcon\Mvc\Model\Exception('The target node should not be root.');
}
if($this->_hasManyRoots) {
$owner->{$this->_rootAttribute} = $target->{$this->_rootAttribute};
}
$this->shiftLeftRight($key, 2);
$owner->{$this->_leftAttribute} = $key;
$owner->{$this->_rightAttribute} = $key + 1;
$owner->{$this->_levelAttribute} = $target->{$this->_levelAttribute} + $levelUp;
$result = $owner->create($attributes);
if (!$result) {
return false;
}
$this->correctCachedOnAddNode($key);
return true;
}
/**
* @param array $attributes.
* @return boolean.
* @throws CException
* @throws Exception
*/
private function makeRoot($attributes)
{
$owner = $this->getOwner();
$owner->{$this->_leftAttribute} = 1;
$owner->{$this->_rightAttribute} = 2;
$owner->{$this->_levelAttribute} = 1;
if($this->_hasManyRoots) {
$db = $owner->getConnection();
if ($db->isUnderTransaction()===null) {
$transaction = $db->beginTransaction();
}
try {
$result = $owner->create($attributes);
if (!$result) {
if (isset($transaction)) {
$transaction->rollback();
}
return false;
}
$pk = $owner->{$this->_rootAttribute} = $owner->getPrimaryKey();
$owner->updateByPk($pk, array($this->_rootAttribute=>$pk));
if(isset($transaction)) {
$transaction->commit();
}
} catch(Exception $e) {
$db->rollback();
throw $e;
}
} else {
if (count($owner->roots())) {
throw new Phalcon\Mvc\Model\Exception('Cannot create more than one root in single root mode.');
}
$result = $owner->create($attributes);
if (!$result) {
return false;
}
}
return true;
}
/**
* @param Phalcon\Mvc\ModelInterface $target.
* @param int $key.
* @param int $levelUp.
* @return boolean.
* @throws CDbException
* @throws CException
* @throws Exception
*/
private function moveNode($target,$key,$levelUp)
{
$owner=$this->getOwner();
if($owner->getIsNewRecord())
throw new Phalcon\Mvc\Model\Exception(Yii::t('yiiext','The node should not be new record.'));
if($this->getIsDeletedRecord())
throw new Phalcon\Mvc\Model\Exception(Yii::t('yiiext','The node should not be deleted.'));
if($target->getIsDeletedRecord())
throw new Phalcon\Mvc\Model\Exception(Yii::t('yiiext','The target node should not be deleted.'));
if($owner->equals($target))
throw new Phalcon\Mvc\Model\Exception(Yii::t('yiiext','The target node should not be self.'));
if($target->isDescendantOf($owner))
throw new Phalcon\Mvc\Model\Exception(Yii::t('yiiext','The target node should not be descendant.'));
if(!$levelUp && $target->isRoot())
throw new Phalcon\Mvc\Model\Exception(Yii::t('yiiext','The target node should not be root.'));
$db=$owner->getDbConnection();
if($db->getCurrentTransaction()===null)
$transaction=$db->beginTransaction();
try
{
$left=$owner->{$this->_leftAttribute};
$right=$owner->{$this->_rightAttribute};
$levelDelta=$target->{$this->_levelAttribute}-$owner->{$this->_levelAttribute}+$levelUp;
if($this->_hasManyRoots && $owner->{$this->_rootAttribute}!==$target->{$this->_rootAttribute})
{
foreach(array($this->_leftAttribute,$this->_rightAttribute) as $attribute)
{
$owner->updateAll(array($attribute=>new CDbExpression($db->quoteColumnName($attribute).sprintf('%+d',$right-$left+1))),
$db->quoteColumnName($attribute).'>='.$key.' AND '.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount,
array(CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++=>$target->{$this->_rootAttribute}));
}
$delta=$key-$left;
$owner->updateAll(
array(
$this->_leftAttribute=>new CDbExpression($db->quoteColumnName($this->_leftAttribute).sprintf('%+d',$delta)),
$this->_rightAttribute=>new CDbExpression($db->quoteColumnName($this->_rightAttribute).sprintf('%+d',$delta)),
$this->_levelAttribute=>new CDbExpression($db->quoteColumnName($this->_levelAttribute).sprintf('%+d',$levelDelta)),
$this->_rootAttribute=>$target->{$this->_rootAttribute},
),
$db->quoteColumnName($this->_leftAttribute).'>='.$left.' AND '.
$db->quoteColumnName($this->_rightAttribute).'<='.$right.' AND '.
$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount,
array(CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++=>$owner->{$this->_rootAttribute}));
$this->shiftLeftRight($right+1,$left-$right-1);
if(isset($transaction))
$transaction->commit();
$this->correctCachedOnMoveBetweenTrees($key,$levelDelta,$target->{$this->_rootAttribute});
}
else
{
$delta=$right-$left+1;
$this->shiftLeftRight($key,$delta);
if($left>=$key)
{
$left+=$delta;
$right+=$delta;
}
$condition=$db->quoteColumnName($this->_leftAttribute).'>='.$left.' AND '.$db->quoteColumnName($this->_rightAttribute).'<='.$right;
$params=array();
if($this->_hasManyRoots)
{
$condition.=' AND '.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount;
$params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++]=$owner->{$this->_rootAttribute};
}
$owner->updateAll(array($this->_levelAttribute=>new CDbExpression($db->quoteColumnName($this->_levelAttribute).sprintf('%+d',$levelDelta))),$condition,$params);
foreach(array($this->_leftAttribute,$this->_rightAttribute) as $attribute)
{
$condition=$db->quoteColumnName($attribute).'>='.$left.' AND '.$db->quoteColumnName($attribute).'<='.$right;
$params=array();
if($this->_hasManyRoots)
{
$condition.=' AND '.$db->quoteColumnName($this->_rootAttribute).'='.CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount;
$params[CDbCriteria::PARAM_PREFIX.CDbCriteria::$paramCount++]=$owner->{$this->_rootAttribute};
}
$owner->updateAll(array($attribute=>new CDbExpression($db->quoteColumnName($attribute).sprintf('%+d',$key-$left))),$condition,$params);
}
$this->shiftLeftRight($right+1,-$delta);
if(isset($transaction))
$transaction->commit();
$this->correctCachedOnMoveNode($key,$levelDelta);
}
}
catch(Exception $e)
{
if(isset($transaction))
$transaction->rollback();
throw $e;
}
return true;
}
/**
* Correct cache for {@link NestedSetBehavior::delete()} and {@link NestedSetBehavior::deleteNode()}.
*/
private function correctCachedOnDelete()
{
$owner = $this->getOwner();
$left = $owner->{$this->_leftAttribute};
$right = $owner->{$this->_rightAttribute};
$key = $right + 1;
$delta = $left - $right - 1;
foreach(self::$_cached[get_class($owner)] as $node)
{
if($node->getIsNewRecord() || $node->getIsDeletedRecord()) {
continue;
}
if ($this->_hasManyRoots && $owner->{$this->_rootAttribute} !== $node->{$this->_rootAttribute}) {
continue;
}
if ($node->{$this->_leftAttribute}>=$left && $node->{$this->_rightAttribute}<=$right) {
$node->setIsDeletedRecord(true);
} else {
if ($node->{$this->_leftAttribute} >= $key) {
$node->{$this->_leftAttribute} += $delta;
}
if ($node->{$this->_rightAttribute} >= $key) {
$node->{$this->_rightAttribute}+=$delta;
}
}
}
}
/**
* Correct cache for {@link NestedSetBehavior::addNode()}.
* @param int $key.
*/
private function correctCachedOnAddNode($key)
{
$owner = $this->getOwner();
if (!isset(self::$_cached[get_class($owner)] )) {
return;
}
foreach(self::$_cached[get_class($owner)] as $node)
{
if($node->getIsNewRecord() || $node->getIsDeletedRecord()) {
continue;
}
if ($this->_hasManyRoots && $owner->{$this->_rootAttribute} !== $node->{$this->_rootAttribute}) {
continue;
}
if ($owner===$node) {
continue;
}
if($node->{$this->_leftAttribute}>=$key)
$node->{$this->_leftAttribute}+=2;
if($node->{$this->_rightAttribute}>=$key)
$node->{$this->_rightAttribute}+=2;
}
}
/**
* Correct cache for {@link NestedSetBehavior::moveNode()}.
* @param int $key.
* @param int $levelDelta.
*/
private function correctCachedOnMoveNode($key,$levelDelta)
{
$owner = $this->getOwner();
$left = $owner->{$this->_leftAttribute};
$right = $owner->{$this->_rightAttribute};
$delta = $right - $left + 1;
if ($left >= $key) {
$left += $delta;
$right += $delta;
}
$delta2 = $key - $left;
foreach(self::$_cached[get_class($owner)] as $node) {
if ($node->getIsNewRecord() || $node->getIsDeletedRecord()) {
continue;
}
if ($this->_hasManyRoots && $owner->{$this->_rootAttribute}!==$node->{$this->_rootAttribute}) {
continue;
}
if ($node->{$this->_leftAttribute}>=$key) {
$node->{$this->_leftAttribute}+=$delta;
}
if($node->{$this->_rightAttribute}>=$key) {
$node->{$this->_rightAttribute}+=$delta;
}
if($node->{$this->_leftAttribute}>=$left && $node->{$this->_rightAttribute}<=$right) {
$node->{$this->_levelAttribute}+=$levelDelta;
}
if($node->{$this->_leftAttribute}>=$left && $node->{$this->_leftAttribute}<=$right) {
$node->{$this->_leftAttribute}+=$delta2;
}
if($node->{$this->_rightAttribute}>=$left && $node->{$this->_rightAttribute}<=$right) {
$node->{$this->_rightAttribute}+=$delta2;
}
if($node->{$this->_leftAttribute}>=$right+1) {
$node->{$this->_leftAttribute}-=$delta;
}
if($node->{$this->_rightAttribute}>=$right+1) {
$node->{$this->_rightAttribute}-=$delta;
}
}
}
/**
* Correct cache for {@link NestedSetBehavior::moveNode()}.
* @param int $key.
* @param int $levelDelta.
* @param int $root.
*/
private function correctCachedOnMoveBetweenTrees($key,$levelDelta,$root)
{
$owner = $this->getOwner();
$left = $owner->{$this->_leftAttribute};
$right = $owner->{$this->_rightAttribute};
$delta = $right - $left + 1;
$delta2 = $key - $left;
$delta3 = $left - $right - 1;
foreach(self::$_cached[get_class($owner)] as $node)
{
if($node->getIsNewRecord() || $node->getIsDeletedRecord()) {
continue;
}
if ($node->{$this->_rootAttribute}===$root) {
if($node->{$this->_leftAttribute}>=$key) {
$node->{$this->_leftAttribute}+=$delta;
}
if($node->{$this->_rightAttribute}>=$key) {
$node->{$this->_rightAttribute}+=$delta;
}
} else {
if($node->{$this->_rootAttribute}===$owner->{$this->_rootAttribute}) {
if($node->{$this->_leftAttribute}>=$left && $node->{$this->_rightAttribute}<=$right) {
$node->{$this->_leftAttribute}+=$delta2;
$node->{$this->_rightAttribute}+=$delta2;
$node->{$this->_levelAttribute}+=$levelDelta;
$node->{$this->_rootAttribute}=$root;
} else {
if ($node->{$this->_leftAttribute} >= $right + 1) {
$node->{$this->_leftAttribute} += $delta3;
}
if ($node->{$this->_rightAttribute} >= $right + 1) {
$node->{$this->_rightAttribute} += $delta3;
}
}
}
}
}
}
public function getOwner()
{
return $this->_owner;
}
public function setOwner($owner)
{
$this->_owner = $owner;
}
public function getIsNewRecord()
{
return $this->getOwner()->getDirtyState() == Phalcon\Mvc\Model::DIRTY_STATE_TRANSIENT;
}
public function missingMethod($model, $method, $arguments=null)
{
if (method_exists($this, $method)) {
$this->setOwner($model);
$result = call_user_func_array(array($this, $method), $arguments);
if ($result===null) {
return '';
}
return $result;
}
return null;
}
/**
* Destructor.
*/
public function __destruct()
{
unset(self::$_cached[get_class($this->getOwner())][$this->_id]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment