Skip to content
Closed
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
169 changes: 45 additions & 124 deletions src/NestedSetsBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@
*/
private Connection|null $db = null;

/**
* Update manager instance for centralized database operations.
*
* Manages the creation and execution of complex update operations through centralized condition and expression
* builders, ensuring consistency and reducing code duplication.
*
* @phpstan-var NestedSetsUpdateManager<T>|null
*/
private NestedSetsUpdateManager|null $updateManager = null;

/**
* Handles post-deletion updates for the nested set structure.
*
Expand All @@ -149,47 +159,7 @@
*/
public function afterDelete(): void
{
$leftValue = $this->getOwner()->getAttribute($this->leftAttribute);
$rightValue = $this->getOwner()->getAttribute($this->rightAttribute);

if ($this->operation === self::OPERATION_DELETE_WITH_CHILDREN || $this->getOwner()->isLeaf()) {
$this->shiftLeftRightAttribute($rightValue, $leftValue - $rightValue - 1);
} else {
$condition = [
'and',
[
'>=',
$this->leftAttribute,
$this->getOwner()->getAttribute($this->leftAttribute),
],
[
'<=',
$this->rightAttribute,
$this->getOwner()->getAttribute($this->rightAttribute),
],
];

$this->applyTreeAttributeCondition($condition);
$db = $this->getOwner()::getDb();
$this->getOwner()::updateAll(
[
$this->leftAttribute => new Expression(
$db->quoteColumnName($this->leftAttribute) . sprintf('%+d', -1),
),
$this->rightAttribute => new Expression(
$db->quoteColumnName($this->rightAttribute) . sprintf('%+d', -1),
),
$this->depthAttribute => new Expression(
$db->quoteColumnName($this->depthAttribute) . sprintf('%+d', -1),
),
],
$condition,
);
$this->shiftLeftRightAttribute($rightValue, -2);
}

$this->operation = null;
$this->node = null;
$this->getUpdateManager()->updateSubtreeForDeletion($this->operation);
}

/**
Expand All @@ -211,26 +181,9 @@
*/
public function afterInsert(): void
{
if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== false) {
$this->getOwner()->setAttribute($this->treeAttribute, $this->getOwner()->getPrimaryKey());
$primaryKey = $this->getOwner()::primaryKey();

if (isset($primaryKey[0]) === false) {
throw new Exception('"' . get_class($this->getOwner()) . '" must have a primary key.');
}

$this->getOwner()::updateAll(
[
$this->treeAttribute => $this->getOwner()->getAttribute($this->treeAttribute),
],
[
$primaryKey[0] => $this->getOwner()->getAttribute($this->treeAttribute),
],
);
if ($this->operation === self::OPERATION_MAKE_ROOT) {
$this->getUpdateManager()->updateTreeAttributeForRoot();
}

$this->operation = null;
$this->node = null;
}

