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
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-strict-rules": "^2.0.3",
"phpunit/phpunit": "^10.2",
"rector/rector": "^2.0",
"symplify/easy-coding-standard": "^12.3",
"rector/rector": "^2.1",
"symplify/easy-coding-standard": "^12.5",
"yii2-extensions/phpstan": "^0.3.0"
},
"autoload": {
Expand Down
23 changes: 22 additions & 1 deletion ecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@
'space_before_parenthesis' => true,
],
)
->withConfiguredRule(
OrderedClassElementsFixer::class,
[
'order' => [
'use_trait',
'constant_public',
'constant_protected',
'constant_private',
'property_public',
'property_protected',
'property_private',
'construct',
'destruct',
'magic',
'phpunit',
'method_public',
'method_protected',
'method_private',
],
'sort_algorithm' => 'alpha',
],
)
->withConfiguredRule(
OrderedImportsFixer::class,
[
Expand Down Expand Up @@ -49,7 +71,6 @@
->withRules(
[
NoUnusedImportsFixer::class,
OrderedClassElementsFixer::class,
OrderedTraitsFixer::class,
SingleQuoteFixer::class,
]
Expand Down
202 changes: 95 additions & 107 deletions src/NestedSetsBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,65 +75,65 @@ class NestedSetsBehavior extends Behavior
public const OPERATION_PREPEND_TO = 'prependTo';

/**
* Holds the reference to the current node involved in a nested set operation.
*
* Stores the {@see ActiveRecord} instance representing the node being manipulated by the behavior during operations
* such as insertion, movement, or deletion within the tree structure.
*
* @template T of NestedSetsBehavior
* Name of the attribute that stores the depth (level) of the node in the tree.
*
* @phpstan-var ActiveRecord<T>|null
* @phpstan-var 'depth' attribute name.
*/
protected ActiveRecord|null $node = null;
public string $depthAttribute = 'depth';

/**
* Stores the current operation being performed on the node.
* Name of the attribute that stores the left boundary value of the node in the nested set tree.
*
* Holds the operation type as a string identifier, such as 'appendTo', 'deleteWithChildren', or other defined
* operation constants.
* @phpstan-var 'lft' attribute name.
*/
protected string|null $operation = null;
public string $leftAttribute = 'lft';

/**
* Name of the attribute that stores the depth (level) of the node in the tree.
* Name of the attribute that stores the right boundary value of the node in the nested set tree.
*
* @phpstan-var 'depth' attribute name.
* @phpstan-var 'rgt' attribute name.
*/
public string $depthAttribute = 'depth';
public string $rightAttribute = 'rgt';

/**
* Stores the depth value for the current operation.
* Name of the attribute that stores the tree identifier for supporting multiple trees.
*/
protected int|null $depthValue = null;
public string|false $treeAttribute = false;

/**
* Name of the attribute that stores the left boundary value of the node in the nested set tree.
*
* @phpstan-var 'lft' attribute name.
* Stores the depth value for the current operation.
*/
public string $leftAttribute = 'lft';
protected int|null $depthValue = null;

/**
* Stores the left value for the current operation.
*/
protected int|null $leftValue = null;

/**
* Name of the attribute that stores the right boundary value of the node in the nested set tree.
* Holds the reference to the current node involved in a nested set operation.
*
* @phpstan-var 'rgt' attribute name.
* Stores the {@see ActiveRecord} instance representing the node being manipulated by the behavior during operations
* such as insertion, movement, or deletion within the tree structure.
*
* @template T of NestedSetsBehavior
*
* @phpstan-var ActiveRecord<T>|null
*/
public string $rightAttribute = 'rgt';
protected ActiveRecord|null $node = null;

/**
* Stores the right value for the current operation.
* Stores the current operation being performed on the node.
*
* Holds the operation type as a string identifier, such as 'appendTo', 'deleteWithChildren', or other defined
* operation constants.
*/
protected int|null $rightValue = null;
protected string|null $operation = null;

/**
* Name of the attribute that stores the tree identifier for supporting multiple trees.
* Stores the right value for the current operation.
*/
public string|false $treeAttribute = false;
protected int|null $rightValue = null;

/**
* Database connection instance used for executing queries.
Expand Down Expand Up @@ -275,30 +275,6 @@ public function afterUpdate(): void
$this->invalidateCache();
}

/**
* Invalidates cached attribute values and resets internal state.
*
* Clears the cached depth, left, and right attribute values, forcing them to be re-fetched from the owner model
* on next access.
*
* This method should be called after operations that modify the owner model's attributes to ensure that cached
* values remain consistent with the actual model state.
*
* Usage example:
* ```php
* // After modifying the model's attributes externally
* $behavior->invalidateCache();
* ```
*/
public function invalidateCache(): void
{
$this->depthValue = null;
$this->leftValue = null;
$this->node = null;
$this->operation = null;
$this->rightValue = null;
}

/**
* Appends the current node as the last child of the specified target node.
*
Expand Down Expand Up @@ -669,6 +645,30 @@ public function insertBefore(ActiveRecord $node, bool $runValidation = true, arr
return $this->executeOperation($node, self::OPERATION_INSERT_BEFORE, $runValidation, $attributes);
}

/**
* Invalidates cached attribute values and resets internal state.
*
* Clears the cached depth, left, and right attribute values, forcing them to be re-fetched from the owner model
* on next access.
*
* This method should be called after operations that modify the owner model's attributes to ensure that cached
* values remain consistent with the actual model state.
*
* Usage example:
* ```php
* // After modifying the model's attributes externally
* $behavior->invalidateCache();
* ```
*/
public function invalidateCache(): void
{
$this->depthValue = null;
$this->leftValue = null;
$this->node = null;
$this->operation = null;
$this->rightValue = null;
}

/**
* Determines whether the current node is a direct or indirect child of the specified parent node.
*
Expand Down Expand Up @@ -1079,46 +1079,6 @@ protected function beforeInsertRootNode(): void
$this->getOwner()->setAttribute($this->depthAttribute, 0);
}

/**
* Deletes the current node and all its descendant nodes from the nested set tree.
*
* Executes a bulk deletion of the node to which this behavior is attached, along with all its children, by
* constructing a condition that matches all nodes within the left and right boundaries of the current node.
*
* This method is used internally to efficiently remove entire subtrees in a single operation, ensuring the
* integrity of the nested set structure.
*
* It also triggers the appropriate lifecycle events before and after deletion, and resets the old attributes of the
* owner model.
*
* The method applies the tree attribute condition if multi-tree support is enabled, restricting the deletion to
* nodes within the same tree.
*
* @return false|int Number of rows deleted, or `false` if the deletion is unsuccessful for any reason.
*/
protected function deleteWithChildrenInternal(): bool|int
{
if ($this->getOwner()->beforeDelete() === false) {
return false;
}

$result = $this->getOwner()::deleteAll(
QueryConditionBuilder::createRangeCondition(
$this->leftAttribute,
$this->getLeftValue(),
$this->rightAttribute,
$this->getRightValue(),
$this->treeAttribute,
$this->getTreeValue($this->getOwner()),
),
);

$this->getOwner()->setOldAttributes(null);
$this->getOwner()->afterDelete();

return $result;
}

/**
* Executes node movement using the provided context.
*
Expand Down Expand Up @@ -1246,6 +1206,46 @@ private function createMoveContext(ActiveRecord $targetNode, string|null $operat
};
}

/**
* Deletes the current node and all its descendant nodes from the nested set tree.
*
* Executes a bulk deletion of the node to which this behavior is attached, along with all its children, by
* constructing a condition that matches all nodes within the left and right boundaries of the current node.
*
* This method is used internally to efficiently remove entire subtrees in a single operation, ensuring the
* integrity of the nested set structure.
*
* It also triggers the appropriate lifecycle events before and after deletion, and resets the old attributes of the
* owner model.
*
* The method applies the tree attribute condition if multi-tree support is enabled, restricting the deletion to
* nodes within the same tree.
*
* @return false|int Number of rows deleted, or `false` if the deletion is unsuccessful for any reason.
*/
private function deleteWithChildrenInternal(): bool|int
{
if ($this->getOwner()->beforeDelete() === false) {
return false;
}

$result = $this->getOwner()::deleteAll(
QueryConditionBuilder::createRangeCondition(
$this->leftAttribute,
$this->getLeftValue(),
$this->rightAttribute,
$this->getRightValue(),
$this->treeAttribute,
$this->getTreeValue($this->getOwner()),
),
);

$this->getOwner()->setOldAttributes(null);
$this->getOwner()->afterDelete();

return $result;
}

private function executeCrossTreeMove(
NodeContext $context,
string $treeAttribute,
Expand Down Expand Up @@ -1383,20 +1383,12 @@ private function getDb(): Connection

private function getDepthValue(): int
{
if ($this->depthValue === null) {
$this->depthValue = $this->getOwner()->getAttribute($this->depthAttribute);
}

return $this->depthValue;
return $this->depthValue ??= $this->getOwner()->getAttribute($this->depthAttribute);
}

private function getLeftValue(): int
{
if ($this->leftValue === null) {
$this->leftValue = $this->getOwner()->getAttribute($this->leftAttribute);
}

return $this->leftValue;
return $this->leftValue ??= $this->getOwner()->getAttribute($this->leftAttribute);
}

/**
Expand Down Expand Up @@ -1425,11 +1417,7 @@ private function getOwner(): ActiveRecord

private function getRightValue(): int
{
if ($this->rightValue === null) {
$this->rightValue = $this->getOwner()->getAttribute($this->rightAttribute);
}

return $this->rightValue;
return $this->rightValue ??= $this->getOwner()->getAttribute($this->rightAttribute);
}

/**
Expand Down
Loading