CoolCMS API

TreeTrait

Public API of nested-set tree operations: moving and deleting a subtree. Wraps them in a transaction and delegates to the service ({@see \App\Core\Traits\Api\Models\Services\TreeTrait}), which handles cache invalidation, and to the mapper/repository, which run the set-based SQL.

Table of Contents

Constants

META_MOVE_SUBTREE_PARENT  : mixed = 'moveSubtreeToParentId'
Meta key by which an admin/front form says "this entity changed its parent"; the value is the new `parentId` (`null`/0 = root). See {@see applyMetaSubtreeMove()}.

Properties

$service  : Service<string|int, BaseEntity>

Methods

deleteNode()  : Result
Deletes a node with its whole subtree and closes the gap in lft/rgt.
insertTreeNode()  : Result
Computes lft/rgt/level for a newly inserted node (after `save()`, when it has `parent_id` in the database but zero lft/rgt). It is inserted as the last sibling (after the largest `rgt` among nodes with the same `parent_id`).
moveDown()  : Result
Moves after the next sibling.
moveSubtree()  : Result
Moves a whole subtree (root and descendants) under another parent.
moveSubtreeTo()  : Result
Moves a whole subtree under a new parent at a specific position.
moveUp()  : Result
Moves before the previous sibling.
reorderChildren()  : Result
Reorders the direct children of a parent (with whole subtrees) into the given order, in one operation under the tree lock (the "sort children alphabetically" admin grid action; the caller decides the order). A mismatching set of children returns `TREE_STALE` and nothing is written; `data.changed` is the number of moved children.
afterMoveSubtree()  : Result
Hook after a subtree move ({@see moveSubtree()}, {@see moveSubtreeTo()}, so also {@see applyMetaSubtreeMove()} from a form). It runs inside the transaction and the tree lock, so a failure returned from here also rolls back the move itself. The default does nothing; a manager whose derived data depends on the tree path (region slugs) overrides it.
applyMetaSubtreeMove()  : Result|null
Recomputes the nested set after a parent change. Called from `afterSave()`, so inside the save transaction.
treeConfigForEntity()  : TreeConfig
`TreeConfig` used when `save()` computes lft/rgt of a new entity automatically ({@see SaveTrait}).
captureSubtreeAssetIds()  : array<int, int>
Returns the ids of images owned by the whole subtree; empty when the manager declares no image cleanup.
moveTreeNode()  : Result
runTreeTransaction()  : Result
The body of {@see treeTransaction()}: a transaction only when none is running, a tag bump after success, an exception means log plus rollback of the own transaction.
treeService()  : TreeServiceInterface
Loud guard against silent failure: tree operations run only when the underlying service implements the method (`TreeTrait` on service, mapper and repository). A missing one is a wiring bug, so a {@see \LogicException} is thrown (logged through `treeTransaction()` / `save()`) instead of a silent `INVALID_OPERATION` that did not show where the problem is.
treeTransaction()  : Result
Wraps a tree operation in a transaction (when the service supports it): the operations rewrite lft/rgt on many rows and a partial write would break the tree.

Constants

META_MOVE_SUBTREE_PARENT

Meta key by which an admin/front form says "this entity changed its parent"; the value is the new `parentId` (`null`/0 = root). See {@see applyMetaSubtreeMove()}.

public mixed META_MOVE_SUBTREE_PARENT = 'moveSubtreeToParentId'

Properties

Methods

deleteNode()

Deletes a node with its whole subtree and closes the gap in lft/rgt.

public deleteNode(int $id[, TreeConfig $config = new TreeConfig() ]) : Result

The DeleteTrait::delete() hooks do not reach here (the service deletes the subtree through deleteAll()), so a manager declaring ownedAssetIds() gets the image cleanup of the whole subtree here: ids are captured before the delete (the FK disappears after DELETE) and released after a successful commit. A shared image stays (AssetManager::releaseAssetRow()).

Parameters
$id : int
$config : TreeConfig = new TreeConfig()
Return values
Result

insertTreeNode()

Computes lft/rgt/level for a newly inserted node (after `save()`, when it has `parent_id` in the database but zero lft/rgt). It is inserted as the last sibling (after the largest `rgt` among nodes with the same `parent_id`).

public insertTreeNode(int $id[, TreeConfig $config = new TreeConfig() ]) : Result
Parameters
$id : int
$config : TreeConfig = new TreeConfig()
Return values
Result

moveSubtree()

Moves a whole subtree (root and descendants) under another parent.

public moveSubtree(int $id, int|null $newParentId[, TreeConfig $config = new TreeConfig() ]) : Result

Updates lft/rgt/level of the subtree and parent_id of its root.

Parameters
$id : int
$newParentId : int|null

null = move to the root level

$config : TreeConfig = new TreeConfig()
Return values
Result

moveSubtreeTo()

Moves a whole subtree under a new parent at a specific position.

public moveSubtreeTo(int $id, int|null $newParentId, int $afterId[, TreeConfig $config = new TreeConfig() ][, TreeMoveExpectation|null $expectation = null ]) : Result

With $expectation (grid drag and drop) the old parent and the prev/next neighbours are verified under the tree lock; a mismatch returns TREE_STALE and nothing is written. The position is given by afterId and must equal expectation->prevId, otherwise INVALID_ARGUMENT (the client would verify a different place than the one it moves to).