/**
Expand Down Expand Up @@ -537,21 +490,21 @@
{
$this->operation = self::OPERATION_DELETE_WITH_CHILDREN;

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

Check warning on line 493 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "NotIdentical": @@ @@ public function deleteWithChildren(): bool|int { $this->operation = self::OPERATION_DELETE_WITH_CHILDREN; - if ($this->getOwner()->isTransactional(ActiveRecord::OP_DELETE) !== false) { + if ($this->getOwner()->isTransactional(ActiveRecord::OP_DELETE) === false) { return $this->deleteWithChildrenInternal(); } $transaction = $this->getOwner()::getDb()->beginTransaction();

Check warning on line 493 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "NotIdentical": @@ @@ public function deleteWithChildren(): bool|int { $this->operation = self::OPERATION_DELETE_WITH_CHILDREN; - if ($this->getOwner()->isTransactional(ActiveRecord::OP_DELETE) !== false) { + if ($this->getOwner()->isTransactional(ActiveRecord::OP_DELETE) === false) { return $this->deleteWithChildrenInternal(); } $transaction = $this->getOwner()::getDb()->beginTransaction();
return $this->deleteWithChildrenInternal();
}

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

try {
match ($result = $this->deleteWithChildrenInternal()) {

Check warning on line 500 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MatchArmRemoval": @@ @@ $transaction = $this->getOwner()::getDb()->beginTransaction(); try { match ($result = $this->deleteWithChildrenInternal()) { - false => $transaction->rollBack(), default => $transaction->commit(), }; return $result;

Check warning on line 500 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MatchArmRemoval": @@ @@ $transaction = $this->getOwner()::getDb()->beginTransaction(); try { match ($result = $this->deleteWithChildrenInternal()) { - false => $transaction->rollBack(), default => $transaction->commit(), }; return $result;
false => $transaction->rollBack(),
default => $transaction->commit(),
};

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

Check warning on line 507 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ }; return $result; } catch (Exception $e) { - $transaction->rollBack(); + throw $e; } }

Check warning on line 507 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ }; return $result; } catch (Exception $e) { - $transaction->rollBack(); + throw $e; } }

throw $e;
}
Expand Down Expand Up @@ -701,7 +654,7 @@
$nodeLeft = $node->getAttribute($this->leftAttribute);
$nodeRight = $node->getAttribute($this->rightAttribute);

if ($currentLeft <= $nodeLeft || $currentRight >= $nodeRight) {

Check warning on line 657 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

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

Check warning on line 657 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

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

Check warning on line 657 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

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

Check warning on line 657 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

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

Expand Down Expand Up @@ -810,7 +763,7 @@

$this->applyTreeAttributeCondition($condition);

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

Check warning on line 766 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ { $condition = ['and', ['>', $this->leftAttribute, $this->getOwner()->getAttribute($this->leftAttribute)], ['<', $this->rightAttribute, $this->getOwner()->getAttribute($this->rightAttribute)], [$this->rightAttribute => new Expression($this->getOwner()::getDb()->quoteColumnName($this->leftAttribute) . '+ 1')]]; $this->applyTreeAttributeCondition($condition); - 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 766 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ { $condition = ['and', ['>', $this->leftAttribute, $this->getOwner()->getAttribute($this->leftAttribute)], ['<', $this->rightAttribute, $this->getOwner()->getAttribute($this->rightAttribute)], [$this->rightAttribute => new Expression($this->getOwner()::getDb()->quoteColumnName($this->leftAttribute) . '+ 1')]]; $this->applyTreeAttributeCondition($condition); - 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 @@ -953,7 +906,7 @@

$this->applyTreeAttributeCondition($condition);

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

Check warning on line 909 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ $condition[] = ['>=', $this->depthAttribute, $this->getOwner()->getAttribute($this->depthAttribute) - $depth]; } $this->applyTreeAttributeCondition($condition); - 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 909 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ $condition[] = ['>=', $this->depthAttribute, $this->getOwner()->getAttribute($this->depthAttribute) - $depth]; } $this->applyTreeAttributeCondition($condition); - 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 @@ -1088,7 +1041,7 @@
$this->getOwner()->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
}

$this->shiftLeftRightAttribute($value, 2);
$this->getUpdateManager()->shiftLeftRightAttribute($value, 2);
}

/**
Expand Down Expand Up @@ -1154,7 +1107,7 @@

$this->applyTreeAttributeCondition($condition);
$result = $this->getOwner()::deleteAll($condition);
$this->getOwner()->setOldAttributes(null);

Check warning on line 1110 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ $condition = ['and', ['>=', $this->leftAttribute, $this->owner?->getAttribute($this->leftAttribute)], ['<=', $this->rightAttribute, $this->owner?->getAttribute($this->rightAttribute)]]; $this->applyTreeAttributeCondition($condition); $result = $this->getOwner()::deleteAll($condition); - $this->getOwner()->setOldAttributes(null); + $this->getOwner()->afterDelete(); return $result; }

Check warning on line 1110 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ $condition = ['and', ['>=', $this->leftAttribute, $this->owner?->getAttribute($this->leftAttribute)], ['<=', $this->rightAttribute, $this->owner?->getAttribute($this->rightAttribute)]]; $this->applyTreeAttributeCondition($condition); $result = $this->getOwner()::deleteAll($condition); - $this->getOwner()->setOldAttributes(null); + $this->getOwner()->afterDelete(); return $result; }
$this->getOwner()->afterDelete();

return $result;
Expand Down Expand Up @@ -1182,9 +1135,9 @@
if ($this->treeAttribute === false || $targetNodeTreeValue === $currentOwnerTreeValue) {
$subtreeSize = $ownerRightValue - $ownerLeftValue + 1;

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

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

Check warning on line 1140 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

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

Check warning on line 1140 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "GreaterThanOrEqualTo": @@ @@ if ($this->treeAttribute === false || $targetNodeTreeValue === $currentOwnerTreeValue) { $subtreeSize = $ownerRightValue - $ownerLeftValue + 1; $this->getUpdateManager()->shiftLeftRightAttribute($context->targetPositionValue, $subtreeSize); - if ($ownerLeftValue >= $context->targetPositionValue) { + if ($ownerLeftValue > $context->targetPositionValue) { $ownerLeftValue += $subtreeSize; $ownerRightValue += $subtreeSize; }
$ownerLeftValue += $subtreeSize;
$ownerRightValue += $subtreeSize;
}
Expand Down Expand Up @@ -1238,7 +1191,7 @@
);
}

$this->shiftLeftRightAttribute($ownerRightValue, -$subtreeSize);
$this->getUpdateManager()->shiftLeftRightAttribute($ownerRightValue, -$subtreeSize);
} else {
foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
$this->getOwner()::updateAll(
Expand Down Expand Up @@ -1269,7 +1222,10 @@
$context->targetPositionValue - $ownerLeftValue,
$ownerRightValue,
);
$this->shiftLeftRightAttribute($ownerRightValue, $ownerLeftValue - $ownerRightValue - 1);
$this->getUpdateManager()->shiftLeftRightAttribute(
$ownerRightValue,
$ownerLeftValue - $ownerRightValue - 1,
);
}
}

Expand Down Expand Up @@ -1308,37 +1264,7 @@
1 - $leftValue,
$rightValue,
);
$this->shiftLeftRightAttribute($rightValue, $leftValue - $rightValue - 1);
}

/**
* Shifts left and right attribute values for nodes after a structural change in the nested set tree.
*
* Updates the left and right boundary attributes of all nodes whose attribute value is greater than or equal to the
* specified value, applying the given delta.
*
* This operation is essential for maintaining the integrity of the nested set structure after insertions,
* deletions, or moves, ensuring that all affected nodes are correctly renumbered.
*
* The method applies the tree attribute condition if multi-tree support is enabled, restricting the update to nodes
* within the same tree.
*
* @param int $value Attribute value from which to start shifting (inclusive).
* @param int $delta Amount to add to the attribute value for affected nodes (can be negative).
*/
protected function shiftLeftRightAttribute(int $value, int $delta): void
{
foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
$condition = ['>=', $attribute, $value];

$this->applyTreeAttributeCondition($condition);
$this->getOwner()::updateAll(
[
$attribute => new Expression($this->getDb()->quoteColumnName($attribute) . sprintf('%+d', $delta)),
],
$condition,
);
}
$this->getUpdateManager()->shiftLeftRightAttribute($rightValue, $leftValue - $rightValue - 1);
}

