Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 21 additions & 18 deletions src/NestedSetsBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@
*
* @return bool Whether the operation was successful and the node was appended or moved.
*
* **Note:** This method uses {@see ActiveRecord::save()} internally, which means Yii2 will automatically handle
* database transactions if the model {@see ActiveRecord::isTransactional()} method returns `true` for the current
* scenario and {@see ActiveRecord::OP_INSERT} or {@see ActiveRecord::OP_UPDATE} operations.
*
* Usage example:
* ```php
* $category->appendTo($parentCategory);
Expand Down Expand Up @@ -534,24 +538,7 @@
{
$this->operation = self::OPERATION_DELETE_WITH_CHILDREN;

if ($this->getOwner()->isTransactional(ActiveRecord::OP_DELETE) !== false) {
return $this->deleteWithChildrenInternal();
}

$transaction = $this->getDb()->beginTransaction();

try {
match ($result = $this->deleteWithChildrenInternal()) {
false => $transaction->rollBack(),
default => $transaction->commit(),
};

return $result;
} catch (Exception $e) {
$transaction->rollBack();

throw $e;
}
return $this->deleteWithChildrenInternal();
}

/**
Expand Down Expand Up @@ -610,6 +597,10 @@
*
* @return bool Whether the operation was successful and the node was inserted or moved.
*
* **Note:** This method uses {@see ActiveRecord::save()} internally, which means Yii2 will automatically handle
* database transactions if the model's {@see ActiveRecord::isTransactional()} method returns `true` for the
* current scenario and {@see ActiveRecord::OP_INSERT} or {@see ActiveRecord::OP_UPDATE} operations.
*
* Usage example:
* ```php
* $category->insertAfter($siblingCategory);
Expand Down Expand Up @@ -651,6 +642,10 @@
*
* @return bool Whether the operation was successful and the node was inserted or moved.
*
* **Note:** This method uses {@see ActiveRecord::save()} internally, which means Yii2 will automatically handle
* database transactions if the model {@see ActiveRecord::isTransactional()} method returns `true` for the current
* scenario and {@see ActiveRecord::OP_INSERT} or {@see ActiveRecord::OP_UPDATE} operations.
*
* Usage example:
* ```php
* $category->insertBefore($siblingCategory);
Expand Down Expand Up @@ -694,7 +689,7 @@
$nodeLeft = $node->getAttribute($this->leftAttribute);
$nodeRight = $node->getAttribute($this->rightAttribute);

if ($this->getLeftValue() <= $nodeLeft || $this->getRightValue() >= $nodeRight) {

Check warning on line 692 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "GreaterThanOrEqualTo": @@ @@ { $nodeLeft = $node->getAttribute($this->leftAttribute); $nodeRight = $node->getAttribute($this->rightAttribute); - if ($this->getLeftValue() <= $nodeLeft || $this->getRightValue() >= $nodeRight) { + if ($this->getLeftValue() <= $nodeLeft || $this->getRightValue() > $nodeRight) { return false; } if ($this->treeAttribute !== false) {

Check warning on line 692 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "LessThanOrEqualTo": @@ @@ { $nodeLeft = $node->getAttribute($this->leftAttribute); $nodeRight = $node->getAttribute($this->rightAttribute); - if ($this->getLeftValue() <= $nodeLeft || $this->getRightValue() >= $nodeRight) { + if ($this->getLeftValue() < $nodeLeft || $this->getRightValue() >= $nodeRight) { return false; } if ($this->treeAttribute !== false) {

Check warning on line 692 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "GreaterThanOrEqualTo": @@ @@ { $nodeLeft = $node->getAttribute($this->leftAttribute); $nodeRight = $node->getAttribute($this->rightAttribute); - if ($this->getLeftValue() <= $nodeLeft || $this->getRightValue() >= $nodeRight) { + if ($this->getLeftValue() <= $nodeLeft || $this->getRightValue() > $nodeRight) { return false; } if ($this->treeAttribute !== false) {

Check warning on line 692 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "LessThanOrEqualTo": @@ @@ { $nodeLeft = $node->getAttribute($this->leftAttribute); $nodeRight = $node->getAttribute($this->rightAttribute); - if ($this->getLeftValue() <= $nodeLeft || $this->getRightValue() >= $nodeRight) { + if ($this->getLeftValue() < $nodeLeft || $this->getRightValue() >= $nodeRight) { return false; } if ($this->treeAttribute !== false) {
return false;
}

Expand Down Expand Up @@ -792,7 +787,7 @@
$this->getRightValue(),
);

return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);

Check warning on line 790 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ public function leaves(): ActiveQuery { $condition = QueryConditionBuilder::createLeavesCondition($this->leftAttribute, $this->rightAttribute, $this->treeAttribute, $this->getTreeValue($this->getOwner()), $this->getLeftValue(), $this->getRightValue()); - return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]); + return $this->getOwner()::find()->andWhere($condition)->addOrderBy([]); } /** * Creates the root node if the active record is new, or moves it as the root node in the nested set tree.

Check warning on line 790 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ public function leaves(): ActiveQuery { $condition = QueryConditionBuilder::createLeavesCondition($this->leftAttribute, $this->rightAttribute, $this->treeAttribute, $this->getTreeValue($this->getOwner()), $this->getLeftValue(), $this->getRightValue()); - return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]); + return $this->getOwner()::find()->andWhere($condition)->addOrderBy([]); } /** * Creates the root node if the active record is new, or moves it as the root node in the nested set tree.
}

/**
Expand Down Expand Up @@ -821,6 +816,10 @@
*
* @return bool Whether the operation was successful and the node was created or moved as root.
*
* **Note:** This method uses {@see ActiveRecord::save()} internally, which means Yii2 will automatically handle
* database transactions if the model {@see ActiveRecord::isTransactional()} method returns `true` for the current
* scenario and {@see ActiveRecord::OP_INSERT} or {@see ActiveRecord::OP_UPDATE} operations.
*
* Usage example:
* ```php
* // Create a new root node
Expand Down Expand Up @@ -928,7 +927,7 @@
$depth,
);

