From 793d01f342d81c0af281884cb9e4e2273c3784b8 Mon Sep 17 00:00:00 2001 From: ildyria Date: Tue, 25 Mar 2025 19:56:53 +0100 Subject: [PATCH 01/10] WIP towards stricter types --- phpstan.neon | 9 +- src/AncestorsRelation.php | 13 +- src/BaseRelation.php | 31 +- src/Collection.php | 22 +- src/Contracts/NestedSetCollection.php | 47 +++ src/{ => Contracts}/Node.php | 80 ++-- src/Contracts/NodeQueryBuilder.php | 387 ++++++++++++++++++++ src/DescendantsRelation.php | 7 +- src/{ => Exceptions}/NestedSetException.php | 2 +- src/NestedSet.php | 1 + src/NodeTrait.php | 3 + src/QueryBuilder.php | 83 +++-- tests/models/Category.php | 2 +- tests/models/DuplicateCategory.php | 2 +- tests/models/MenuItem.php | 2 +- 15 files changed, 580 insertions(+), 111 deletions(-) create mode 100644 src/Contracts/NestedSetCollection.php rename src/{ => Contracts}/Node.php (79%) create mode 100644 src/Contracts/NodeQueryBuilder.php rename src/{ => Exceptions}/NestedSetException.php (86%) diff --git a/phpstan.neon b/phpstan.neon index 1e5e209..a1f1667 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,16 +3,11 @@ includes: - vendor/lychee-org/phpstan-lychee/phpstan.neon parameters: - level: 3 + level: 2 paths: - src excludePaths: stubFiles: ignoreErrors: # - identifier: missingType.generics - - '#Interface must be located in "Contract" or "Contracts" namespace#' - - '#Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder<.*>::select\(\).#' - - '#Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder<.*>::from\(\).#' - # - '#Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder<.*>::whereRaw\(\).#' - - '#Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder<.*>::whereNested\(\).#' - - '#Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder<.*>::whereIn\(\).#' + - '#Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder::whereRaw\(\).#' diff --git a/src/AncestorsRelation.php b/src/AncestorsRelation.php index 2fabefe..6f24636 100644 --- a/src/AncestorsRelation.php +++ b/src/AncestorsRelation.php @@ -3,17 +3,17 @@ namespace Kalnoy\Nestedset; use Illuminate\Database\Eloquent\Model; +use Kalnoy\Nestedset\Contracts\Node; +use Kalnoy\Nestedset\Contracts\NodeQueryBuilder; /** * @template Tmodel of Model * - * @phpstan-type NodeModel Node&Tmodel - * - * @disregard P1037 + * @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node&Tmodel * * @extends BaseRelation */ -class AncestorsRelation extends BaseRelation +final class AncestorsRelation extends BaseRelation { /** * Set the base constraints on the relation query. @@ -36,13 +36,13 @@ public function addConstraints() * * @return bool */ - protected function matches(Model $model, $related): bool + protected function matches(Node $model, Node $related): bool { return $related->isAncestorOf($model); } /** - * @param QueryBuilder $query + * @param NodeQueryBuilder $query * @param NodeModel $model * * @return void @@ -57,6 +57,7 @@ protected function addEagerConstraint($query, $model) */ protected function relationExistenceCondition(string $hash, string $table, string $lft, string $rgt): string { + /** @disregard P1013 */ $key = $this->getBaseQuery()->getGrammar()->wrap($this->parent->getKeyName()); return "{$table}.{$rgt} between {$hash}.{$lft} and {$hash}.{$rgt} and $table.$key <> $hash.$key"; diff --git a/src/BaseRelation.php b/src/BaseRelation.php index e263b4d..6ff79a9 100644 --- a/src/BaseRelation.php +++ b/src/BaseRelation.php @@ -7,23 +7,23 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Query\Builder; +use Kalnoy\Nestedset\Contracts\Node; +use Kalnoy\Nestedset\Contracts\NodeQueryBuilder; +use Kalnoy\Nestedset\Contracts\NestedSetCollection; /** * @template Tmodel of Model * - * @phpstan-type NodeModel Node&Tmodel - * - * @extends Relation> + * @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node&Model + * @extends Relation>> * * @property NodeModel $related * @property NodeModel $parent - * - * @method NodeModel getParent() */ -abstract class BaseRelation extends Relation +abstract class BaseRelation extends Relation // @phpstan-ignore generics.notSubtype (Phpstan does not recognise the require-extend on the interface) { /** - * @var QueryBuilder + * @var NodeQueryBuilder */ protected $query; @@ -60,10 +60,10 @@ public function __construct(QueryBuilder $builder, Model $model) * * @return bool */ - abstract protected function matches(Model&Node $model, Node $related): bool; + abstract protected function matches(Node $model, Node $related): bool; /** - * @param QueryBuilder $query + * @param NodeQueryBuilder $query * @param NodeModel $model * * @return void @@ -90,6 +90,7 @@ abstract protected function relationExistenceCondition(string $hash, string $tab public function getRelationExistenceQuery(EloquentBuilder $query, EloquentBuilder $parentQuery, $columns = ['*'], ) { + /** @disregard P1006 */ $query = $this->getParent()->replicate()->newScopedQuery()->select($columns); $table = $query->getModel()->getTable(); @@ -106,7 +107,7 @@ public function getRelationExistenceQuery(EloquentBuilder $query, EloquentBuilde $grammar->wrap($this->parent->getLftName()), $grammar->wrap($this->parent->getRgtName())); - return $query->whereRaw($condition); /** @phpstan-ignore-line */ + return $query->whereRaw($condition); } /** @@ -137,11 +138,11 @@ public function getRelationCountHash($incrementJoinCount = true) /** * Get the results of the relationship. * - * @return Collection + * @return NestedSetCollection */ public function getResults() { - /** @var Collection */ + /** @disregard P1013 */ $result = $this->query->get(); return $result; @@ -164,6 +165,7 @@ public function addEagerConstraints(array $models) $this->query->whereNested(function (Builder $inner) use ($models) { // We will use this query in order to apply constraints to the // base query builder + /** @disregard P1013 */ $outer = $this->parent->newQuery()->setQuery($inner); foreach ($models as $model) { @@ -187,6 +189,7 @@ public function match(array $models, EloquentCollection $results, $relation) /** @disregard P1006 */ $related = $this->matchForModel($model, $results); + /** @disregard P1013 */ $model->setRelation($relation, $related); } @@ -197,16 +200,16 @@ public function match(array $models, EloquentCollection $results, $relation) * @param NodeModel $model * @param EloquentCollection $results * - * @return Collection + * @return NestedSetCollection */ protected function matchForModel(Model $model, EloquentCollection $results) { - /** @var Collection */ $result = $this->related->newCollection(); foreach ($results as $related) { /** @disregard P1006 */ if ($this->matches($model, $related)) { + /** @disregard P1013 */ $result->push($related); } } diff --git a/src/Collection.php b/src/Collection.php index de80624..17bfcc3 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -4,15 +4,18 @@ use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; +use Kalnoy\Nestedset\Contracts\NestedSetCollection; +use Kalnoy\Nestedset\Exceptions\NestedSetException; /** + * * @template Tmodel of Model * - * @phpstan-type NodeModel Node&Tmodel + * @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node * * @extends EloquentCollection */ -final class Collection extends EloquentCollection +final class Collection extends EloquentCollection implements NestedSetCollection { /** * Fill `parent` and `children` relationships for every node in the collection. @@ -34,16 +37,19 @@ public function linkNodes() /** @var NodeModel $node */ foreach ($this->items as $node) { if ($node->getParentId() === null) { + /** @disregard */ $node->setRelation('parent', null); } /** @var array */ - $children = $groupedNodes->get($node->getKey(), []); /** @phpstan-ignore varTag.type */ + $children = $groupedNodes->get($node->getKey(), []); foreach ($children as $child) { + /** @disregard */ $child->setRelation('parent', $node); } + /** @disregard */ $node->setRelation('children', EloquentCollection::make($children)); } @@ -61,7 +67,7 @@ public function linkNodes() * * @return Collection */ - public function toTree($root = false) + public function toTree($root = false): Collection { if ($this->isEmpty()) { return new static(); @@ -131,15 +137,15 @@ public function toFlatTree($root = false): Collection $result = new Collection(); if ($this->isEmpty()) { - return $result; /** @phpstan-ignore-line */ + return $result; } /** @var NodeModel */ $first = $this->first(); /** @var Collection */ - $groupedNodes = $this->groupBy($first->getParentIdName()); /** @phpstan-ignore varTag.type */ + $groupedNodes = $this->groupBy($first->getParentIdName()); - return $result->flattenTree($groupedNodes, $this->getRootNodeId($root)); /** @phpstan-ignore-line */ + return $result->flattenTree($groupedNodes, $this->getRootNodeId($root)); } /** @@ -153,7 +159,7 @@ public function toFlatTree($root = false): Collection protected function flattenTree(Collection $groupedNodes, $parentId): Collection { /** @var array */ - $nodes = $groupedNodes->get($parentId, []); /** @phpstan-ignore varTag.type */ + $nodes = $groupedNodes->get($parentId, []); foreach ($nodes as $node) { $this->push($node); diff --git a/src/Contracts/NestedSetCollection.php b/src/Contracts/NestedSetCollection.php new file mode 100644 index 0000000..30cc5d6 --- /dev/null +++ b/src/Contracts/NestedSetCollection.php @@ -0,0 +1,47 @@ + + * + * @require-extends \Illuminate\Database\Eloquent\Collection + */ +interface NestedSetCollection +{ + /** + * Fill `parent` and `children` relationships for every node in the collection. + * + * This will overwrite any previously set relations. + * + * @return $this + */ + public function linkNodes(); + + + /** + * Build a tree from a list of nodes. Each item will have set children relation. + * + * To successfully build tree "id", "_lft" and "parent_id" keys must present. + * + * If `$root` is provided, the tree will contain only descendants of that node. + * + * @param mixed $root + * + * @return Collection + */ + public function toTree($root = false): NestedSetCollection; + + /** + * Build a list of nodes that retain the order that they were pulled from + * the database. + * + * @param bool $root + * + * @return Collection + */ + public function toFlatTree($root = false): NestedSetCollection; +} diff --git a/src/Node.php b/src/Contracts/Node.php similarity index 79% rename from src/Node.php rename to src/Contracts/Node.php index fd2c0cc..2ba4710 100644 --- a/src/Node.php +++ b/src/Contracts/Node.php @@ -1,12 +1,13 @@ &Tmodel + * @phpstan-type NodeModel Node + * + * @require-extends \Illuminate\Database\Eloquent\Model */ interface Node { @@ -50,21 +53,21 @@ public function children(): HasMany; * * @return DescendantsRelation */ - public function descendants(): DescendantsRelation; + public function descendants(): Relation; /** * Get query for siblings of the node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function siblings(): QueryBuilder; + public function siblings(): NodeQueryBuilder; /** * Get the node siblings and the node itself. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function siblingsAndSelf(): QueryBuilder; + public function siblingsAndSelf(): NodeQueryBuilder; /** * Get query for the node siblings and the node itself. @@ -78,37 +81,37 @@ public function getSiblingsAndSelf(array $columns = ['*']): EloquentCollection; /** * Get query for siblings after the node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function nextSiblings(): QueryBuilder; + public function nextSiblings(): NodeQueryBuilder; /** * Get query for siblings before the node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function prevSiblings(): QueryBuilder; + public function prevSiblings(): NodeQueryBuilder; /** * Get query for nodes after current node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function nextNodes(): QueryBuilder; + public function nextNodes(): NodeQueryBuilder; /** * Get query for nodes before current node in reversed order. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function prevNodes(): QueryBuilder; + public function prevNodes(): NodeQueryBuilder; /** * Get query ancestors of the node. * * @return AncestorsRelation */ - public function ancestors(): AncestorsRelation; + public function ancestors(): Relation; /** * Make this node a root node. @@ -154,27 +157,27 @@ public function down(int $amount = 1): bool; /** * @since 2.0 * - * @param BaseQueryBuilder|EloquentBuilder|QueryBuilder $query + * @param BaseQueryBuilder|EloquentBuilder|NodeQueryBuilder $query * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function newEloquentBuilder(BaseQueryBuilder|EloquentBuilder|QueryBuilder $query): QueryBuilder; + public function newEloquentBuilder(BaseQueryBuilder|EloquentBuilder|NodeQueryBuilder $query): NodeQueryBuilder; /** * Get a new base query that includes deleted nodes. * * @since 1.1 * - * @param (QueryBuilder)|string|null $table + * @param NodeQueryBuilder|string|null $table * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function newNestedSetQuery(QueryBuilder|string|null $table = null): QueryBuilder; + public function newNestedSetQuery(NodeQueryBuilder|string|null $table = null): NodeQueryBuilder; /** * @param ?string $table * - * @return QueryBuilder + * @return NodeQueryBuilder */ public function newScopedQuery($table = null); @@ -189,16 +192,16 @@ public function applyNestedSetScope($query, $table = null); /** * @param string[] $attributes * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public static function scoped(array $attributes): QueryBuilder; + public static function scoped(array $attributes): NodeQueryBuilder; /** * @param array $models * - * @return Collection + * @return NestedSetCollection */ - public function newCollection(array $models = []): Collection; + public function newCollection(array $models = []): NestedSetCollection; /** * Get node height (rgt - lft + 1). @@ -226,6 +229,9 @@ public function setParentIdAttribute(mixed $value): void; */ public function isRoot(): bool; + /** + * Get whether node is a leaf. + */ public function isLeaf(): bool; /** @@ -421,7 +427,23 @@ public function isSelfOrDescendantOf(Node $other); /** * Create a new Query. * - * @return QueryBuilder + * @return NodeQueryBuilder */ public function newQuery(); + + /** + * Get the value of the model's primary key. + * ! This is directly from Model method... + * + * @return mixed + */ + public function getKey(); + + /** + * Get the value of the model's primary key. + * ! This is directly from Model method... + * + * @return mixed + */ + public function getKeyName(); } diff --git a/src/Contracts/NodeQueryBuilder.php b/src/Contracts/NodeQueryBuilder.php new file mode 100644 index 0000000..fd10fbb --- /dev/null +++ b/src/Contracts/NodeQueryBuilder.php @@ -0,0 +1,387 @@ + + * + * @require-extends \Illuminate\Database\Eloquent\Builder + * @extends \Illuminate\Database\Eloquent\Builder + * + * @method NodeQueryBuilder select(array $columns) + * @method Tmodel getModel() + * @method NodeQueryBuilder from(string $table) + * @method NodeQueryBuilder getQuery() + * @method NodeQueryBuilder whereRaw(string $sql, array $bindings = [], string $boolean = 'and') + * @method \Illuminate\Database\Query\Grammars\Grammar getGrammar() + * @method NodeQueryBuilder whereNested(\Closure|string $callback, string $boolean = 'and') + * @method EloquentCollection get() + */ +interface NodeQueryBuilder +{ + /** + * Get node's `lft` and `rgt` values. + * + * @since 2.0 + * + * @param NodeModel $id + * @param bool $required + * + * @return array + */ + public function getNodeData(mixed $id, $required = false): array; + + /** + * Get plain node data. + * + * @since 2.0 + * + * @param NodeModel $id + * @param bool $required + * + * @return array + */ + public function getPlainNodeData(mixed $id, $required = false): array; + + /** + * Scope limits query to select just root node. + * + * @return NodeQueryBuilder + */ + public function whereIsRoot(): NodeQueryBuilder; + + /** + * Limit results to ancestors of specified node. + * + * @since 2.0 + * + * @param NodeModel $id + * @param bool $andSelf + * @param string $boolean + * + * @return NodeQueryBuilder + */ + public function whereAncestorOf(mixed $id, bool $andSelf = false, string $boolean = 'and'): NodeQueryBuilder; + + /** + * @param NodeModel $id + * @param bool $andSelf + * + * @return NodeQueryBuilder + */ + public function orWhereAncestorOf(mixed $id, $andSelf = false): NodeQueryBuilder; + + /** + * @param NodeModel $id + * + * @return NodeQueryBuilder + */ + public function whereAncestorOrSelf(mixed $id): NodeQueryBuilder; + + /** + * Get ancestors of specified node. + * + * @since 2.0 + * + * @param NodeModel $id + * @param string[] $columns + * + * @return EloquentCollection + */ + public function ancestorsOf(mixed $id, array $columns = ['*']): EloquentCollection; + + /** + * @param NodeModel $id + * @param string[] $columns + * + * @return EloquentCollection + */ + public function ancestorsAndSelf(mixed $id, array $columns = ['*']): EloquentCollection; + + /** + * Add node selection statement between specified range. + * + * @since 2.0 + * + * @param int[] $values + * @param string $boolean + * @param bool $not + * + * @return NodeQueryBuilder + */ + public function whereNodeBetween(array $values, $boolean = 'and', $not = false): NodeQueryBuilder; + + /** + * Add node selection statement between specified range joined with `or` operator. + * + * @since 2.0 + * + * @param int[] $values + * + * @return NodeQueryBuilder + */ + public function orWhereNodeBetween(array $values): NodeQueryBuilder; + + /** + * Add constraint statement to descendants of specified node. + * + * @since 2.0 + * + * @param ?NodeModel $id + * @param string $boolean + * @param bool $not + * @param bool $andSelf + * + * @return NodeQueryBuilder + */ + public function whereDescendantOf(mixed $id, $boolean = 'and', $not = false, $andSelf = false): NodeQueryBuilder; + + /** + * @param NodeModel $id + * + * @return NodeQueryBuilder + */ + public function whereNotDescendantOf(mixed $id): NodeQueryBuilder; + + /** + * @param NodeModel $id + * + * @return NodeQueryBuilder + */ + public function orWhereDescendantOf(mixed $id): NodeQueryBuilder; + + /** + * @param NodeModel $id + * + * @return NodeQueryBuilder + */ + public function orWhereNotDescendantOf(mixed $id): NodeQueryBuilder; + + /** + * @param NodeModel $id + * @param string $boolean + * @param bool $not + * + * @return NodeQueryBuilder + */ + public function whereDescendantOrSelf(mixed $id, string $boolean = 'and', bool $not = false): NodeQueryBuilder; + + /** + * Get descendants of specified node. + * + * @since 2.0 + * + * @param NodeModel $id + * @param string[] $columns + * @param bool $andSelf + * + * @return EloquentCollection|Collection + */ + public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = false): EloquentCollection; + + /** + * @param NodeModel $id + * @param string[] $columns + * + * @return EloquentCollection + */ + public function descendantsAndSelf($id, array $columns = ['*']): EloquentCollection; + + /** + * Constraint nodes to those that are after specified node. + * + * @since 2.0 + * + * @param NodeModel $id + * @param string $boolean + * + * @return NodeQueryBuilder + */ + public function whereIsAfter($id, $boolean = 'and'): NodeQueryBuilder; + + /** + * Constraint nodes to those that are before specified node. + * + * @since 2.0 + * + * @param NodeModel $id + * @param string $boolean + * + * @return NodeQueryBuilder + */ + public function whereIsBefore($id, $boolean = 'and'): NodeQueryBuilder; + + /** + * @return NodeQueryBuilder + */ + public function whereIsLeaf(): NodeQueryBuilder; + + /** + * @param string[] $columns + * + * @return EloquentCollection + */ + public function leaves(array $columns = ['*']): EloquentCollection; + + /** + * Include depth level into the result. + * + * @param string $as + * + * @return NodeQueryBuilder + */ + public function withDepth($as = 'depth'): NodeQueryBuilder; + + /** + * Exclude root node from the result. + * + * @return NodeQueryBuilder + */ + public function withoutRoot(): NodeQueryBuilder; + + /** + * Equivalent of `withoutRoot`. + * + * @since 2.0 + * @deprecated since v4.1 + * + * @return NodeQueryBuilder + */ + public function hasParent(): NodeQueryBuilder; + + /** + * Get only nodes that have children. + * + * @since 2.0 + * @deprecated since v4.1 + * + * @return NodeQueryBuilder + */ + public function hasChildren(): NodeQueryBuilder; + + /** + * Order by node position. + * + * @param string $dir + * + * @return NodeQueryBuilder + */ + public function defaultOrder($dir = 'asc'): NodeQueryBuilder; + + /** + * Order by reversed node position. + * + * @return NodeQueryBuilder + */ + public function reversed(): NodeQueryBuilder; + + /** + * Move a node to the new position. + * + * @param mixed $key + * @param int $position + * + * @return int + */ + public function moveNode($key, $position): int; + + + /** + * Make or remove gap in the tree. Negative height will remove gap. + * + * @since 2.0 + * + * @param int $cut + * @param int $height + * + * @return int + */ + public function makeGap($cut, $height): int; + + /** + * Get statistics of errors of the tree. + * + * @since 2.0 + * + * @return array{oddness:int,duplicates:int,wrong_parent:int,missing_parent:int} + */ + public function countErrors(): array; + + /** + * Get the number of total errors of the tree. + * + * @since 2.0 + * + * @return int + */ + public function getTotalErrors(): int; + + /** + * Get whether the tree is broken. + * + * @since 2.0 + * + * @return bool + */ + public function isBroken(): bool; + + /** + * Fixes the tree based on parentage info. + * + * Nodes with invalid parent are saved as roots. + * + * @param ?NodeModel $root + * + * @return int The number of changed nodes + */ + public function fixTree($root = null): int; + + /** + * @param NodeModel $root + * + * @return int + */ + public function fixSubtree($root): int; + + /** + * Rebuild the tree based on raw data. + * + * If item data does not contain primary key, new node will be created. + * + * @param array[] $data + * @param bool $delete Whether to delete nodes that exists but not in the data array + * @param ?NodeModel $root + * + * @return int + */ + public function rebuildTree(array $data, $delete = false, $root = null): int; + + /** + * @param null $root + * @param array[] $data + * @param bool $delete + * + * @return int + */ + public function rebuildSubtree($root, array $data, $delete = false): int; + + /** + * @param string|null $table + * + * @return NodeQueryBuilder + */ + public function applyNestedSetScope($table = null): NodeQueryBuilder; + + /** + * Get the root node. + * + * @param string[] $columns + * + * @return NodeModel|null + */ + public function root(array $columns = ['*']): Node|null; +} \ No newline at end of file diff --git a/src/DescendantsRelation.php b/src/DescendantsRelation.php index 2e13bcc..02ed7bb 100644 --- a/src/DescendantsRelation.php +++ b/src/DescendantsRelation.php @@ -3,17 +3,18 @@ namespace Kalnoy\Nestedset; use Illuminate\Database\Eloquent\Model; +use Kalnoy\Nestedset\Contracts\Node; /** * @template Tmodel of Model * - * @phpstan-type NodeModel Node&Tmodel + * @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node * * @disregard P1037 * * @extends BaseRelation */ -class DescendantsRelation extends BaseRelation +final class DescendantsRelation extends BaseRelation { /** * Set the base constraints on the relation query. @@ -45,7 +46,7 @@ protected function addEagerConstraint($query, $model) * * @return bool */ - protected function matches(Model $model, $related): bool + protected function matches(Node $model, Node $related): bool { return $related->isDescendantOf($model); } diff --git a/src/NestedSetException.php b/src/Exceptions/NestedSetException.php similarity index 86% rename from src/NestedSetException.php rename to src/Exceptions/NestedSetException.php index 38bad44..2d31573 100644 --- a/src/NestedSetException.php +++ b/src/Exceptions/NestedSetException.php @@ -1,6 +1,6 @@ */ -class QueryBuilder extends Builder +class QueryBuilder extends Builder implements NodeQueryBuilder { /** * @var NodeModel @@ -35,7 +38,7 @@ class QueryBuilder extends Builder * * @return array */ - public function getNodeData(mixed $id, $required = false) + public function getNodeData(mixed $id, $required = false): array { $query = $this->toBase(); @@ -61,7 +64,7 @@ public function getNodeData(mixed $id, $required = false) * * @return array */ - public function getPlainNodeData(mixed $id, $required = false) + public function getPlainNodeData(mixed $id, $required = false): array { return array_values($this->getNodeData($id, $required)); } @@ -69,9 +72,9 @@ public function getPlainNodeData(mixed $id, $required = false) /** * Scope limits query to select just root node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function whereIsRoot(): QueryBuilder + public function whereIsRoot(): NodeQueryBuilder { $this->query->whereNull($this->model->getParentIdName()); @@ -87,9 +90,9 @@ public function whereIsRoot(): QueryBuilder * @param bool $andSelf * @param string $boolean * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function whereAncestorOf(mixed $id, bool $andSelf = false, string $boolean = 'and') + public function whereAncestorOf(mixed $id, bool $andSelf = false, string $boolean = 'and'): NodeQueryBuilder { $keyName = $this->model->getTable() . '.' . $this->model->getKeyName(); @@ -158,7 +161,7 @@ public function whereAncestorOrSelf(mixed $id): QueryBuilder * * @return EloquentCollection */ - public function ancestorsOf(mixed $id, array $columns = ['*']) + public function ancestorsOf(mixed $id, array $columns = ['*']): EloquentCollection { return $this->whereAncestorOf($id)->get($columns); } @@ -169,7 +172,7 @@ public function ancestorsOf(mixed $id, array $columns = ['*']) * * @return EloquentCollection */ - public function ancestorsAndSelf(mixed $id, array $columns = ['*']) + public function ancestorsAndSelf(mixed $id, array $columns = ['*']): EloquentCollection { return $this->whereAncestorOf($id, true)->get($columns); } @@ -185,7 +188,7 @@ public function ancestorsAndSelf(mixed $id, array $columns = ['*']) * * @return QueryBuilder */ - public function whereNodeBetween(array $values, $boolean = 'and', $not = false) + public function whereNodeBetween(array $values, $boolean = 'and', $not = false): NodeQueryBuilder { $this->query->whereBetween($this->model->getTable() . '.' . $this->model->getLftName(), $values, $boolean, $not); @@ -201,7 +204,7 @@ public function whereNodeBetween(array $values, $boolean = 'and', $not = false) * * @return QueryBuilder */ - public function orWhereNodeBetween(array $values) + public function orWhereNodeBetween(array $values): NodeQueryBuilder { return $this->whereNodeBetween($values, 'or'); } @@ -220,7 +223,7 @@ public function orWhereNodeBetween(array $values) */ public function whereDescendantOf(mixed $id, $boolean = 'and', $not = false, $andSelf = false, - ) { + ): NodeQueryBuilder { if (NestedSet::isNode($id)) { $data = $id->getBounds(); } else { @@ -241,7 +244,7 @@ public function whereDescendantOf(mixed $id, $boolean = 'and', $not = false, * * @return QueryBuilder */ - public function whereNotDescendantOf(mixed $id) + public function whereNotDescendantOf(mixed $id): NodeQueryBuilder { return $this->whereDescendantOf($id, 'and', true); } @@ -251,7 +254,7 @@ public function whereNotDescendantOf(mixed $id) * * @return QueryBuilder */ - public function orWhereDescendantOf(mixed $id) + public function orWhereDescendantOf(mixed $id): NodeQueryBuilder { return $this->whereDescendantOf($id, 'or'); } @@ -261,7 +264,7 @@ public function orWhereDescendantOf(mixed $id) * * @return QueryBuilder */ - public function orWhereNotDescendantOf(mixed $id) + public function orWhereNotDescendantOf(mixed $id): NodeQueryBuilder { return $this->whereDescendantOf($id, 'or', true); } @@ -273,7 +276,7 @@ public function orWhereNotDescendantOf(mixed $id) * * @return QueryBuilder */ - public function whereDescendantOrSelf(mixed $id, string $boolean = 'and', bool $not = false) + public function whereDescendantOrSelf(mixed $id, string $boolean = 'and', bool $not = false): NodeQueryBuilder { return $this->whereDescendantOf($id, $boolean, $not, true); } @@ -289,7 +292,7 @@ public function whereDescendantOrSelf(mixed $id, string $boolean = 'and', bool $ * * @return EloquentCollection|Collection */ - public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = false) + public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = false): EloquentCollection { try { return $this->whereDescendantOf($id, 'and', false, $andSelf)->get($columns); @@ -304,7 +307,7 @@ public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = * * @return EloquentCollection */ - public function descendantsAndSelf($id, array $columns = ['*']) + public function descendantsAndSelf($id, array $columns = ['*']): EloquentCollection { return $this->descendantsOf($id, $columns, true); } @@ -352,7 +355,7 @@ protected function whereIsBeforeOrAfter(mixed $id, string $operator, string $boo * * @return QueryBuilder */ - public function whereIsAfter($id, $boolean = 'and') + public function whereIsAfter($id, $boolean = 'and'): NodeQueryBuilder { return $this->whereIsBeforeOrAfter($id, '>', $boolean); } @@ -367,7 +370,7 @@ public function whereIsAfter($id, $boolean = 'and') * * @return QueryBuilder */ - public function whereIsBefore($id, $boolean = 'and') + public function whereIsBefore($id, $boolean = 'and'): NodeQueryBuilder { return $this->whereIsBeforeOrAfter($id, '<', $boolean); } @@ -375,11 +378,11 @@ public function whereIsBefore($id, $boolean = 'and') /** * @return QueryBuilder */ - public function whereIsLeaf() + public function whereIsLeaf(): NodeQueryBuilder { list($lft, $rgt) = $this->wrappedColumns(); - return $this->whereRaw("$lft = $rgt - 1"); /** @phpstan-ignore-line */ + return $this->whereRaw("$lft = $rgt - 1"); } /** @@ -387,7 +390,7 @@ public function whereIsLeaf() * * @return EloquentCollection */ - public function leaves(array $columns = ['*']) + public function leaves(array $columns = ['*']): EloquentCollection { return $this->whereIsLeaf()->get($columns); } @@ -399,7 +402,7 @@ public function leaves(array $columns = ['*']) * * @return QueryBuilder */ - public function withDepth($as = 'depth') + public function withDepth($as = 'depth'): NodeQueryBuilder { if ($this->query->columns === null) { $this->query->columns = ['*']; @@ -543,7 +546,7 @@ public function reversed(): QueryBuilder * * @return int */ - public function moveNode($key, $position) + public function moveNode($key, $position): int { list($lft, $rgt) = $this->model->newNestedSetQuery() ->getPlainNodeData($key, true); @@ -595,7 +598,7 @@ public function moveNode($key, $position) * * @return int */ - public function makeGap($cut, $height) + public function makeGap($cut, $height): int { $params = compact('cut', 'height'); @@ -828,7 +831,7 @@ protected function getMissingParentQuery() * * @return int */ - public function getTotalErrors() + public function getTotalErrors(): int { return array_sum($this->countErrors()); } @@ -840,7 +843,7 @@ public function getTotalErrors() * * @return bool */ - public function isBroken() + public function isBroken(): bool { return $this->getTotalErrors() > 0; } @@ -854,7 +857,7 @@ public function isBroken() * * @return int The number of changed nodes */ - public function fixTree($root = null) + public function fixTree($root = null): int { $columns = [ $this->model->getKeyName(), @@ -881,7 +884,7 @@ public function fixTree($root = null) * * @return int */ - public function fixSubtree($root) + public function fixSubtree($root): int { return $this->fixTree($root); } @@ -892,7 +895,7 @@ public function fixSubtree($root) * * @return int */ - protected function fixNodes(array &$dictionary, $parent = null) + protected function fixNodes(array &$dictionary, $parent = null): int { $parentId = $parent !== null ? $parent->getKey() : null; $cut = $parent !== null ? $parent->getLft() + 1 : 1; @@ -969,10 +972,10 @@ protected static function reorderNodes( * * @return int */ - public function rebuildTree(array $data, $delete = false, $root = null) + public function rebuildTree(array $data, $delete = false, $root = null): int { - if ($this->model->usesSoftDelete()) { /** @phpstan-ignore-line */ - $this->withTrashed(); /** @phpstan-ignore-line */ + if ($this->model->usesSoftDelete()) { + $this->withTrashed(); /** @phpstan-ignore method.notFound (it does exists!) */ } $existing = $this @@ -988,7 +991,7 @@ public function rebuildTree(array $data, $delete = false, $root = null) $this->buildRebuildDictionary($dictionary, $data, $existing, $parentId); if ($existing !== null && $existing !== []) { - if ($delete && !$this->model->usesSoftDelete()) { /** @phpstan-ignore-line */ + if ($delete && !$this->model->usesSoftDelete()) { $this->model ->newScopedQuery() ->whereIn($this->model->getKeyName(), array_keys($existing)) @@ -997,12 +1000,12 @@ public function rebuildTree(array $data, $delete = false, $root = null) foreach ($existing as $model) { $dictionary[$model->getParentId()][] = $model; - if ($delete && $this->model->usesSoftDelete() && /** @phpstan-ignore-line */ - !$model->{$model->getDeletedAtColumn()} /** @phpstan-ignore-line */ + if ($delete && $this->model->usesSoftDelete() && + !$model->{$model->getDeletedAtColumn()} /** @phpstan-ignore property.dynamicName */ ) { $time = $this->model->fromDateTime($this->model->freshTimestamp()); - $model->{$model->getDeletedAtColumn()} = $time; /** @phpstan-ignore-line */ + $model->{$model->getDeletedAtColumn()} = $time; /** @phpstan-ignore property.dynamicName */ } } } @@ -1018,7 +1021,7 @@ public function rebuildTree(array $data, $delete = false, $root = null) * * @return int */ - public function rebuildSubtree($root, array $data, $delete = false) + public function rebuildSubtree($root, array $data, $delete = false): int { return $this->rebuildTree($data, $delete, $root); } @@ -1077,7 +1080,7 @@ protected function buildRebuildDictionary(array &$dictionary, * * @return QueryBuilder */ - public function applyNestedSetScope($table = null) + public function applyNestedSetScope($table = null): NodeQueryBuilder { return $this->model->applyNestedSetScope($this, $table); } diff --git a/tests/models/Category.php b/tests/models/Category.php index e2217a1..0094208 100644 --- a/tests/models/Category.php +++ b/tests/models/Category.php @@ -2,7 +2,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; -use Kalnoy\Nestedset\Node; +use Kalnoy\Nestedset\Contracts\Node; use Kalnoy\Nestedset\NodeTrait; class Category extends Model implements Node diff --git a/tests/models/DuplicateCategory.php b/tests/models/DuplicateCategory.php index 91f2e2d..f23285d 100644 --- a/tests/models/DuplicateCategory.php +++ b/tests/models/DuplicateCategory.php @@ -1,7 +1,7 @@ Date: Wed, 26 Mar 2025 00:18:05 +0100 Subject: [PATCH 02/10] lvl 4 --- phpstan-baseline.neon | 15 +++ phpstan.neon | 10 +- src/AncestorsRelation.php | 2 +- src/BaseRelation.php | 15 ++- src/Collection.php | 35 ++--- src/Contracts/NestedSetCollection.php | 11 +- src/Contracts/Node.php | 50 ++++--- src/Contracts/NodeQueryBuilder.php | 58 ++++---- src/NodeTrait.php | 182 ++++++++++++++------------ src/QueryBuilder.php | 24 ++-- tests/models/Category.php | 3 + tests/models/DuplicateCategory.php | 3 + tests/models/MenuItem.php | 3 + 13 files changed, 230 insertions(+), 181 deletions(-) create mode 100644 phpstan-baseline.neon diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..4acd070 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,15 @@ +parameters: + ignoreErrors: + + - + message: '#^Call to an undefined method Kalnoy\\Nestedset\\Contracts\\NodeQueryBuilder\\:\:whereIn\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/QueryBuilder.php + + - + message: '#^Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder\\:\:whereRaw\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: src/QueryBuilder.php + diff --git a/phpstan.neon b/phpstan.neon index a1f1667..13e1811 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,13 +1,17 @@ includes: - vendor/larastan/larastan/extension.neon - vendor/lychee-org/phpstan-lychee/phpstan.neon + - phpstan-baseline.neon parameters: - level: 2 + level: 4 paths: - src + - tests/models/ excludePaths: stubFiles: ignoreErrors: - # - identifier: missingType.generics - - '#Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder::whereRaw\(\).#' + - '#.*covariant.*#' + - '#.*contravariant.*#' + - + message: '#^Call to an undefined method Kalnoy\\Nestedset\\Contracts\\Node::assert.*\(\)\.$#' \ No newline at end of file diff --git a/src/AncestorsRelation.php b/src/AncestorsRelation.php index 6f24636..1d2b5f9 100644 --- a/src/AncestorsRelation.php +++ b/src/AncestorsRelation.php @@ -43,7 +43,7 @@ protected function matches(Node $model, Node $related): bool /** * @param NodeQueryBuilder $query - * @param NodeModel $model + * @param NodeModel $model * * @return void */ diff --git a/src/BaseRelation.php b/src/BaseRelation.php index 6ff79a9..91fe331 100644 --- a/src/BaseRelation.php +++ b/src/BaseRelation.php @@ -7,20 +7,21 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Query\Builder; +use Kalnoy\Nestedset\Contracts\NestedSetCollection; use Kalnoy\Nestedset\Contracts\Node; use Kalnoy\Nestedset\Contracts\NodeQueryBuilder; -use Kalnoy\Nestedset\Contracts\NestedSetCollection; /** * @template Tmodel of Model * - * @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node&Model - * @extends Relation>> + * @phpstan-type NodeModel Node&Tmodel + * + * @extends Relation> * * @property NodeModel $related * @property NodeModel $parent */ -abstract class BaseRelation extends Relation // @phpstan-ignore generics.notSubtype (Phpstan does not recognise the require-extend on the interface) +abstract class BaseRelation extends Relation { /** * @var NodeQueryBuilder @@ -42,8 +43,8 @@ abstract class BaseRelation extends Relation // @phpstan-ignore generics.notSubt /** * AncestorsRelation constructor. * - * @param QueryBuilder $builder - * @param NodeModel $model + * @param QueryBuilder $builder + * @param NodeModel $model */ public function __construct(QueryBuilder $builder, Model $model) { @@ -64,7 +65,7 @@ abstract protected function matches(Node $model, Node $related): bool; /** * @param NodeQueryBuilder $query - * @param NodeModel $model + * @param NodeModel $model * * @return void */ diff --git a/src/Collection.php b/src/Collection.php index 17bfcc3..6c12ec2 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -8,10 +8,9 @@ use Kalnoy\Nestedset\Exceptions\NestedSetException; /** - * * @template Tmodel of Model * - * @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node + * @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node&Model * * @extends EloquentCollection */ @@ -41,12 +40,13 @@ public function linkNodes() $node->setRelation('parent', null); } - /** @var array */ $children = $groupedNodes->get($node->getKey(), []); - foreach ($children as $child) { - /** @disregard */ - $child->setRelation('parent', $node); + if (count($children) > 0) { + foreach ($children as $child) { + /** @disregard */ + $child->setRelation('parent', $node); + } } /** @disregard */ @@ -65,9 +65,9 @@ public function linkNodes() * * @param mixed $root * - * @return Collection + * @return NestedSetCollection */ - public function toTree($root = false): Collection + public function toTree($root = false): NestedSetCollection { if ($this->isEmpty()) { return new static(); @@ -129,9 +129,9 @@ protected function getRootNodeId($root = false) * * @param bool $root * - * @return Collection + * @return NestedSetCollection */ - public function toFlatTree($root = false): Collection + public function toFlatTree($root = false): NestedSetCollection { /** @Var Collection */ $result = new Collection(); @@ -142,7 +142,6 @@ public function toFlatTree($root = false): Collection /** @var NodeModel */ $first = $this->first(); - /** @var Collection */ $groupedNodes = $this->groupBy($first->getParentIdName()); return $result->flattenTree($groupedNodes, $this->getRootNodeId($root)); @@ -154,16 +153,18 @@ public function toFlatTree($root = false): Collection * @param Collection $groupedNodes * @param array-key $parentId * - * @return Collection + * @return NestedSetCollection */ - protected function flattenTree(Collection $groupedNodes, $parentId): Collection + protected function flattenTree(Collection $groupedNodes, $parentId): NestedSetCollection { - /** @var array */ $nodes = $groupedNodes->get($parentId, []); - foreach ($nodes as $node) { - $this->push($node); - $this->flattenTree($groupedNodes, $node->getKey()); + if (count($nodes) > 0) { + foreach ($nodes as $node) { + $this->push($node); + + $this->flattenTree($groupedNodes, $node->getKey()); + } } return $this; diff --git a/src/Contracts/NestedSetCollection.php b/src/Contracts/NestedSetCollection.php index 30cc5d6..c5c0549 100644 --- a/src/Contracts/NestedSetCollection.php +++ b/src/Contracts/NestedSetCollection.php @@ -1,14 +1,16 @@ * * @require-extends \Illuminate\Database\Eloquent\Collection + * + * @method NestedSetCollection groupBy(string $column) + * @method array all() */ interface NestedSetCollection { @@ -21,7 +23,6 @@ interface NestedSetCollection */ public function linkNodes(); - /** * Build a tree from a list of nodes. Each item will have set children relation. * @@ -31,7 +32,7 @@ public function linkNodes(); * * @param mixed $root * - * @return Collection + * @return NestedSetCollection */ public function toTree($root = false): NestedSetCollection; @@ -41,7 +42,7 @@ public function toTree($root = false): NestedSetCollection; * * @param bool $root * - * @return Collection + * @return NestedSetCollection */ public function toFlatTree($root = false): NestedSetCollection; } diff --git a/src/Contracts/Node.php b/src/Contracts/Node.php index 2ba4710..6d450ae 100644 --- a/src/Contracts/Node.php +++ b/src/Contracts/Node.php @@ -2,12 +2,12 @@ namespace Kalnoy\Nestedset\Contracts; -use Illuminate\Database\Query\Builder as BaseQueryBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Database\Query\Builder as BaseQueryBuilder; /** * Accompanies {@link \Kalnoy\Nestedset\NodeTrait}. @@ -23,9 +23,19 @@ * * @template Tmodel of \Illuminate\Database\Eloquent\Model * - * @phpstan-type NodeModel Node - * + * @phpstan-type NodeModel Node&Tmodel + * * @require-extends \Illuminate\Database\Eloquent\Model + * + * @method mixed getKey() + * @method mixed getKeyName() + * @method Node setRelation($relation, $value) + * @method mixed save() + * @method string getTable() + * @method mixed getAttribute($key) + * @method string getDeletedAtColumn() + * @method Node getRelationValue($key) + * @method bool usesSoftDelete() */ interface Node { @@ -51,7 +61,7 @@ public function children(): HasMany; /** * Get query for descendants of the node. * - * @return DescendantsRelation + * @return Relation&Tmodel>> */ public function descendants(): Relation; @@ -74,9 +84,9 @@ public function siblingsAndSelf(): NodeQueryBuilder; * * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function getSiblingsAndSelf(array $columns = ['*']): EloquentCollection; + public function getSiblingsAndSelf(array $columns = ['*']): NestedSetCollection; /** * Get query for siblings after the node. @@ -109,7 +119,7 @@ public function prevNodes(): NodeQueryBuilder; /** * Get query ancestors of the node. * - * @return AncestorsRelation + * @return Relation&Tmodel>> */ public function ancestors(): Relation; @@ -291,35 +301,35 @@ public function getPrevNode(array $columns = ['*']): Node; /** * @param string[] $columns * - * @return Collection + * @return NestedSetCollection */ public function getAncestors(array $columns = ['*']); /** * @param string[] $columns * - * @return Collection + * @return NestedSetCollection */ public function getDescendants(array $columns = ['*']); /** * @param string[] $columns * - * @return Collection + * @return NestedSetCollection */ public function getSiblings(array $columns = ['*']); /** * @param string[] $columns * - * @return Collection + * @return NestedSetCollection */ public function getNextSiblings(array $columns = ['*']); /** * @param string[] $columns * - * @return Collection + * @return NestedSetCollection */ public function getPrevSiblings(array $columns = ['*']); @@ -430,20 +440,4 @@ public function isSelfOrDescendantOf(Node $other); * @return NodeQueryBuilder */ public function newQuery(); - - /** - * Get the value of the model's primary key. - * ! This is directly from Model method... - * - * @return mixed - */ - public function getKey(); - - /** - * Get the value of the model's primary key. - * ! This is directly from Model method... - * - * @return mixed - */ - public function getKeyName(); } diff --git a/src/Contracts/NodeQueryBuilder.php b/src/Contracts/NodeQueryBuilder.php index fd10fbb..c41328f 100644 --- a/src/Contracts/NodeQueryBuilder.php +++ b/src/Contracts/NodeQueryBuilder.php @@ -2,28 +2,37 @@ namespace Kalnoy\Nestedset\Contracts; -use Illuminate\Database\Eloquent\Collection as EloquentCollection; +use Illuminate\Contracts\Database\Eloquent\Builder; +use Illuminate\Database\Query\Builder as BaseQueryBuilder; /** * @template Tmodel of \Illuminate\Database\Eloquent\Model * * @phpstan-type NodeModel Node * - * @require-extends \Illuminate\Database\Eloquent\Builder - * @extends \Illuminate\Database\Eloquent\Builder - * - * @method NodeQueryBuilder select(array $columns) - * @method Tmodel getModel() - * @method NodeQueryBuilder from(string $table) - * @method NodeQueryBuilder getQuery() - * @method NodeQueryBuilder whereRaw(string $sql, array $bindings = [], string $boolean = 'and') + * @require-extends Illuminate\Database\Eloquent\Builder + * + * @method NodeQueryBuilder select(array $columns) + * @method Tmodel getModel() + * @method NodeQueryBuilder from(string $table) + * @method NodeQueryBuilder getQuery() + * @method NodeQueryBuilder whereRaw(string $sql, array $bindings = [], string $boolean = 'and') * @method \Illuminate\Database\Query\Grammars\Grammar getGrammar() - * @method NodeQueryBuilder whereNested(\Closure|string $callback, string $boolean = 'and') - * @method EloquentCollection get() + * @method NodeQueryBuilder whereNested(\Closure|string $callback, string $boolean = 'and') + * @method NestedSetCollection get(array $columns = ['*']) + * @method int max(string $column) + * @method NodeQueryBuilder where(string|array|\Closure $column, mixed $operator = null, mixed $value = null, string $boolean = 'and') + * @method NodeModel|null first(array|string $columns = ['*']) + * @method NodeModel findOrFail(array|string $columns = ['*']) + * @method NodeQueryBuilder skip(int $value) + * @method NodeQueryBuilder take(int $value) + * @method NodeQueryBuilder orderBy(string $column, string $direction = 'asc') + * @method NodeQueryBuilder when(bool $value, \Closure $callback) + * @method BaseQueryBuilder toBase() */ -interface NodeQueryBuilder +interface NodeQueryBuilder extends Builder { - /** + /** * Get node's `lft` and `rgt` values. * * @since 2.0 @@ -90,17 +99,17 @@ public function whereAncestorOrSelf(mixed $id): NodeQueryBuilder; * @param NodeModel $id * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function ancestorsOf(mixed $id, array $columns = ['*']): EloquentCollection; + public function ancestorsOf(mixed $id, array $columns = ['*']): NestedSetCollection; /** * @param NodeModel $id * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function ancestorsAndSelf(mixed $id, array $columns = ['*']): EloquentCollection; + public function ancestorsAndSelf(mixed $id, array $columns = ['*']): NestedSetCollection; /** * Add node selection statement between specified range. @@ -139,7 +148,7 @@ public function orWhereNodeBetween(array $values): NodeQueryBuilder; * @return NodeQueryBuilder */ public function whereDescendantOf(mixed $id, $boolean = 'and', $not = false, $andSelf = false): NodeQueryBuilder; - + /** * @param NodeModel $id * @@ -179,17 +188,17 @@ public function whereDescendantOrSelf(mixed $id, string $boolean = 'and', bool $ * @param string[] $columns * @param bool $andSelf * - * @return EloquentCollection|Collection + * @return NestedSetCollection */ - public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = false): EloquentCollection; + public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = false): NestedSetCollection; /** * @param NodeModel $id * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function descendantsAndSelf($id, array $columns = ['*']): EloquentCollection; + public function descendantsAndSelf($id, array $columns = ['*']): NestedSetCollection; /** * Constraint nodes to those that are after specified node. @@ -223,9 +232,9 @@ public function whereIsLeaf(): NodeQueryBuilder; /** * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function leaves(array $columns = ['*']): EloquentCollection; + public function leaves(array $columns = ['*']): NestedSetCollection; /** * Include depth level into the result. @@ -289,7 +298,6 @@ public function reversed(): NodeQueryBuilder; */ public function moveNode($key, $position): int; - /** * Make or remove gap in the tree. Negative height will remove gap. * diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 94a2119..72cac8a 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -3,16 +3,22 @@ namespace Kalnoy\Nestedset; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Database\Query\Builder as BaseQueryBuilder; use Illuminate\Support\Arr; +use Kalnoy\Nestedset\Contracts\NestedSetCollection; use Kalnoy\Nestedset\Contracts\Node; +use Kalnoy\Nestedset\Contracts\NodeQueryBuilder; /** * @template Tmodel extends Model - * + * @template Tmodelkey of array-key + * * @method void setRelation(string $relation, mixed $value) */ trait NodeTrait @@ -52,11 +58,11 @@ public static function bootNodeTrait(): void }); if (static::usesSoftDelete()) { - static::restoring(function ($model) { - static::$deletedAt = $model->{$model->getDeletedAtColumn()}; + static::restoring(function ($model) { /** @phpstan-ignore staticMethod.notFound */ + static::$deletedAt = $model->{$model->getDeletedAtColumn()}; /** @phpstan-ignore property.dynamicName */ }); - static::restored(function ($model) { + static::restored(function ($model) { /** @phpstan-ignore staticMethod.notFound */ $model->restoreDescendants(static::$deletedAt); }); } @@ -67,7 +73,7 @@ public static function bootNodeTrait(): void * * @param string $action * - * @return $this + * @return Node */ protected function setNodeAction($action): Node { @@ -83,11 +89,11 @@ protected function callPendingAction() { $this->moved = false; - if (!$this->pending && !$this->exists) { + if (($this->pending === null || $this->pending === []) && !$this->exists) { $this->makeRoot(); } - if (!$this->pending) { + if ($this->pending === null || $this->pending === []) { return; } @@ -107,9 +113,9 @@ public static function usesSoftDelete(): bool static $softDelete; if (is_null($softDelete)) { - $instance = new static(); + $instance = new self(); - return $softDelete = method_exists($instance, 'bootSoftDeletes'); + return $softDelete = method_exists($instance, 'bootSoftDeletes'); /** @phpstan-ignore function.alreadyNarrowedType, function.impossibleType */ } return $softDelete; @@ -146,7 +152,7 @@ protected function actionRoot() */ protected function getLowerBound(): int { - return (int) $this->newNestedSetQuery()->max($this->getRgtName()); + return (int) $this->newNestedSetQuery()->max($this->getRgtName()); /** @phpstan-ignore staticMethod.dynamicCall, cast.useless */ } /** @@ -181,7 +187,7 @@ protected function actionAppendOrPrepend(Node $parent, $prepend = false) */ protected function setParent($value) { - $this->setParentId($value ? $value->getKey() : null) + $this->setParentId($value !== null ? $value->getKey() : null) ->setRelation('parent', $value); return $this; @@ -224,7 +230,7 @@ public function refreshNode(): void */ public function parent(): BelongsTo { - return $this->belongsTo(get_class($this), $this->getParentIdName()) + return $this->belongsTo(get_class($this), $this->getParentIdName()) /** @phpstan-ignore-line staticMethod.dynamicCall */ ->setModel($this); } @@ -235,16 +241,16 @@ public function parent(): BelongsTo */ public function children(): HasMany { - return $this->hasMany(get_class($this), $this->getParentIdName()) + return $this->hasMany(get_class($this), $this->getParentIdName()) /** @phpstan-ignore-line staticMethod.dynamicCall */ ->setModel($this); } /** * Get query for descendants of the node. * - * @return DescendantsRelation + * @return Relation */ - public function descendants(): DescendantsRelation + public function descendants(): Relation { return new DescendantsRelation($this->newQuery(), $this); } @@ -252,9 +258,9 @@ public function descendants(): DescendantsRelation /** * Get query for siblings of the node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function siblings(): QueryBuilder + public function siblings(): NodeQueryBuilder { return $this->newScopedQuery() ->where($this->getKeyName(), '<>', $this->getKey()) @@ -264,9 +270,9 @@ public function siblings(): QueryBuilder /** * Get the node siblings and the node itself. * - * @return \Kalnoy\Nestedset\QueryBuilder + * @return NodeQueryBuilder */ - public function siblingsAndSelf(): QueryBuilder + public function siblingsAndSelf(): NodeQueryBuilder { return $this->newScopedQuery() ->where($this->getParentIdName(), '=', $this->getParentId()); @@ -277,9 +283,9 @@ public function siblingsAndSelf(): QueryBuilder * * @param array $columns * - * @return \Illuminate\Database\Eloquent\Collection + * @return NestedSetCollection */ - public function getSiblingsAndSelf(array $columns = ['*']): EloquentCollection + public function getSiblingsAndSelf(array $columns = ['*']): NestedSetCollection { return $this->siblingsAndSelf()->get($columns); } @@ -287,9 +293,9 @@ public function getSiblingsAndSelf(array $columns = ['*']): EloquentCollection /** * Get query for siblings after the node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function nextSiblings(): QueryBuilder + public function nextSiblings(): NodeQueryBuilder { return $this->nextNodes() ->where($this->getParentIdName(), '=', $this->getParentId()); @@ -298,9 +304,9 @@ public function nextSiblings(): QueryBuilder /** * Get query for siblings before the node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function prevSiblings(): QueryBuilder + public function prevSiblings(): NodeQueryBuilder { return $this->prevNodes() ->where($this->getParentIdName(), '=', $this->getParentId()); @@ -309,9 +315,9 @@ public function prevSiblings(): QueryBuilder /** * Get query for nodes after current node. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function nextNodes(): QueryBuilder + public function nextNodes(): NodeQueryBuilder { return $this->newScopedQuery() ->where($this->getLftName(), '>', $this->getLft()); @@ -320,20 +326,20 @@ public function nextNodes(): QueryBuilder /** * Get query for nodes before current node in reversed order. * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function prevNodes(): QueryBuilder + public function prevNodes(): NodeQueryBuilder { return $this->newScopedQuery() - ->where($this->getLftName(), '<', $this->getLft()); + ->where($this->getLftName(), '<', $this->getLft()); /** @phpstan-ignore method.notFound */ } /** * Get query ancestors of the node. * - * @return AncestorsRelation + * @return Relation */ - public function ancestors(): AncestorsRelation + public function ancestors(): Relation { return new AncestorsRelation($this->newQuery(), $this); } @@ -341,7 +347,7 @@ public function ancestors(): AncestorsRelation /** * Make this node a root node. * - * @return $this + * @return Node */ public function makeRoot(): Node { @@ -410,7 +416,7 @@ public function appendToNode(Node $parent): Node */ public function prependToNode(Node $parent): Node { - return $this->appendOrPrependTo($parent, true); + return $this->appendOrPrependTo($parent, true); /** @phpstan-ignore return.type */ } /** @@ -439,7 +445,7 @@ public function appendOrPrependTo(Node $parent, bool $prepend = false) */ public function afterNode(Node $node) { - return $this->beforeOrAfterNode($node, true); + return $this->beforeOrAfterNode($node, true); /** @phpstan-ignore return.type */ } /** @@ -451,7 +457,7 @@ public function afterNode(Node $node) */ public function beforeNode(Node $node) { - return $this->beforeOrAfterNode($node); + return $this->beforeOrAfterNode($node); /** @phpstan-ignore return.type */ } /** @@ -534,7 +540,7 @@ public function up(int $amount = 1): bool ->skip($amount - 1) ->first(); - if (!$sibling) { + if ($sibling === null) { return false; } @@ -555,7 +561,7 @@ public function down(int $amount = 1): bool ->skip($amount - 1) ->first(); - if (!$sibling) { + if ($sibling === null) { return false; } @@ -587,9 +593,9 @@ protected function insertAt($position) * * @param int $position * - * @return int + * @return bool */ - protected function moveNode(int $position) + protected function moveNode(int $position): bool { $updated = $this->newNestedSetQuery() ->moveNode($this->getKey(), $position) > 0; @@ -610,7 +616,7 @@ protected function moveNode(int $position) * * @return bool */ - protected function insertNode(int $position) + protected function insertNode(int $position): bool { $this->newNestedSetQuery()->makeGap($position, 2); @@ -630,7 +636,7 @@ protected function deleteDescendants() $lft = $this->getLft(); $rgt = $this->getRgt(); - $method = $this->usesSoftDelete() && $this->forceDeleting + $method = $this->usesSoftDelete() && $this->forceDeleting /** @phpstan-ignore property.notFound, staticMethod.dynamicCall */ ? 'forceDelete' : 'delete'; @@ -656,6 +662,7 @@ protected function deleteDescendants() // need for it. // The grammar compiler removes the superfluous "ORDER BY" for // PostgreSQL. + /** @phpstan-ignore method.dynamicName, staticMethod.dynamicCall */ $this->descendants() ->orderBy($this->getLftName(), 'desc') ->{$method}(); @@ -679,17 +686,19 @@ protected function deleteDescendants() */ protected function restoreDescendants($deletedAt) { - $this->descendants() + $this->descendants() /** @phpstan-ignore staticMethod.dynamicCall, method.notFound */ ->where($this->getDeletedAtColumn(), '>=', $deletedAt) ->restore(); } /** - * @param BaseQueryBuilder|EloquentBuilder|QueryBuilder $query + * @param BaseQueryBuilder|EloquentBuilder|QueryBuilder $query * - * @return QueryBuilder + * @return NodeQueryBuilder + * + * @phpstan-ignore generics.notSubtype */ - public function newEloquentBuilder($query): QueryBuilder + public function newEloquentBuilder($query): NodeQueryBuilder { /** @disregard P1006 */ return new QueryBuilder($query); @@ -700,12 +709,13 @@ public function newEloquentBuilder($query): QueryBuilder * * @since 1.1 * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function newNestedSetQuery($table = null): QueryBuilder + public function newNestedSetQuery($table = null): NodeQueryBuilder { + /** @phpstan-ignore staticMethod.dynamicCall */ $builder = $this->usesSoftDelete() - ? $this->withTrashed() + ? $this->withTrashed() /** @phpstan-ignore method.notFound, staticMethod.dynamicCall (it does exists!) */ : $this->newQuery(); return $this->applyNestedSetScope($builder, $table); @@ -714,9 +724,9 @@ public function newNestedSetQuery($table = null): QueryBuilder /** * @param string $table * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public function newScopedQuery($table = null): QueryBuilder + public function newScopedQuery($table = null): NodeQueryBuilder { return $this->applyNestedSetScope($this->newQuery(), $table); } @@ -729,11 +739,12 @@ public function newScopedQuery($table = null): QueryBuilder */ public function applyNestedSetScope($query, $table = null) { - if (!$scoped = $this->getScopeAttributes()) { + $scoped = $this->getScopeAttributes(); + if ($scoped === null || $scoped === []) { return $query; } - if (!$table) { + if ($table === null) { $table = $this->getTable(); } @@ -746,7 +757,7 @@ public function applyNestedSetScope($query, $table = null) } /** - * @return array + * @return array|null */ protected function getScopeAttributes() { @@ -756,11 +767,11 @@ protected function getScopeAttributes() /** * @param array $attributes * - * @return QueryBuilder + * @return NodeQueryBuilder */ - public static function scoped(array $attributes): QueryBuilder + public static function scoped(array $attributes): NodeQueryBuilder { - $instance = new static(); + $instance = new self(); $instance->setRawAttributes($attributes); @@ -770,7 +781,7 @@ public static function scoped(array $attributes): QueryBuilder /** * {@inheritdoc} */ - public function newCollection(array $models = []): Collection + public function newCollection(array $models = []): NestedSetCollection { return new Collection($models); } @@ -786,9 +797,9 @@ public static function create(array $attributes = [], ?Node $parent = null) { $children = Arr::pull($attributes, 'children'); - $instance = new static($attributes); + $instance = new self($attributes); - if ($parent) { + if ($parent !== null) { $instance->appendToNode($parent); } @@ -804,8 +815,9 @@ public static function create(array $attributes = [], ?Node $parent = null) } $instance->refreshNode(); + $instance->setRelation('children', $relation); - return $instance->setRelation('children', $relation); + return $instance; } /** @@ -825,7 +837,7 @@ public function getNodeHeight(): int */ public function getDescendantCount(): int { - return ceil($this->getNodeHeight() / 2) - 1; + return (int) ceil($this->getNodeHeight() / 2) - 1; } /** @@ -839,12 +851,11 @@ public function getDescendantCount(): int */ public function setParentIdAttribute(mixed $value): void { - if ($this->getParentId() == $value) { + if ($this->getParentId() === $value) { return; } - if ($value) { - /** @var Node&Tmodel */ + if ($value !== null && $value !== 0 && $value !== '') { $node = $this->newScopedQuery()->findOrFail($value); $this->appendToNode($node); } else { @@ -862,7 +873,7 @@ public function isRoot(): bool public function isLeaf(): bool { - return $this->getLft() + 1 == $this->getRgt(); + return $this->getLft() + 1 === $this->getRgt(); } /** @@ -946,27 +957,27 @@ public function getPrevNode(array $columns = ['*']): Node /** * @param string[] $columns * - * @return Collection + * @return NestedSetCollection */ public function getAncestors(array $columns = ['*']) { - return $this->ancestors()->get($columns); + return $this->ancestors()->get($columns); /** @phpstan-ignore return.type */ } /** * @param string[] $columns * - * @return Collection|Node[] + * @return NestedSetCollection */ public function getDescendants(array $columns = ['*']) { - return $this->descendants()->get($columns); + return $this->descendants()->get($columns); /** @phpstan-ignore return.type */ } /** * @param string[] $columns * - * @return Collection|Node[] + * @return NestedSetCollection */ public function getSiblings(array $columns = ['*']) { @@ -976,7 +987,7 @@ public function getSiblings(array $columns = ['*']) /** * @param string[] $columns * - * @return Collection|Node[] + * @return NestedSetCollection */ public function getNextSiblings(array $columns = ['*']) { @@ -986,7 +997,7 @@ public function getNextSiblings(array $columns = ['*']) /** * @param string[] $columns * - * @return Collection|Node[] + * @return NestedSetCollection */ public function getPrevSiblings(array $columns = ['*']) { @@ -1048,7 +1059,7 @@ public function isSelfOrDescendantOf(Node $other) */ public function isChildOf(Node $other) { - return $this->getParentId() == $other->getKey(); + return $this->getParentId() === $other->getKey(); } /** @@ -1060,7 +1071,7 @@ public function isChildOf(Node $other) */ public function isSiblingOf(Node $other) { - return $this->getParentId() == $other->getParentId(); + return $this->getParentId() === $other->getParentId(); } /** @@ -1117,7 +1128,7 @@ protected function getArrayableRelations() */ protected function hardDeleting() { - return !$this->usesSoftDelete() || $this->forceDeleting; + return !$this->usesSoftDelete() || $this->forceDeleting; /** @phpstan-ignore property.notFound, staticMethod.dynamicCall */ } /** @@ -1182,7 +1193,7 @@ protected function dirtyBounds() */ protected function assertNotDescendant(Node $node) { - if ($node == $this || $node->isDescendantOf($this)) { + if ($node === $this || $node->isDescendantOf($this)) { throw new \LogicException('Node must not be a descendant.'); } @@ -1196,7 +1207,7 @@ protected function assertNotDescendant(Node $node) */ protected function assertNodeExists(Node $node) { - if (!$node->getLft() || !$node->getRgt()) { + if ($node->getLft() === null || $node->getRgt() === null) { throw new \LogicException('Node must exists.'); } @@ -1204,16 +1215,19 @@ protected function assertNodeExists(Node $node) } /** - * @param Node&Tmodel $node + * @param Node&Tmodel $node + * + * @phpstan-ignore generics.notSubtype */ protected function assertSameScope(Node $node): void { - if (!$scoped = $this->getScopeAttributes()) { + $scoped = $this->getScopeAttributes(); + if ($scoped === null || $scoped === []) { return; } foreach ($scoped as $attr) { - if ($this->getAttribute($attr) != $node->getAttribute($attr)) { + if ($this->getAttribute($attr) !== $node->getAttribute($attr)) { throw new \LogicException('Nodes must be in the same scope'); } } @@ -1222,7 +1236,9 @@ protected function assertSameScope(Node $node): void /** * @param string[]|null $except * - * @return \Illuminate\Database\Eloquent\Model + * @return Node&Tmodel + * + * @phpstan-ignore generics.notSubtype */ public function replicate(?array $except = null): Node { @@ -1232,7 +1248,7 @@ public function replicate(?array $except = null): Node $this->getRgtName(), ]; - $except = $except ? array_unique(array_merge($except, $defaults)) : $defaults; + $except = ($except !== null && $except !== []) ? array_unique(array_merge($except, $defaults)) : $defaults; return parent::replicate($except); } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 39971a3..3337dc2 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -3,13 +3,13 @@ namespace Kalnoy\Nestedset; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Query\Builder as BaseQueryBuilder; use Illuminate\Database\Query\Builder as Query; use Illuminate\Database\Query\Expression; use Illuminate\Support\Arr; +use Kalnoy\Nestedset\Contracts\NestedSetCollection; use Kalnoy\Nestedset\Contracts\Node; use Kalnoy\Nestedset\Contracts\NodeQueryBuilder; use Kalnoy\Nestedset\Exceptions\NestedSetException; @@ -159,9 +159,9 @@ public function whereAncestorOrSelf(mixed $id): QueryBuilder * @param NodeModel $id * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function ancestorsOf(mixed $id, array $columns = ['*']): EloquentCollection + public function ancestorsOf(mixed $id, array $columns = ['*']): NestedSetCollection { return $this->whereAncestorOf($id)->get($columns); } @@ -170,9 +170,9 @@ public function ancestorsOf(mixed $id, array $columns = ['*']): EloquentCollecti * @param NodeModel $id * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function ancestorsAndSelf(mixed $id, array $columns = ['*']): EloquentCollection + public function ancestorsAndSelf(mixed $id, array $columns = ['*']): NestedSetCollection { return $this->whereAncestorOf($id, true)->get($columns); } @@ -290,9 +290,9 @@ public function whereDescendantOrSelf(mixed $id, string $boolean = 'and', bool $ * @param string[] $columns * @param bool $andSelf * - * @return EloquentCollection|Collection + * @return NestedSetCollection */ - public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = false): EloquentCollection + public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = false): NestedSetCollection { try { return $this->whereDescendantOf($id, 'and', false, $andSelf)->get($columns); @@ -305,9 +305,9 @@ public function descendantsOf(mixed $id, array $columns = ['*'], bool $andSelf = * @param NodeModel $id * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function descendantsAndSelf($id, array $columns = ['*']): EloquentCollection + public function descendantsAndSelf($id, array $columns = ['*']): NestedSetCollection { return $this->descendantsOf($id, $columns, true); } @@ -388,9 +388,9 @@ public function whereIsLeaf(): NodeQueryBuilder /** * @param string[] $columns * - * @return EloquentCollection + * @return NestedSetCollection */ - public function leaves(array $columns = ['*']): EloquentCollection + public function leaves(array $columns = ['*']): NestedSetCollection { return $this->whereIsLeaf()->get($columns); } @@ -990,7 +990,7 @@ public function rebuildTree(array $data, $delete = false, $root = null): int $this->buildRebuildDictionary($dictionary, $data, $existing, $parentId); - if ($existing !== null && $existing !== []) { + if ($existing !== []) { if ($delete && !$this->model->usesSoftDelete()) { $this->model ->newScopedQuery() diff --git a/tests/models/Category.php b/tests/models/Category.php index 0094208..6af035b 100644 --- a/tests/models/Category.php +++ b/tests/models/Category.php @@ -5,6 +5,9 @@ use Kalnoy\Nestedset\Contracts\Node; use Kalnoy\Nestedset\NodeTrait; +/** + * @implements Node + */ class Category extends Model implements Node { use SoftDeletes; diff --git a/tests/models/DuplicateCategory.php b/tests/models/DuplicateCategory.php index f23285d..ca2f674 100644 --- a/tests/models/DuplicateCategory.php +++ b/tests/models/DuplicateCategory.php @@ -4,6 +4,9 @@ use Kalnoy\Nestedset\Contracts\Node; use Kalnoy\Nestedset\NodeTrait; +/** + * @implements Node + */ class DuplicateCategory extends Model implements Node { use NodeTrait; diff --git a/tests/models/MenuItem.php b/tests/models/MenuItem.php index 1f4d1f4..bc90e5b 100644 --- a/tests/models/MenuItem.php +++ b/tests/models/MenuItem.php @@ -4,6 +4,9 @@ use Kalnoy\Nestedset\Contracts\Node; use Kalnoy\Nestedset\NodeTrait; +/** + * @implements Node + */ class MenuItem extends Model implements Node { use NodeTrait; From 766b99e278e100d41077cea37a04c777c766082a Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 26 Mar 2025 00:33:27 +0100 Subject: [PATCH 03/10] lvl 5 --- phpstan.neon | 2 +- src/Contracts/Node.php | 2 +- src/NodeTrait.php | 4 ++-- src/QueryBuilder.php | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 13e1811..c159d78 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,7 +4,7 @@ includes: - phpstan-baseline.neon parameters: - level: 4 + level: 5 paths: - src - tests/models/ diff --git a/src/Contracts/Node.php b/src/Contracts/Node.php index 6d450ae..d3c3e05 100644 --- a/src/Contracts/Node.php +++ b/src/Contracts/Node.php @@ -419,7 +419,7 @@ public function isAncestorOf(Node $other): bool; /** * Get whether a node is a descendant of other node. * - * @param NodeModel $other + * @param Node $other * * @return bool */ diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 72cac8a..4ac7aab 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -16,7 +16,7 @@ use Kalnoy\Nestedset\Contracts\NodeQueryBuilder; /** - * @template Tmodel extends Model + * @template Tmodel extends \Illuminate\Database\Eloquent\Model * @template Tmodelkey of array-key * * @method void setRelation(string $relation, mixed $value) @@ -181,7 +181,7 @@ protected function actionAppendOrPrepend(Node $parent, $prepend = false) /** * Apply parent model. * - * @param Model|null $value + * @param (Node&Tmodel)|null $value * * @return $this */ diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 3337dc2..3e75c8c 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -214,7 +214,7 @@ public function orWhereNodeBetween(array $values): NodeQueryBuilder * * @since 2.0 * - * @param ?NodeModel $id + * @param ?Node $id * @param string $boolean * @param bool $not * @param bool $andSelf @@ -250,7 +250,7 @@ public function whereNotDescendantOf(mixed $id): NodeQueryBuilder } /** - * @param NodeModel $id + * @param Node $id * * @return QueryBuilder */ @@ -260,7 +260,7 @@ public function orWhereDescendantOf(mixed $id): NodeQueryBuilder } /** - * @param NodeModel $id + * @param Node $id * * @return QueryBuilder */ @@ -270,7 +270,7 @@ public function orWhereNotDescendantOf(mixed $id): NodeQueryBuilder } /** - * @param NodeModel $id + * @param Node $id * @param string $boolean * @param bool $not * @@ -868,7 +868,7 @@ public function fixTree($root = null): int $dictionary = $this->model ->newNestedSetQuery() - ->when($root, function (self $query) use ($root) { + ->when($root !== null, function (self $query) use ($root) { return $query->whereDescendantOf($root); }) ->defaultOrder() @@ -876,7 +876,7 @@ public function fixTree($root = null): int ->groupBy($this->model->getParentIdName()) ->all(); - return $this->fixNodes($dictionary, $root); + return $this->fixNodes($dictionary, $root); /** @phpstan-ignore argument.type */ } /** From 4bfeaf59ea36112c4deeb767cde1b3021522effe Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 26 Mar 2025 00:42:56 +0100 Subject: [PATCH 04/10] some annotations --- src/Collection.php | 2 ++ src/Contracts/NestedSetCollection.php | 8 ++++---- src/Contracts/Node.php | 18 +++++++++--------- src/QueryBuilder.php | 12 ++++++------ tests/models/Category.php | 3 ++- tests/models/DuplicateCategory.php | 1 + tests/models/MenuItem.php | 8 ++++++-- 7 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index 6c12ec2..cd45945 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -13,6 +13,8 @@ * @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node&Model * * @extends EloquentCollection + * + * @implements NestedSetCollection */ final class Collection extends EloquentCollection implements NestedSetCollection { diff --git a/src/Contracts/NestedSetCollection.php b/src/Contracts/NestedSetCollection.php index c5c0549..e241719 100644 --- a/src/Contracts/NestedSetCollection.php +++ b/src/Contracts/NestedSetCollection.php @@ -5,12 +5,12 @@ /** * @template-covariant Tmodel of \Illuminate\Database\Eloquent\Model * - * @phpstan-type NodeModel Node + * @phpstan-type NodeModel Node&Tmodel * * @require-extends \Illuminate\Database\Eloquent\Collection * - * @method NestedSetCollection groupBy(string $column) - * @method array all() + * @method NestedSetCollection groupBy(string $column) + * @method array all() */ interface NestedSetCollection { @@ -19,7 +19,7 @@ interface NestedSetCollection * * This will overwrite any previously set relations. * - * @return $this + * @return NestedSetCollection */ public function linkNodes(); diff --git a/src/Contracts/Node.php b/src/Contracts/Node.php index d3c3e05..85e8209 100644 --- a/src/Contracts/Node.php +++ b/src/Contracts/Node.php @@ -27,15 +27,15 @@ * * @require-extends \Illuminate\Database\Eloquent\Model * - * @method mixed getKey() - * @method mixed getKeyName() - * @method Node setRelation($relation, $value) - * @method mixed save() - * @method string getTable() - * @method mixed getAttribute($key) - * @method string getDeletedAtColumn() - * @method Node getRelationValue($key) - * @method bool usesSoftDelete() + * @method mixed getKey() + * @method mixed getKeyName() + * @method Node setRelation($relation, $value) + * @method mixed save() + * @method string getTable() + * @method mixed getAttribute($key) + * @method string getDeletedAtColumn() + * @method Node getRelationValue($key) + * @method bool usesSoftDelete() */ interface Node { diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 3e75c8c..fc781c1 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -215,9 +215,9 @@ public function orWhereNodeBetween(array $values): NodeQueryBuilder * @since 2.0 * * @param ?Node $id - * @param string $boolean - * @param bool $not - * @param bool $andSelf + * @param string $boolean + * @param bool $not + * @param bool $andSelf * * @return QueryBuilder */ @@ -271,8 +271,8 @@ public function orWhereNotDescendantOf(mixed $id): NodeQueryBuilder /** * @param Node $id - * @param string $boolean - * @param bool $not + * @param string $boolean + * @param bool $not * * @return QueryBuilder */ @@ -868,7 +868,7 @@ public function fixTree($root = null): int $dictionary = $this->model ->newNestedSetQuery() - ->when($root !== null, function (self $query) use ($root) { + ->when($root !== null, function (self $query) use ($root) { return $query->whereDescendantOf($root); }) ->defaultOrder() diff --git a/tests/models/Category.php b/tests/models/Category.php index 6af035b..e3ce3a5 100644 --- a/tests/models/Category.php +++ b/tests/models/Category.php @@ -11,13 +11,14 @@ class Category extends Model implements Node { use SoftDeletes; + /** @use NodeTrait */ use NodeTrait; protected $fillable = ['name', 'parent_id']; public $timestamps = false; - public static function resetActionsPerformed() + public static function resetActionsPerformed(): void { static::$actionsPerformed = 0; } diff --git a/tests/models/DuplicateCategory.php b/tests/models/DuplicateCategory.php index ca2f674..6a64503 100644 --- a/tests/models/DuplicateCategory.php +++ b/tests/models/DuplicateCategory.php @@ -9,6 +9,7 @@ */ class DuplicateCategory extends Model implements Node { + /** @use NodeTrait */ use NodeTrait; protected $table = 'categories'; diff --git a/tests/models/MenuItem.php b/tests/models/MenuItem.php index bc90e5b..cadb9fa 100644 --- a/tests/models/MenuItem.php +++ b/tests/models/MenuItem.php @@ -9,18 +9,22 @@ */ class MenuItem extends Model implements Node { + /** @use NodeTrait */ use NodeTrait; public $timestamps = false; protected $fillable = ['menu_id', 'parent_id']; - public static function resetActionsPerformed() + public static function resetActionsPerformed(): void { static::$actionsPerformed = 0; } - protected function getScopeAttributes() + /** + * @return list + */ + protected function getScopeAttributes(): array { return ['menu_id']; } From 5e0a88f6943d7f2211dc0f13edab2cc484fd8262 Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 26 Mar 2025 07:41:23 +0100 Subject: [PATCH 05/10] fix yaml --- .github/workflows/CI.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 46f272b..ad1e490 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -19,9 +19,9 @@ jobs: if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1 - with: - access_token: ${{ github.token }} + uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1 + with: + access_token: ${{ github.token }} php_syntax_errors: name: 1️⃣ PHP - Syntax errors From b462f6d3dc93b102a6811bf9cf5309a252083d0d Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 26 Mar 2025 13:14:59 +0100 Subject: [PATCH 06/10] lvl 4 completed --- phpstan.neon | 2 +- src/Contracts/Node.php | 18 +++++++++--------- src/NodeTrait.php | 22 +++++++++++----------- src/QueryBuilder.php | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index c159d78..13e1811 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,7 +4,7 @@ includes: - phpstan-baseline.neon parameters: - level: 5 + level: 4 paths: - src - tests/models/ diff --git a/src/Contracts/Node.php b/src/Contracts/Node.php index 85e8209..86cb5d0 100644 --- a/src/Contracts/Node.php +++ b/src/Contracts/Node.php @@ -128,7 +128,7 @@ public function ancestors(): Relation; * * @return $this */ - public function makeRoot(): Node; + public function makeRoot(): self; /** * Save node as root. @@ -144,7 +144,7 @@ public function saveAsRoot(): bool; * * @return $this */ - public function rawNode(int $lft, int $rgt, mixed $parentId): Node; + public function rawNode(int $lft, int $rgt, mixed $parentId): self; /** * Move node up given amount of positions. @@ -338,14 +338,14 @@ public function getPrevSiblings(array $columns = ['*']); * * @return NodeModel */ - public function getNextSibling(array $columns = ['*']); + public function getNextSibling(array $columns = ['*']): Node; /** * @param string[] $columns * * @return NodeModel */ - public function getPrevSibling(array $columns = ['*']); + public function getPrevSibling(array $columns = ['*']): Node; /** * @return array @@ -357,21 +357,21 @@ public function getBounds(); * * @return NodeModel */ - public function setLft(int $value): Node; + public function setLft(int $value): self; /** * @param $value * * @return NodeModel */ - public function setRgt(int $value): Node; + public function setRgt(int $value): self; /** * @param array-key|null $id * * @return NodeModel */ - public function setParentId(mixed $id): Node; + public function setParentId(mixed $id): self; /** * @param string[]|null $except @@ -396,7 +396,7 @@ public function appendNode(Node $node): bool; * * @return NodeModel */ - public function appendToNode(Node $parent): Node; + public function appendToNode(Node $parent): self; /** * Prepend a node to the new parent. @@ -405,7 +405,7 @@ public function appendToNode(Node $parent): Node; * * @return NodeModel */ - public function prependToNode(Node $parent): Node; + public function prependToNode(Node $parent): self; /** * Get whether the node is an ancestor of other node, including immediate parent. diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 4ac7aab..711e603 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -73,9 +73,9 @@ public static function bootNodeTrait(): void * * @param string $action * - * @return Node + * @return self */ - protected function setNodeAction($action): Node + protected function setNodeAction($action): self { $this->pending = func_get_args(); @@ -400,9 +400,9 @@ public function prependNode(Node $node): bool * * @param Node&Tmodel $parent * - * @return Node&Tmodel + * @return self */ - public function appendToNode(Node $parent): Node + public function appendToNode(Node $parent): self { return $this->appendOrPrependTo($parent); } @@ -423,9 +423,9 @@ public function prependToNode(Node $parent): Node * @param Node $parent * @param bool $prepend * - * @return Node + * @return self */ - public function appendOrPrependTo(Node $parent, bool $prepend = false) + public function appendOrPrependTo(Node $parent, bool $prepend = false): self { $this->assertNodeExists($parent) ->assertNotDescendant($parent) @@ -740,7 +740,7 @@ public function newScopedQuery($table = null): NodeQueryBuilder public function applyNestedSetScope($query, $table = null) { $scoped = $this->getScopeAttributes(); - if ($scoped === null || $scoped === []) { + if ($scoped === null || $scoped === []) { /** @phpstan-ignore identical.alwaysFalse */ return $query; } @@ -855,7 +855,7 @@ public function setParentIdAttribute(mixed $value): void return; } - if ($value !== null && $value !== 0 && $value !== '') { + if ($value !== null) { $node = $this->newScopedQuery()->findOrFail($value); $this->appendToNode($node); } else { @@ -1009,7 +1009,7 @@ public function getPrevSiblings(array $columns = ['*']) * * @return Node */ - public function getNextSibling(array $columns = ['*']) + public function getNextSibling(array $columns = ['*']): Node { return $this->nextSiblings()->defaultOrder()->first($columns); } @@ -1019,7 +1019,7 @@ public function getNextSibling(array $columns = ['*']) * * @return Node */ - public function getPrevSibling(array $columns = ['*']) + public function getPrevSibling(array $columns = ['*']): Node { return $this->prevSiblings()->defaultOrder('desc')->first($columns); } @@ -1222,7 +1222,7 @@ protected function assertNodeExists(Node $node) protected function assertSameScope(Node $node): void { $scoped = $this->getScopeAttributes(); - if ($scoped === null || $scoped === []) { + if ($scoped === null || $scoped === []) { /** @phpstan-ignore identical.alwaysFalse */ return; } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index fc781c1..91897a8 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -876,7 +876,7 @@ public function fixTree($root = null): int ->groupBy($this->model->getParentIdName()) ->all(); - return $this->fixNodes($dictionary, $root); /** @phpstan-ignore argument.type */ + return $this->fixNodes($dictionary, $root); } /** From 9adb1dc27bc578f22e6682db0caf55e08d10a092 Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 26 Mar 2025 13:41:26 +0100 Subject: [PATCH 07/10] level 5 --- phpstan.neon | 2 +- src/Contracts/NodeQueryBuilder.php | 2 +- src/NodeTrait.php | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 13e1811..c159d78 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,7 +4,7 @@ includes: - phpstan-baseline.neon parameters: - level: 4 + level: 5 paths: - src - tests/models/ diff --git a/src/Contracts/NodeQueryBuilder.php b/src/Contracts/NodeQueryBuilder.php index c41328f..bc27143 100644 --- a/src/Contracts/NodeQueryBuilder.php +++ b/src/Contracts/NodeQueryBuilder.php @@ -23,7 +23,7 @@ * @method int max(string $column) * @method NodeQueryBuilder where(string|array|\Closure $column, mixed $operator = null, mixed $value = null, string $boolean = 'and') * @method NodeModel|null first(array|string $columns = ['*']) - * @method NodeModel findOrFail(array|string $columns = ['*']) + * @method NodeModel findOrFail(int|string $id) * @method NodeQueryBuilder skip(int $value) * @method NodeQueryBuilder take(int $value) * @method NodeQueryBuilder orderBy(string $column, string $direction = 'asc') diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 711e603..931608c 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -5,7 +5,6 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Collection as EloquentCollection; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\Relation; @@ -181,7 +180,7 @@ protected function actionAppendOrPrepend(Node $parent, $prepend = false) /** * Apply parent model. * - * @param (Node&Tmodel)|null $value + * @param Node|null $value * * @return $this */ @@ -373,7 +372,7 @@ public function saveAsRoot(): bool /** * Append and save a node. * - * @param Node&Tmodel $node + * @param Node $node * * @return bool */ @@ -398,7 +397,7 @@ public function prependNode(Node $node): bool /** * Append a node to the new parent. * - * @param Node&Tmodel $parent + * @param Node $parent * * @return self */ @@ -425,7 +424,7 @@ public function prependToNode(Node $parent): Node * * @return self */ - public function appendOrPrependTo(Node $parent, bool $prepend = false): self + protected function appendOrPrependTo(Node $parent, bool $prepend = false): self { $this->assertNodeExists($parent) ->assertNotDescendant($parent) @@ -461,7 +460,7 @@ public function beforeNode(Node $node) } /** - * @param Node&Tmodel $node + * @param Node $node * @param bool $after * * @return Node From 86ab6cfb296d444d009483e97f943da46ff63c72 Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 26 Mar 2025 13:43:33 +0100 Subject: [PATCH 08/10] formatting --- src/NodeTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 931608c..25033bd 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -461,7 +461,7 @@ public function beforeNode(Node $node) /** * @param Node $node - * @param bool $after + * @param bool $after * * @return Node */ From 5a2ae21761d848ed8d35602009697c4453d430fd Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 26 Mar 2025 13:55:28 +0100 Subject: [PATCH 09/10] formatting --- phpstan.neon | 2 +- src/Contracts/NodeQueryBuilder.php | 24 ++++++++++++------------ src/QueryBuilder.php | 5 +++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index c159d78..5470d0e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,7 +4,7 @@ includes: - phpstan-baseline.neon parameters: - level: 5 + level: 6 paths: - src - tests/models/ diff --git a/src/Contracts/NodeQueryBuilder.php b/src/Contracts/NodeQueryBuilder.php index bc27143..e5787ff 100644 --- a/src/Contracts/NodeQueryBuilder.php +++ b/src/Contracts/NodeQueryBuilder.php @@ -12,22 +12,22 @@ * * @require-extends Illuminate\Database\Eloquent\Builder * - * @method NodeQueryBuilder select(array $columns) + * @method NodeQueryBuilder select(string[] $columns) * @method Tmodel getModel() - * @method NodeQueryBuilder from(string $table) - * @method NodeQueryBuilder getQuery() - * @method NodeQueryBuilder whereRaw(string $sql, array $bindings = [], string $boolean = 'and') + * @method NodeQueryBuilder from(string $table) + * @method NodeQueryBuilder getQuery() + * @method NodeQueryBuilder whereRaw(string $sql, string[] $bindings = [], string $boolean = 'and') * @method \Illuminate\Database\Query\Grammars\Grammar getGrammar() - * @method NodeQueryBuilder whereNested(\Closure|string $callback, string $boolean = 'and') - * @method NestedSetCollection get(array $columns = ['*']) + * @method NodeQueryBuilder whereNested(\Closure|string $callback, string $boolean = 'and') + * @method NestedSetCollection get(string[] $columns = ['*']) * @method int max(string $column) - * @method NodeQueryBuilder where(string|array|\Closure $column, mixed $operator = null, mixed $value = null, string $boolean = 'and') - * @method NodeModel|null first(array|string $columns = ['*']) + * @method NodeQueryBuilder where(string|string[]|\Closure $column, mixed $operator = null, mixed $value = null, string $boolean = 'and') + * @method NodeModel|null first(string[]|string $columns = ['*']) * @method NodeModel findOrFail(int|string $id) - * @method NodeQueryBuilder skip(int $value) - * @method NodeQueryBuilder take(int $value) - * @method NodeQueryBuilder orderBy(string $column, string $direction = 'asc') - * @method NodeQueryBuilder when(bool $value, \Closure $callback) + * @method NodeQueryBuilder skip(int $value) + * @method NodeQueryBuilder take(int $value) + * @method NodeQueryBuilder orderBy(string $column, string $direction = 'asc') + * @method NodeQueryBuilder when(bool $value, \Closure $callback) * @method BaseQueryBuilder toBase() */ interface NodeQueryBuilder extends Builder diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 91897a8..134a441 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -20,6 +20,7 @@ * @phpstan-type NodeModel Node&Tmodel * * @extends Builder + * @implements NodeQueryBuilder */ class QueryBuilder extends Builder implements NodeQueryBuilder { @@ -617,7 +618,7 @@ public function makeGap($cut, $height): int * * @param array{height:int,cut?:int,distance?:int,lft?:int,rgt?:int,to?:int,from?:int} $params * - * @return array + * @return array> */ protected function patch(array $params): array { @@ -640,7 +641,7 @@ protected function patch(array $params): array * @param string $col * @param array{height:int,cut?:int,distance?:int,lft?:int,rgt?:int,to?:int,from?:int} $params * - * @return Expression + * @return Expression */ protected function columnPatch(string $col, array $params): Expression { From 609c1e45144b898cb9e27feaf11523b04e777735 Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 26 Mar 2025 14:33:15 +0100 Subject: [PATCH 10/10] level 6 --- phpstan.neon | 2 +- src/BaseRelation.php | 2 +- src/Contracts/Node.php | 5 +- src/NodeTrait.php | 150 +++++++++++++++++++++-------------------- src/QueryBuilder.php | 1 + 5 files changed, 82 insertions(+), 78 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 5470d0e..a0e5e0d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,4 +14,4 @@ parameters: - '#.*covariant.*#' - '#.*contravariant.*#' - - message: '#^Call to an undefined method Kalnoy\\Nestedset\\Contracts\\Node::assert.*\(\)\.$#' \ No newline at end of file + message: '#^Call to an undefined method Kalnoy\\Nestedset\\Contracts\\Node<.*>::assert.*\(\)\.$#' \ No newline at end of file diff --git a/src/BaseRelation.php b/src/BaseRelation.php index 91fe331..a59a708 100644 --- a/src/BaseRelation.php +++ b/src/BaseRelation.php @@ -16,7 +16,7 @@ * * @phpstan-type NodeModel Node&Tmodel * - * @extends Relation> + * @extends Relation> * * @property NodeModel $related * @property NodeModel $parent diff --git a/src/Contracts/Node.php b/src/Contracts/Node.php index 86cb5d0..5b7ddb0 100644 --- a/src/Contracts/Node.php +++ b/src/Contracts/Node.php @@ -3,7 +3,6 @@ namespace Kalnoy\Nestedset\Contracts; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; -use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\Relation; @@ -61,7 +60,7 @@ public function children(): HasMany; /** * Get query for descendants of the node. * - * @return Relation&Tmodel>> + * @return Relation> */ public function descendants(): Relation; @@ -119,7 +118,7 @@ public function prevNodes(): NodeQueryBuilder; /** * Get query ancestors of the node. * - * @return Relation&Tmodel>> + * @return Relation> */ public function ancestors(): Relation; diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 25033bd..bb18f04 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -24,6 +24,8 @@ trait NodeTrait { /** * Pending operation. + * + * @var array|null */ protected array|null $pending = null; @@ -84,7 +86,7 @@ protected function setNodeAction($action): self /** * Call pending action. */ - protected function callPendingAction() + protected function callPendingAction(): void { $this->moved = false; @@ -131,7 +133,7 @@ protected function actionRaw(): bool /** * Make a root node. */ - protected function actionRoot() + protected function actionRoot(): bool { // Simplest case that do not affect other nodes. if (!$this->exists) { @@ -157,12 +159,12 @@ protected function getLowerBound(): int /** * Append or prepend a node to the parent. * - * @param Node $parent - * @param bool $prepend + * @param Node $parent + * @param bool $prepend * * @return bool */ - protected function actionAppendOrPrepend(Node $parent, $prepend = false) + protected function actionAppendOrPrepend(Node $parent, $prepend = false): bool { $parent->refreshNode(); @@ -180,9 +182,9 @@ protected function actionAppendOrPrepend(Node $parent, $prepend = false) /** * Apply parent model. * - * @param Node|null $value + * @param Node|null $value * - * @return $this + * @return self */ protected function setParent($value) { @@ -195,8 +197,8 @@ protected function setParent($value) /** * Insert node before or after another node. * - * @param Node $node - * @param bool $after + * @param Node $node + * @param bool $after * * @return bool */ @@ -225,7 +227,7 @@ public function refreshNode(): void /** * Relation to the parent. * - * @return BelongsTo + * @return BelongsTo */ public function parent(): BelongsTo { @@ -236,7 +238,7 @@ public function parent(): BelongsTo /** * Relation to children. * - * @return HasMany + * @return HasMany */ public function children(): HasMany { @@ -247,7 +249,7 @@ public function children(): HasMany /** * Get query for descendants of the node. * - * @return Relation + * @return Relation> */ public function descendants(): Relation { @@ -257,7 +259,7 @@ public function descendants(): Relation /** * Get query for siblings of the node. * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public function siblings(): NodeQueryBuilder { @@ -269,7 +271,7 @@ public function siblings(): NodeQueryBuilder /** * Get the node siblings and the node itself. * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public function siblingsAndSelf(): NodeQueryBuilder { @@ -280,9 +282,9 @@ public function siblingsAndSelf(): NodeQueryBuilder /** * Get query for the node siblings and the node itself. * - * @param array $columns + * @param string[] $columns * - * @return NestedSetCollection + * @return NestedSetCollection */ public function getSiblingsAndSelf(array $columns = ['*']): NestedSetCollection { @@ -292,7 +294,7 @@ public function getSiblingsAndSelf(array $columns = ['*']): NestedSetCollection /** * Get query for siblings after the node. * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public function nextSiblings(): NodeQueryBuilder { @@ -303,7 +305,7 @@ public function nextSiblings(): NodeQueryBuilder /** * Get query for siblings before the node. * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public function prevSiblings(): NodeQueryBuilder { @@ -314,7 +316,7 @@ public function prevSiblings(): NodeQueryBuilder /** * Get query for nodes after current node. * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public function nextNodes(): NodeQueryBuilder { @@ -325,7 +327,7 @@ public function nextNodes(): NodeQueryBuilder /** * Get query for nodes before current node in reversed order. * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public function prevNodes(): NodeQueryBuilder { @@ -336,7 +338,7 @@ public function prevNodes(): NodeQueryBuilder /** * Get query ancestors of the node. * - * @return Relation + * @return Relation> */ public function ancestors(): Relation { @@ -346,7 +348,7 @@ public function ancestors(): Relation /** * Make this node a root node. * - * @return Node + * @return Node */ public function makeRoot(): Node { @@ -372,7 +374,7 @@ public function saveAsRoot(): bool /** * Append and save a node. * - * @param Node $node + * @param Node $node * * @return bool */ @@ -385,7 +387,7 @@ public function appendNode(Node $node): bool /** * Prepend and save a node. * - * @param Node $node + * @param Node $node * * @return bool */ @@ -397,7 +399,7 @@ public function prependNode(Node $node): bool /** * Append a node to the new parent. * - * @param Node $parent + * @param Node $parent * * @return self */ @@ -409,7 +411,7 @@ public function appendToNode(Node $parent): self /** * Prepend a node to the new parent. * - * @param Node $parent + * @param Node $parent * * @return $this */ @@ -419,8 +421,8 @@ public function prependToNode(Node $parent): Node } /** - * @param Node $parent - * @param bool $prepend + * @param Node $parent + * @param bool $prepend * * @return self */ @@ -438,7 +440,7 @@ protected function appendOrPrependTo(Node $parent, bool $prepend = false): self /** * Insert self after a node. * - * @param Node $node + * @param Node $node * * @return $this */ @@ -450,7 +452,7 @@ public function afterNode(Node $node) /** * Insert self before node. * - * @param Node $node + * @param Node $node * * @return $this */ @@ -460,10 +462,10 @@ public function beforeNode(Node $node) } /** - * @param Node $node - * @param bool $after + * @param Node $node + * @param bool $after * - * @return Node + * @return Node */ public function beforeOrAfterNode(Node $node, bool $after = false) { @@ -483,7 +485,7 @@ public function beforeOrAfterNode(Node $node, bool $after = false) /** * Insert self after a node and save. * - * @param Node $node + * @param Node $node * * @return bool */ @@ -495,7 +497,7 @@ public function insertAfterNode(Node $node) /** * Insert self before a node and save. * - * @param Node $node + * @param Node $node * * @return bool */ @@ -516,7 +518,7 @@ public function insertBeforeNode(Node $node) * @param int $rgt * @param Tmodelkey|null $parentId * - * @return Node + * @return Node */ public function rawNode(int $lft, int $rgt, mixed $parentId): Node { @@ -630,7 +632,7 @@ protected function insertNode(int $position): bool /** * Update the tree when the node is removed physically. */ - protected function deleteDescendants() + protected function deleteDescendants(): void { $lft = $this->getLft(); $rgt = $this->getRgt(); @@ -681,9 +683,9 @@ protected function deleteDescendants() /** * Restore the descendants. * - * @param $deletedAt + * @param Carbon $deletedAt */ - protected function restoreDescendants($deletedAt) + protected function restoreDescendants(Carbon $deletedAt): void { $this->descendants() /** @phpstan-ignore staticMethod.dynamicCall, method.notFound */ ->where($this->getDeletedAtColumn(), '>=', $deletedAt) @@ -693,13 +695,12 @@ protected function restoreDescendants($deletedAt) /** * @param BaseQueryBuilder|EloquentBuilder|QueryBuilder $query * - * @return NodeQueryBuilder - * - * @phpstan-ignore generics.notSubtype + * @return NodeQueryBuilder */ public function newEloquentBuilder($query): NodeQueryBuilder { /** @disregard P1006 */ + /** @var QueryBuilder */ return new QueryBuilder($query); } @@ -708,7 +709,7 @@ public function newEloquentBuilder($query): NodeQueryBuilder * * @since 1.1 * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public function newNestedSetQuery($table = null): NodeQueryBuilder { @@ -723,7 +724,7 @@ public function newNestedSetQuery($table = null): NodeQueryBuilder /** * @param string $table * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public function newScopedQuery($table = null): NodeQueryBuilder { @@ -756,7 +757,7 @@ public function applyNestedSetScope($query, $table = null) } /** - * @return array|null + * @return string[]|null */ protected function getScopeAttributes() { @@ -764,9 +765,9 @@ protected function getScopeAttributes() } /** - * @param array $attributes + * @param string[] $attributes * - * @return NodeQueryBuilder + * @return NodeQueryBuilder */ public static function scoped(array $attributes): NodeQueryBuilder { @@ -778,7 +779,7 @@ public static function scoped(array $attributes): NodeQueryBuilder } /** - * {@inheritdoc} + * @return NestedSetCollection */ public function newCollection(array $models = []): NestedSetCollection { @@ -790,7 +791,10 @@ public function newCollection(array $models = []): NestedSetCollection * * Use `children` key on `$attributes` to create child nodes. * - * @param Node $parent + * @param array $attributes + * @param Node $parent + * + * @return Node */ public static function create(array $attributes = [], ?Node $parent = null) { @@ -932,7 +936,7 @@ public function getParentId(): mixed * * @param string[] $columns * - * @return Node + * @return Node */ public function getNextNode(array $columns = ['*']): Node { @@ -946,7 +950,7 @@ public function getNextNode(array $columns = ['*']): Node * * @param string[] $columns * - * @return Node + * @return Node */ public function getPrevNode(array $columns = ['*']): Node { @@ -956,7 +960,7 @@ public function getPrevNode(array $columns = ['*']): Node /** * @param string[] $columns * - * @return NestedSetCollection + * @return NestedSetCollection */ public function getAncestors(array $columns = ['*']) { @@ -966,7 +970,7 @@ public function getAncestors(array $columns = ['*']) /** * @param string[] $columns * - * @return NestedSetCollection + * @return NestedSetCollection */ public function getDescendants(array $columns = ['*']) { @@ -976,7 +980,7 @@ public function getDescendants(array $columns = ['*']) /** * @param string[] $columns * - * @return NestedSetCollection + * @return NestedSetCollection */ public function getSiblings(array $columns = ['*']) { @@ -986,7 +990,7 @@ public function getSiblings(array $columns = ['*']) /** * @param string[] $columns * - * @return NestedSetCollection + * @return NestedSetCollection */ public function getNextSiblings(array $columns = ['*']) { @@ -996,7 +1000,7 @@ public function getNextSiblings(array $columns = ['*']) /** * @param string[] $columns * - * @return NestedSetCollection + * @return NestedSetCollection */ public function getPrevSiblings(array $columns = ['*']) { @@ -1006,7 +1010,7 @@ public function getPrevSiblings(array $columns = ['*']) /** * @param string[] $columns * - * @return Node + * @return Node */ public function getNextSibling(array $columns = ['*']): Node { @@ -1016,7 +1020,7 @@ public function getNextSibling(array $columns = ['*']): Node /** * @param string[] $columns * - * @return Node + * @return Node */ public function getPrevSibling(array $columns = ['*']): Node { @@ -1026,7 +1030,7 @@ public function getPrevSibling(array $columns = ['*']): Node /** * Get whether a node is a descendant of other node. * - * @param Node $other + * @param Node $other * * @return bool */ @@ -1039,7 +1043,7 @@ public function isDescendantOf(Node $other): bool /** * Get whether a node is itself or a descendant of other node. * - * @param Node $other + * @param Node $other * * @return bool */ @@ -1064,7 +1068,7 @@ public function isChildOf(Node $other) /** * Get whether the node is a sibling of another node. * - * @param Node $other + * @param Node $other * * @return bool */ @@ -1076,7 +1080,7 @@ public function isSiblingOf(Node $other) /** * Get whether the node is an ancestor of other node, including immediate parent. * - * @param Node $other + * @param Node $other * * @return bool */ @@ -1088,7 +1092,7 @@ public function isAncestorOf(Node $other): bool /** * Get whether the node is itself or an ancestor of other node, including immediate parent. * - * @param Node $other + * @param Node $other * * @return bool */ @@ -1108,7 +1112,7 @@ public function hasMoved() } /** - * @return array + * @return string[] */ protected function getArrayableRelations() { @@ -1131,7 +1135,7 @@ protected function hardDeleting() } /** - * @return array + * @return array{0:int,1:int} */ public function getBounds() { @@ -1141,7 +1145,7 @@ public function getBounds() /** * @param $value * - * @return Node $this + * @return Node $this */ public function setLft(int $value): Node { @@ -1153,7 +1157,7 @@ public function setLft(int $value): Node /** * @param $value * - * @return Node $this + * @return Node $this */ public function setRgt(int $value): Node { @@ -1165,7 +1169,7 @@ public function setRgt(int $value): Node /** * @param Tmodelkey|null $value * - * @return Node&Tmodel + * @return Node&Tmodel */ public function setParentId(mixed $value): Node { @@ -1175,7 +1179,7 @@ public function setParentId(mixed $value): Node } /** - * @return Node + * @return Node */ protected function dirtyBounds() { @@ -1186,9 +1190,9 @@ protected function dirtyBounds() } /** - * @param Node $node + * @param Node $node * - * @return Node + * @return Node */ protected function assertNotDescendant(Node $node) { @@ -1200,9 +1204,9 @@ protected function assertNotDescendant(Node $node) } /** - * @param Node $node + * @param Node $node * - * @return Node&Tmodel + * @return Node&Tmodel */ protected function assertNodeExists(Node $node) { diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 134a441..3e4cbd4 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -20,6 +20,7 @@ * @phpstan-type NodeModel Node&Tmodel * * @extends Builder + * * @implements NodeQueryBuilder */ class QueryBuilder extends Builder implements NodeQueryBuilder