/**
Expand Down Expand Up @@ -1468,35 +1394,30 @@
int $positionOffset,
int $rightValue,
): void {
$this->getOwner()::updateAll(
[
$this->leftAttribute => new Expression(
$this->getDb()->quoteColumnName($this->leftAttribute) . sprintf('%+d', $positionOffset),
),
$this->rightAttribute => new Expression(
$this->getDb()->quoteColumnName($this->rightAttribute) . sprintf('%+d', $positionOffset),
),
$this->depthAttribute => new Expression(
$this->getDb()->quoteColumnName($this->depthAttribute) . sprintf('%+d', $depth),
),
$this->treeAttribute => $targetNodeTreeValue,
],
[
'and',
[
'>=',
$this->leftAttribute,
$leftValue,
],
[
'<=',
$this->rightAttribute,
$rightValue,
],
[
$this->treeAttribute => $currentOwnerTreeValue,
],
],
$this->getUpdateManager()->moveSubtreeToTargetTree(
$leftValue,
$rightValue,
$positionOffset,
$depth,
$currentOwnerTreeValue,
$targetNodeTreeValue,
);
}

/**
* Gets or creates the update manager instance.
*
* @return NestedSetsUpdateManager<T> Update manager for database operations.
*/
private function getUpdateManager(): NestedSetsUpdateManager
{
return $this->updateManager ??= new NestedSetsUpdateManager(
$this->getOwner(),
$this->getOwner()::class,
$this->leftAttribute,
$this->rightAttribute,
$this->depthAttribute,
$this->treeAttribute,
);
}
}
Loading