diff --git a/NestedSet.php b/NestedSet.php index 81ce5cb..003c5f3 100644 --- a/NestedSet.php +++ b/NestedSet.php @@ -69,6 +69,7 @@ class NestedSet extends Behavior if ($this->_rootAttributeQuoted === null) { $this->_rootAttributeQuoted = $this->owner->db->quoteColumnName($this->rootAttribute); } + return $this->_rootAttributeQuoted; } @@ -81,6 +82,7 @@ class NestedSet extends Behavior if ($this->_leftAttributeQuoted === null) { $this->_leftAttributeQuoted = $this->owner->db->quoteColumnName($this->leftAttribute); } + return $this->_leftAttributeQuoted; } @@ -93,6 +95,7 @@ class NestedSet extends Behavior if ($this->_rightAttributeQuoted === null) { $this->_rightAttributeQuoted = $this->owner->db->quoteColumnName($this->rightAttribute); } + return $this->_rightAttributeQuoted; } @@ -105,6 +108,7 @@ class NestedSet extends Behavior if ($this->_levelAttributeQuoted === null) { $this->_levelAttributeQuoted = $this->owner->db->quoteColumnName($this->levelAttribute); } + return $this->_levelAttributeQuoted; } @@ -119,14 +123,17 @@ class NestedSet extends Behavior $query->andWhere($this->getLeftAttributeQuoted() . '>' . $this->owner->{$this->leftAttribute}); $query->andWhere($this->getRightAttributeQuoted() . '<' . $this->owner->{$this->rightAttribute}); $query->addOrderBy($this->getLeftAttributeQuoted()); + if ($depth !== null) { $query->andWhere($this->getLevelAttributeQuoted() . '<=' . ($this->owner->{$this->levelAttribute} + $depth)); } + if ($this->hasManyRoots) { - $query->andWhere($this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, array( - ':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}, - )); + $query->andWhere( + $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, + array(':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}) + ); } } @@ -151,14 +158,17 @@ class NestedSet extends Behavior $query->andWhere($this->getLeftAttributeQuoted() . '<' . $this->owner->{$this->leftAttribute}); $query->andWhere($this->getRightAttributeQuoted() . '>' . $this->owner->{$this->rightAttribute}); $query->addOrderBy($this->getLeftAttributeQuoted()); + if ($depth !== null) { $query->andWhere($this->getLevelAttributeQuoted() . '>=' . ($this->owner->{$this->levelAttribute} - $depth)); } + if ($this->hasManyRoots) { - $query->andWhere($this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, array( - ':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}, - )); + $query->andWhere( + $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, + array(':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}) + ); } } @@ -182,10 +192,12 @@ class NestedSet extends Behavior $query->andWhere($this->getLeftAttributeQuoted() . '<' . $this->owner->{$this->leftAttribute}); $query->andWhere($this->getRightAttributeQuoted() . '>' . $this->owner->{$this->rightAttribute}); $query->addOrderBy($this->getRightAttributeQuoted()); + if ($this->hasManyRoots) { - $query->andWhere($this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, array( - ':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}, - )); + $query->andWhere( + $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, + array(':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}) + ); } } @@ -197,10 +209,12 @@ class NestedSet extends Behavior public function prev($query) { $query->andWhere($this->getRightAttributeQuoted() . '=' . ($this->owner->{$this->leftAttribute} - 1)); + if ($this->hasManyRoots) { - $query->andWhere($this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, array( - ':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}, - )); + $query->andWhere( + $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, + array(':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}) + ); } } @@ -212,10 +226,12 @@ class NestedSet extends Behavior public function next($query) { $query->andWhere($this->getLeftAttributeQuoted() . '=' . ($this->owner->{$this->rightAttribute} + 1)); + if ($this->hasManyRoots) { - $query->andWhere($this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, array( - ':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}, - )); + $query->andWhere( + $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, + array(':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}) + ); } } @@ -230,12 +246,15 @@ class NestedSet extends Behavior if ($runValidation && !$this->owner->validate($attributes)) { return false; } + if ($this->owner->getIsNewRecord()) { return $this->makeRoot($attributes); } + $this->_ignoreEvent = true; $result = $this->owner->update(false, $attributes); $this->_ignoreEvent = false; + return $result; } @@ -261,13 +280,17 @@ class NestedSet extends Behavior if ($this->owner->getIsNewRecord()) { throw new Exception(\Yii::t('nestedset', 'The node cannot be deleted because it is new.')); } + if ($this->getIsDeletedRecord()) { throw new Exception(\Yii::t('nestedset', 'The node cannot be deleted because it is already deleted.')); } + $db = $this->owner->getDb(); + if ($db->getTransaction() === null) { $transaction = $db->beginTransaction(); } + try { if ($this->owner->isLeaf()) { $this->_ignoreEvent = true; @@ -277,32 +300,40 @@ class NestedSet extends Behavior $condition = $this->getLeftAttributeQuoted() . '>=' . $this->owner->{$this->leftAttribute} . ' AND ' . $this->getRightAttributeQuoted() . '<=' . $this->owner->{$this->rightAttribute}; $params = array(); + if ($this->hasManyRoots) { $condition .= ' AND ' . $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute; $params[':' . $this->rootAttribute] = $this->owner->{$this->rootAttribute}; } + $result = $this->owner->deleteAll($condition, $params) > 0; } if (!$result) { if (isset($transaction)) { $transaction->rollback(); } + return false; } + $this->shiftLeftRight( $this->owner->{$this->rightAttribute} + 1, $this->owner->{$this->leftAttribute} - $this->owner->{$this->rightAttribute} - 1 ); + if (isset($transaction)) { $transaction->commit(); } + $this->correctCachedOnDelete(); } catch (\Exception $e) { if (isset($transaction)) { $transaction->rollback(); } + throw $e; } + return true; } @@ -438,44 +469,57 @@ class NestedSet extends Behavior if (!$this->hasManyRoots) { throw new Exception(\Yii::t('nestedset', 'Many roots mode is off.')); } + if ($this->owner->getIsNewRecord()) { throw new Exception(\Yii::t('nestedset', 'The node should not be new record.')); } + if ($this->getIsDeletedRecord()) { throw new Exception(\Yii::t('nestedset', 'The node should not be deleted.')); } + if ($this->owner->isRoot()) { throw new Exception(\Yii::t('nestedset', 'The node already is root node.')); } + $db = $this->owner->getDb(); + if ($db->getTransaction() === null) { $transaction = $db->beginTransaction(); } + try { $left = $this->owner->{$this->leftAttribute}; $right = $this->owner->{$this->rightAttribute}; $levelDelta = 1 - $this->owner->{$this->levelAttribute}; $delta = 1 - $left; - $this->owner->updateAll(array( + $this->owner->updateAll( + array( $this->leftAttribute => new Expression($this->getLeftAttributeQuoted() . sprintf('%+d', $delta)), $this->rightAttribute => new Expression($this->getRightAttributeQuoted() . sprintf('%+d', $delta)), - $this->levelAttribute => new Expression($this->getLevelAttributeQuoted() . sprintf('%+d', $levelDelta)), + $this->levelAttribute => new Expression($this->getLevelAttributeQuoted() . + sprintf('%+d', $levelDelta)), $this->rootAttribute => $this->owner->getPrimaryKey(), - ), $this->getLeftAttributeQuoted() . '>=' . $left . ' AND ' . $this->getRightAttributeQuoted() . '<=' . - $right . ' AND ' . $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, + ), + $this->getLeftAttributeQuoted() . '>=' . $left . ' AND ' . $this->getRightAttributeQuoted() . '<=' . + $right . ' AND ' . $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, array(':' . $this->rootAttribute => $this->owner->{$this->rootAttribute}) ); $this->shiftLeftRight($right + 1, $left - $right - 1); + if (isset($transaction)) { $transaction->commit(); } + $this->correctCachedOnMoveBetweenTrees(1, $levelDelta, $this->owner->getPrimaryKey()); } catch (\Exception $e) { if (isset($transaction)) { $transaction->rollback(); } + throw $e; } + return true; } @@ -488,9 +532,11 @@ class NestedSet extends Behavior { $result = ($this->owner->{$this->leftAttribute} > $subj->{$this->leftAttribute}) && ($this->owner->{$this->rightAttribute} < $subj->{$this->rightAttribute}); + if ($this->hasManyRoots) { $result = $result && ($this->owner->{$this->rootAttribute} === $subj->{$this->rootAttribute}); } + return $result; } @@ -590,20 +636,23 @@ class NestedSet extends Behavior */ private function shiftLeftRight($key, $delta) { - $db = $this->owner->getDb(); foreach (array( - $this->getLeftAttributeQuoted() => $this->leftAttribute, - $this->getRightAttributeQuoted() => $this->rightAttribute, - ) as $attributeQuoted => $attribute) { + $this->getLeftAttributeQuoted() => $this->leftAttribute, + $this->getRightAttributeQuoted() => $this->rightAttribute, + ) as $attributeQuoted => $attribute) { $condition = $attributeQuoted . '>=' . $key; $params = array(); + if ($this->hasManyRoots) { $condition .= ' AND ' . $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute; $params[':' . $this->rootAttribute] = $this->owner->{$this->rootAttribute}; } - $this->owner->updateAll(array( - $attribute => new Expression($attributeQuoted . sprintf('%+d', $delta)), - ), $condition, $params); + + $this->owner->updateAll( + array($attribute => new Expression($attributeQuoted . sprintf('%+d', $delta))), + $condition, + $params + ); } } @@ -622,28 +671,37 @@ class NestedSet extends Behavior if (!$this->owner->getIsNewRecord()) { throw new Exception(\Yii::t('nestedset', 'The node cannot be inserted because it is not new.')); } + if ($this->getIsDeletedRecord()) { throw new Exception(\Yii::t('nestedset', 'The node cannot be inserted because it is deleted.')); } + if ($target->getIsDeletedRecord()) { throw new Exception(\Yii::t('nestedset', 'The node cannot be inserted because target node is deleted.')); } + if ($this->owner->equals($target)) { throw new Exception(\Yii::t('nestedset', 'The target node should not be self.')); } + if (!$levelUp && $target->isRoot()) { throw new Exception(\Yii::t('nestedset', 'The target node should not be root.')); } + if ($runValidation && !$this->owner->validate()) { return false; } + if ($this->hasManyRoots) { $this->owner->{$this->rootAttribute} = $target->{$this->rootAttribute}; } + $db = $this->owner->getDb(); + if ($db->getTransaction() === null) { $transaction = $db->beginTransaction(); } + try { $this->shiftLeftRight($key, 2); $this->owner->{$this->leftAttribute} = $key; @@ -652,22 +710,28 @@ class NestedSet extends Behavior $this->_ignoreEvent = true; $result = $this->owner->insert(false, $attributes); $this->_ignoreEvent = false; + if (!$result) { if (isset($transaction)) { $transaction->rollback(); } + return false; } + if (isset($transaction)) { $transaction->commit(); } + $this->correctCachedOnAddNode($key); } catch (\Exception $e) { if (isset($transaction)) { $transaction->rollback(); } + throw $e; } + return true; } @@ -682,8 +746,10 @@ class NestedSet extends Behavior $this->owner->{$this->leftAttribute} = 1; $this->owner->{$this->rightAttribute} = 2; $this->owner->{$this->levelAttribute} = 1; + if ($this->hasManyRoots) { $db = $this->owner->getDb(); + if ($db->getTransaction() === null) { $transaction = $db->beginTransaction(); } @@ -691,17 +757,21 @@ class NestedSet extends Behavior $this->_ignoreEvent = true; $result = $this->owner->insert(false, $attributes); $this->_ignoreEvent = false; + if (!$result) { if (isset($transaction)) { $transaction->rollback(); } + return false; } + $pk = $this->owner->{$this->rootAttribute} = $this->owner->getPrimaryKey(); $this->owner->updateAll( array($this->rootAttribute => $pk), array($this->owner->primaryKey()[0] => $pk) ); + if (isset($transaction)) { $transaction->commit(); } @@ -709,19 +779,23 @@ class NestedSet extends Behavior if (isset($transaction)) { $transaction->rollback(); } + throw $e; } } else { if ($this->owner->roots()->exists()) { throw new Exception(\Yii::t('nestedset', 'Cannot create more than one root in single root mode.')); } + $this->_ignoreEvent = true; $result = $this->owner->insert(false, $attributes); $this->_ignoreEvent = false; + if (!$result) { return false; } } + return true; } @@ -738,100 +812,135 @@ class NestedSet extends Behavior if ($this->owner->getIsNewRecord()) { throw new Exception(\Yii::t('nestedset', 'The node should not be new record.')); } + if ($this->getIsDeletedRecord()) { throw new Exception(\Yii::t('nestedset', 'The node should not be deleted.')); } + if ($target->getIsDeletedRecord()) { throw new Exception(\Yii::t('nestedset', 'The target node should not be deleted.')); } + if ($this->owner->equals($target)) { throw new Exception(\Yii::t('nestedset', 'The target node should not be self.')); } + if ($target->isDescendantOf($this->owner)) { throw new Exception(\Yii::t('nestedset', 'The target node should not be descendant.')); } + if (!$levelUp && $target->isRoot()) { throw new Exception(\Yii::t('nestedset', 'The target node should not be root.')); } + $db = $this->owner->getDb(); + if ($db->getTransaction() === null) { $transaction = $db->beginTransaction(); } + try { $left = $this->owner->{$this->leftAttribute}; $right = $this->owner->{$this->rightAttribute}; $levelDelta = $target->{$this->levelAttribute} - $this->owner->{$this->levelAttribute} + $levelUp; + if ($this->hasManyRoots && $this->owner->{$this->rootAttribute} !== $target->{$this->rootAttribute}) { foreach (array( - $this->getLeftAttributeQuoted() => $this->leftAttribute, - $this->getRightAttributeQuoted() => $this->rightAttribute, - ) as $attributeQuoted => $attribute) { - $this->owner->updateAll(array( - $attribute => new Expression($attributeQuoted . sprintf('%+d', $right - $left + 1)), - ), $attributeQuoted . '>=' . $key . ' AND ' . $this->getRootAttributeQuoted() . '=:' . - $this->rootAttribute, array(':' . $this->rootAttribute => $target->{$this->rootAttribute}) + $this->getLeftAttributeQuoted() => $this->leftAttribute, + $this->getRightAttributeQuoted() => $this->rightAttribute, + ) as $attributeQuoted => $attribute) { + $this->owner->updateAll( + array($attribute => new Expression($attributeQuoted . sprintf('%+d', $right - $left + 1))), + $attributeQuoted . '>=' . $key . ' AND ' . $this->getRootAttributeQuoted() . '=:' . + $this->rootAttribute, + array(':' . $this->rootAttribute => $target->{$this->rootAttribute}) ); } + $delta = $key - $left; - $this->owner->updateAll(array( - $this->leftAttribute => new Expression($this->getLeftAttributeQuoted() . sprintf('%+d', $delta)), - $this->rightAttribute => new Expression($this->getRightAttributeQuoted() . sprintf('%+d', $delta)), + $this->owner->updateAll( + array( + $this->leftAttribute => new Expression($this->getLeftAttributeQuoted() . + sprintf('%+d', $delta)), + $this->rightAttribute => new Expression($this->getRightAttributeQuoted() . + sprintf('%+d', $delta)), $this->levelAttribute => new Expression($this->getLevelAttributeQuoted() . sprintf('%+d', $levelDelta)), $this->rootAttribute => $target->{$this->rootAttribute}, - ), $this->getLeftAttributeQuoted() . '>=' . $left . ' AND ' . $this->getRightAttributeQuoted() . '<=' . - $right . ' AND ' . $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, + ), + $this->getLeftAttributeQuoted() . '>=' . $left . ' AND ' . $this->getRightAttributeQuoted() . '<=' . + $right . ' AND ' . $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute, array(':' . $this->rootAttribute => $this->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 = $this->getLeftAttributeQuoted() . '>=' . $left . ' AND ' . $this->getRightAttributeQuoted() . '<=' . $right; $params = array(); + if ($this->hasManyRoots) { $condition .= ' AND ' . $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute; $params[':' . $this->rootAttribute] = $this->owner->{$this->rootAttribute}; } - $this->owner->updateAll(array( - $this->levelAttribute => new Expression($this->getLevelAttributeQuoted() . - sprintf('%+d', $levelDelta)), - ), $condition, $params); + + $this->owner->updateAll( + array( + $this->levelAttribute => new Expression($this->getLevelAttributeQuoted() . + sprintf('%+d', $levelDelta)), + ), + $condition, + $params + ); + foreach (array( - $this->getLeftAttributeQuoted() => $this->leftAttribute, - $this->getRightAttributeQuoted() => $this->rightAttribute, - ) as $attributeQuoted => $attribute) { + $this->getLeftAttributeQuoted() => $this->leftAttribute, + $this->getRightAttributeQuoted() => $this->rightAttribute, + ) as $attributeQuoted => $attribute) { $condition = $attributeQuoted . '>=' . $left . ' AND ' . $attributeQuoted . '<=' . $right; $params = array(); + if ($this->hasManyRoots) { $condition .= ' AND ' . $this->getRootAttributeQuoted() . '=:' . $this->rootAttribute; $params[':' . $this->rootAttribute] = $this->owner->{$this->rootAttribute}; } - $this->owner->updateAll(array( - $attribute => new Expression($attributeQuoted . sprintf('%+d', $key - $left)), - ), $condition, $params); + + $this->owner->updateAll( + array($attribute => new Expression($attributeQuoted . 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; } @@ -844,19 +953,23 @@ class NestedSet extends Behavior $right = $this->owner->{$this->rightAttribute}; $key = $right + 1; $delta = $left - $right - 1; + foreach (self::$_cached[get_class($this->owner)] as $node) { if ($node->getIsNewRecord() || $node->getIsDeletedRecord()) { continue; } + if ($this->hasManyRoots && $this->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; } @@ -874,15 +987,19 @@ class NestedSet extends Behavior if ($node->getIsNewRecord() || $node->getIsDeletedRecord()) { continue; } + if ($this->hasManyRoots && $this->owner->{$this->rootAttribute} !== $node->{$this->rootAttribute}) { continue; } + if ($this->owner === $node) { continue; } + if ($node->{$this->leftAttribute} >= $key) { $node->{$this->leftAttribute} += 2; } + if ($node->{$this->rightAttribute} >= $key) { $node->{$this->rightAttribute} += 2; } @@ -899,36 +1016,47 @@ class NestedSet extends Behavior $left = $this->owner->{$this->leftAttribute}; $right = $this->owner->{$this->rightAttribute}; $delta = $right - $left + 1; + if ($left >= $key) { $left += $delta; $right += $delta; } + $delta2 = $key - $left; + foreach (self::$_cached[get_class($this->owner)] as $node) { if ($node->getIsNewRecord() || $node->getIsDeletedRecord()) { continue; } + if ($this->hasManyRoots && $this->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; } @@ -948,14 +1076,17 @@ class NestedSet extends Behavior $delta = $right - $left + 1; $delta2 = $key - $left; $delta3 = $left - $right - 1; + foreach (self::$_cached[get_class($this->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; } @@ -969,6 +1100,7 @@ class NestedSet extends Behavior if ($node->{$this->leftAttribute} >= $right + 1) { $node->{$this->leftAttribute} += $delta3; } + if ($node->{$this->rightAttribute} >= $right + 1) { $node->{$this->rightAttribute} += $delta3; }