Parameters
$id : int
$newParentId : int|null

null = root level

$afterId : int

-1 = as first child, 0 = as last child, >0 = after the sibling with this id

$config : TreeConfig = new TreeConfig()
$expectation : TreeMoveExpectation|null = null
Return values
Result

reorderChildren()

Reorders the direct children of a parent (with whole subtrees) into the given order, in one operation under the tree lock (the "sort children alphabetically" admin grid action; the caller decides the order). A mismatching set of children returns `TREE_STALE` and nothing is written; `data.changed` is the number of moved children.

public reorderChildren(int|null $parentId, array<int, int> $orderedIds[, TreeConfig $config = new TreeConfig() ]) : Result
Parameters
$parentId : int|null

null = root level

$orderedIds : array<int, int>

ids of all children of the parent in the new order

$config : TreeConfig = new TreeConfig()
Return values
Result

afterMoveSubtree()

Hook after a subtree move ({@see moveSubtree()}, {@see moveSubtreeTo()}, so also {@see applyMetaSubtreeMove()} from a form). It runs inside the transaction and the tree lock, so a failure returned from here also rolls back the move itself. The default does nothing; a manager whose derived data depends on the tree path (region slugs) overrides it.

protected afterMoveSubtree(int $id, Result $result) : Result

Reordering children (reorderChildren()) does not change the path and does not call it.

Parameters
$id : int
$result : Result
Return values
Result

applyMetaSubtreeMove()

Recomputes the nested set after a parent change. Called from `afterSave()`, so inside the save transaction.

protected applyMetaSubtreeMove(bool $isInsert, BaseEntity $entity, int|null $entityId, array<string, mixed> $meta) : Result|null

IMPORTANT: it belongs in afterSave() because a form that moved the subtree as a separate operation after a successful save() left a new parent_id with unrecomputed lft/rgt when the move failed: a broken tree that could not be repaired from the UI. From here a failure is returned as a Result and the shared SaveTrait also rolls back the parent_id save.

The scope of several trees in one table (menu items) is taken from the entity through treeConfigForEntity(); the caller passes nothing.

Returns null when nothing is moved (insert, missing key, entity without id) or when the move succeeded; otherwise the failed Result.

$entityId is passed separately: BaseEntity declares no id getter (each entity has its own), so only the concrete manager knows it.

Parameters
$isInsert : bool
$entity : BaseEntity
$entityId : int|null
$meta : array<string, mixed>
Return values
Result|null

treeConfigForEntity()

`TreeConfig` used when `save()` computes lft/rgt of a new entity automatically ({@see SaveTrait}).

protected treeConfigForEntity(BaseEntity $entity) : TreeConfig

The scope is read from the entity through ScopedTreeNodeInterface::getTreeScope(): several trees in one table (for example menu items told apart by menuId) are solved by implementing the interface on the entity, not by overriding this method. The test targets the interface, not a concrete class, so it also works for an entity touched through the admin bridge (which persists the Admin variant, not the Api one; the Api SaveTrait rewrites only \Front\ to \Api\ in entityClass). A tree with a single numbering per table (pages) does not implement the interface and gets an empty scope.

Parameters
$entity : BaseEntity
Return values
TreeConfig

captureSubtreeAssetIds()

Returns the ids of images owned by the whole subtree; empty when the manager declares no image cleanup.

private captureSubtreeAssetIds(int $id, TreeConfig $config) : array<int, int>
Parameters
$id : int
$config : TreeConfig
Return values
array<int, int>

runTreeTransaction()

The body of {@see treeTransaction()}: a transaction only when none is running, a tag bump after success, an exception means log plus rollback of the own transaction.

private runTreeTransaction(Closure $operation) : Result
Parameters
$operation : Closure
Return values
Result

treeService()

Loud guard against silent failure: tree operations run only when the underlying service implements the method (`TreeTrait` on service, mapper and repository). A missing one is a wiring bug, so a {@see \LogicException} is thrown (logged through `treeTransaction()` / `save()`) instead of a silent `INVALID_OPERATION` that did not show where the problem is.

private treeService(string $method) : TreeServiceInterface
Parameters
$method : string
Return values
TreeServiceInterface

treeTransaction()

Wraps a tree operation in a transaction (when the service supports it): the operations rewrite lft/rgt on many rows and a partial write would break the tree.

private treeTransaction(Closure $operation[, TreeConfig|null $lockConfig = null ]) : Result

IMPORTANT: opens its own transaction only when none is running. A nested call (typically applyMetaSubtreeMove() from afterSave inside SaveTrait::save()) runs in the caller's transaction and only returns the result; the caller handles commit/rollback.

That way the caller keeps control over commit/rollback and returns the concrete Result (TREE_NODE_CORRUPTED) instead of a generic save error.

IMPORTANT: tree lock ($lockConfig): when passed, it is taken before the transaction opens and released only after commit/rollback, so no other operation ever reads bounds this one has not committed yet, and waiting for the lock does not open a transaction (a timeout returns TREE_LOCKED, nothing started). The repository takes the lock again under it (re-entrant GET_LOCK). Without $lockConfig (older callers, for example PageManager) only the repository locks.

Parameters
$operation : Closure
$lockConfig : TreeConfig|null = null
Return values
Result
On this page

Search results