return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);

Check warning on line 930 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ public function parents(int|null $depth = null): ActiveQuery { $condition = QueryConditionBuilder::createParentsCondition($this->leftAttribute, $this->getLeftValue(), $this->rightAttribute, $this->getRightValue(), $this->treeAttribute, $this->getTreeValue($this->getOwner()), $depth !== null ? $this->depthAttribute : null, $depth !== null ? $this->getDepthValue() : null, $depth); - return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]); + return $this->getOwner()::find()->andWhere($condition)->addOrderBy([]); } /** * Inserts the current node as the first child of the specified target node or moves it if it already exists.

Check warning on line 930 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ public function parents(int|null $depth = null): ActiveQuery { $condition = QueryConditionBuilder::createParentsCondition($this->leftAttribute, $this->getLeftValue(), $this->rightAttribute, $this->getRightValue(), $this->treeAttribute, $this->getTreeValue($this->getOwner()), $depth !== null ? $this->depthAttribute : null, $depth !== null ? $this->getDepthValue() : null, $depth); - return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]); + return $this->getOwner()::find()->andWhere($condition)->addOrderBy([]); } /** * Inserts the current node as the first child of the specified target node or moves it if it already exists.
}

/**
Expand Down Expand Up @@ -957,6 +956,10 @@
*
* @return bool Whether the operation was successful and the node was prepended or moved.
*
* **Note:** This method uses {@see ActiveRecord::save()} internally, which means Yii2 will automatically handle
* database transactions if the model {@see ActiveRecord::isTransactional()} method returns `true` for the current
* scenario and {@see ActiveRecord::OP_INSERT} or {@see ActiveRecord::OP_UPDATE} operations.
*
* Usage example:
* ```php
* $category->prependTo($parentCategory);
Expand Down Expand Up @@ -1103,7 +1106,7 @@
$this->getTreeValue($this->getOwner()),
);
$result = $this->getOwner()::deleteAll($condition);
$this->getOwner()->setOldAttributes(null);

Check warning on line 1109 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ } $condition = QueryConditionBuilder::createRangeCondition($this->leftAttribute, $this->getLeftValue(), $this->rightAttribute, $this->getRightValue(), $this->treeAttribute, $this->getTreeValue($this->getOwner())); $result = $this->getOwner()::deleteAll($condition); - $this->getOwner()->setOldAttributes(null); + $this->getOwner()->afterDelete(); return $result; }

Check warning on line 1109 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ } $condition = QueryConditionBuilder::createRangeCondition($this->leftAttribute, $this->getLeftValue(), $this->rightAttribute, $this->getRightValue(), $this->treeAttribute, $this->getTreeValue($this->getOwner())); $result = $this->getOwner()::deleteAll($condition); - $this->getOwner()->setOldAttributes(null); + $this->getOwner()->afterDelete(); return $result; }
$this->getOwner()->afterDelete();

return $result;
Expand Down Expand Up @@ -1131,7 +1134,7 @@

$this->shiftLeftRightAttribute($context->targetPositionValue, $subtreeSize);

if ($ownerLeftValue >= $context->targetPositionValue) {

Check warning on line 1137 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "GreaterThanOrEqualTo": @@ @@ $ownerRightValue = $this->getRightValue(); $subtreeSize = $ownerRightValue - $ownerLeftValue + 1; $this->shiftLeftRightAttribute($context->targetPositionValue, $subtreeSize); - if ($ownerLeftValue >= $context->targetPositionValue) { + if ($ownerLeftValue > $context->targetPositionValue) { $ownerLeftValue += $subtreeSize; $ownerRightValue += $subtreeSize; }

Check warning on line 1137 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "GreaterThanOrEqualTo": @@ @@ $ownerRightValue = $this->getRightValue(); $subtreeSize = $ownerRightValue - $ownerLeftValue + 1; $this->shiftLeftRightAttribute($context->targetPositionValue, $subtreeSize); - if ($ownerLeftValue >= $context->targetPositionValue) { + if ($ownerLeftValue > $context->targetPositionValue) { $ownerLeftValue += $subtreeSize; $ownerRightValue += $subtreeSize; }
$ownerLeftValue += $subtreeSize;
$ownerRightValue += $subtreeSize;
}
Expand Down
98 changes: 0 additions & 98 deletions tests/NestedSetsBehaviorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1429,71 +1429,6 @@ public function testThrowLogicExceptionWhenBehaviorIsDetachedFromOwner(): void
$behavior->parents();
}

public function testReturnAffectedRowsAndUpdateTreeAfterDeleteWithChildrenWhenManualTransactionIsUsed(): void
{
$this->generateFixtureTree();

$node = Tree::findOne(10);

self::assertNotNull(
$node,
"Node with ID '10' should exist before attempting to delete with children using manual transaction.",
);
self::assertEquals(
'Node 2.1',
$node->getAttribute('name'),
"Node with ID '10' should have the name 'Node 2.1' before deletion.",
);
self::assertFalse(
$node->isTransactional(ActiveRecord::OP_DELETE),
"Node with ID '10' should not use transactional delete (manual transaction expected).",
);

$initialCount = (int) Tree::find()->count();
$toDeleteCount = (int) Tree::find()
->andWhere(['>=', 'lft', $node->getAttribute('lft')])
->andWhere(['<=', 'rgt', $node->getAttribute('rgt')])
->count();

self::assertEquals(
3,
$toDeleteCount,
"Node '2.1' should have itself and '2' children (total '3' nodes to delete).",
);

$result = (int) $node->deleteWithChildren();

self::assertEquals(
$toDeleteCount,
$result,
"'deleteWithChildren()' should return the number of affected rows equal to the nodes deleted.",
);

$finalCount = (int) Tree::find()->count();

self::assertEquals(
$initialCount - $toDeleteCount,
$finalCount,
'Tree node count after deletion should decrease by the number of deleted nodes.',
);
self::assertNull(
Tree::findOne(10),
"Node with ID '10' should not exist after deletion.",
);
self::assertNull(
Tree::findOne(11),
"Node with ID '11' should not exist after deletion.",
);
self::assertNull(
Tree::findOne(12),
"Node with ID '12' should not exist after deletion.",
);
self::assertNotNull(
Tree::findOne(1),
"Root node with ID '1' should still exist after deleting node '10' and its children.",
);
}

public function testReturnFalseWhenDeleteWithChildrenIsAbortedByBeforeDelete(): void
{
$this->createDatabase();
Expand Down Expand Up @@ -1529,39 +1464,6 @@ public function testReturnFalseWhenDeleteWithChildrenIsAbortedByBeforeDelete():
);
}

public function testThrowExceptionWhenDeleteWithChildrenThrowsExceptionInTransaction(): void
{
$this->createDatabase();

$node = new Tree(['name' => 'Root']);

$node->detachBehavior('nestedSetsBehavior');

self::assertNull(
$node->getBehavior('nestedSetsBehavior'),
'Behavior must be detached before testing exception handling.',
);

$nestedSetsBehavior = $this->createMock(NestedSetsBehavior::class);
$nestedSetsBehavior->expects(self::once())
->method('deleteWithChildren')
->willThrowException(new Exception('Simulated database error during deletion'));

$node->attachBehavior('nestedSetsBehavior', $nestedSetsBehavior);
$behavior = $node->getBehavior('nestedSetsBehavior');

self::assertInstanceOf(
NestedSetsBehavior::class,
$behavior,
'Behavior must be attached to the node before testing exception handling.',
);

$this->expectException(Exception::class);
$this->expectExceptionMessage('Simulated database error during deletion');

$behavior->deleteWithChildren();
}

public function testThrowExceptionWhenMakeRootIsCalledOnModelWithoutPrimaryKey(): void
{
$this->createDatabase();
Expand Down