Skip to content

Working torwards scricter types. #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
parameters:
ignoreErrors:

-
message: '#^Call to an undefined method Kalnoy\\Nestedset\\Contracts\\NodeQueryBuilder\<Tmodel of Illuminate\\Database\\Eloquent\\Model\>\:\:whereIn\(\)\.$#'
identifier: method.notFound
count: 1
path: src/QueryBuilder.php

-
message: '#^Dynamic call to static method Kalnoy\\Nestedset\\QueryBuilder\<Tmodel of Illuminate\\Database\\Eloquent\\Model\>\:\:whereRaw\(\)\.$#'
identifier: staticMethod.dynamicCall
count: 1
path: src/QueryBuilder.php

15 changes: 7 additions & 8 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
includes:
- vendor/larastan/larastan/extension.neon
- vendor/lychee-org/phpstan-lychee/phpstan.neon
- phpstan-baseline.neon

parameters:
level: 3
level: 6
paths:
- src
- tests/models/
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\(\).#'
- '#.*covariant.*#'
- '#.*contravariant.*#'
-
Comment on lines +14 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the covariant 💥

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol yeah.

message: '#^Call to an undefined method Kalnoy\\Nestedset\\Contracts\\Node<.*>::assert.*\(\)\.$#'
15 changes: 8 additions & 7 deletions src/AncestorsRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -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>&Tmodel
*
* @disregard P1037
* @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node<Tmodel>&Tmodel
*
* @extends BaseRelation<Tmodel>
*/
class AncestorsRelation extends BaseRelation
final class AncestorsRelation extends BaseRelation
{
/**
* Set the base constraints on the relation query.
Expand All @@ -36,14 +36,14 @@ 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<Tmodel> $query
* @param NodeModel $model
* @param NodeQueryBuilder<Tmodel> $query
* @param NodeModel $model
*
* @return void
*/
Expand All @@ -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";
Expand Down
34 changes: 19 additions & 15 deletions src/BaseRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@
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;

/**
* @template Tmodel of Model
*
* @phpstan-type NodeModel Node<Tmodel>&Tmodel
* @phpstan-type NodeModel Node<Tmodel>&Tmodel
*
* @extends Relation<NodeModel,NodeModel,EloquentCollection<int,NodeModel>>
* @extends Relation<NodeModel,NodeModel,NestedSetCollection<Tmodel>>
*
* @property NodeModel $related
* @property NodeModel $parent
*
* @method NodeModel getParent()
*/
abstract class BaseRelation extends Relation
{
/**
* @var QueryBuilder<Tmodel>
* @var NodeQueryBuilder<Tmodel>
*/
protected $query;

Expand All @@ -42,8 +43,8 @@ abstract class BaseRelation extends Relation
/**
* AncestorsRelation constructor.
*
* @param QueryBuilder<Tmodel> $builder
* @param NodeModel $model
* @param QueryBuilder<NodeModel> $builder
* @param NodeModel $model
*/
public function __construct(QueryBuilder $builder, Model $model)
{
Expand All @@ -60,11 +61,11 @@ 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<Tmodel> $query
* @param NodeModel $model
* @param NodeQueryBuilder<Tmodel> $query
* @param NodeModel $model
*
* @return void
*/
Expand All @@ -90,6 +91,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();
Expand All @@ -106,7 +108,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);
}

/**
Expand Down Expand Up @@ -137,11 +139,11 @@ public function getRelationCountHash($incrementJoinCount = true)
/**
* Get the results of the relationship.
*
* @return Collection<NodeModel>
* @return NestedSetCollection<NodeModel>
*/
public function getResults()
{
/** @var Collection<NodeModel> */
/** @disregard P1013 */
$result = $this->query->get();

return $result;
Expand All @@ -164,6 +166,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) {
Expand All @@ -187,6 +190,7 @@ public function match(array $models, EloquentCollection $results, $relation)
/** @disregard P1006 */
$related = $this->matchForModel($model, $results);

/** @disregard P1013 */
$model->setRelation($relation, $related);
}

Expand All @@ -197,16 +201,16 @@ public function match(array $models, EloquentCollection $results, $relation)
* @param NodeModel $model
* @param EloquentCollection<int,NodeModel> $results
*
* @return Collection<Tmodel>
* @return NestedSetCollection<Tmodel>
*/
protected function matchForModel(Model $model, EloquentCollection $results)
{
/** @var Collection<Tmodel> */
$result = $this->related->newCollection();

foreach ($results as $related) {
/** @disregard P1006 */
if ($this->matches($model, $related)) {
/** @disregard P1013 */
$result->push($related);
}
}
Expand Down
51 changes: 30 additions & 21 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@

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>&Tmodel
* @phpstan-type NodeModel \Kalnoy\Nestedset\Contracts\Node<Tmodel>&Model
*
* @extends EloquentCollection<array-key,NodeModel>
*
* @implements NestedSetCollection<Tmodel>
*/
final class Collection extends EloquentCollection
final class Collection extends EloquentCollection implements NestedSetCollection
{
/**
* Fill `parent` and `children` relationships for every node in the collection.
Expand All @@ -34,16 +38,20 @@ public function linkNodes()
/** @var NodeModel $node */
foreach ($this->items as $node) {
if ($node->getParentId() === null) {
/** @disregard */
$node->setRelation('parent', null);
}

/** @var array<int,NodeModel> */
$children = $groupedNodes->get($node->getKey(), []); /** @phpstan-ignore varTag.type */
$children = $groupedNodes->get($node->getKey(), []);

foreach ($children as $child) {
$child->setRelation('parent', $node);
if (count($children) > 0) {
foreach ($children as $child) {
/** @disregard */
$child->setRelation('parent', $node);
}
}

/** @disregard */
$node->setRelation('children', EloquentCollection::make($children));
}

Expand All @@ -59,9 +67,9 @@ public function linkNodes()
*
* @param mixed $root
*
* @return Collection<Tmodel>
* @return NestedSetCollection<Tmodel>
*/
public function toTree($root = false)
public function toTree($root = false): NestedSetCollection
{
if ($this->isEmpty()) {
return new static();
Expand Down Expand Up @@ -123,23 +131,22 @@ protected function getRootNodeId($root = false)
*
* @param bool $root
*
* @return Collection<Tmodel>
* @return NestedSetCollection<Tmodel>
*/
public function toFlatTree($root = false): Collection
public function toFlatTree($root = false): NestedSetCollection
{
/** @Var Collection<Tmodel> */
$result = new Collection();

if ($this->isEmpty()) {
return $result; /** @phpstan-ignore-line */
return $result;
}

/** @var NodeModel */
$first = $this->first();
/** @var Collection<NodeModel> */
$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));
}

/**
Expand All @@ -148,16 +155,18 @@ public function toFlatTree($root = false): Collection
* @param Collection<Tmodel> $groupedNodes
* @param array-key $parentId
*
* @return Collection<Tmodel>
* @return NestedSetCollection<Tmodel>
*/
protected function flattenTree(Collection $groupedNodes, $parentId): Collection
protected function flattenTree(Collection $groupedNodes, $parentId): NestedSetCollection
{
/** @var array<int,NodeModel> */
$nodes = $groupedNodes->get($parentId, []); /** @phpstan-ignore varTag.type */
foreach ($nodes as $node) {
$this->push($node);
$nodes = $groupedNodes->get($parentId, []);

$this->flattenTree($groupedNodes, $node->getKey());
if (count($nodes) > 0) {
foreach ($nodes as $node) {
$this->push($node);

$this->flattenTree($groupedNodes, $node->getKey());
}
}

return $this;
Expand Down
48 changes: 48 additions & 0 deletions src/Contracts/NestedSetCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Kalnoy\Nestedset\Contracts;

/**
* @template-covariant Tmodel of \Illuminate\Database\Eloquent\Model
*
* @phpstan-type NodeModel Node<Tmodel>&Tmodel
*
* @require-extends \Illuminate\Database\Eloquent\Collection
*
* @method NestedSetCollection<NodeModel> groupBy(string $column)
* @method array<int,NodeModel> all()
*/
interface NestedSetCollection
{
/**
* Fill `parent` and `children` relationships for every node in the collection.
*
* This will overwrite any previously set relations.
*
* @return NestedSetCollection<Tmodel>
*/
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 NestedSetCollection<Tmodel>
*/
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 NestedSetCollection<Tmodel>
*/
public function toFlatTree($root = false): NestedSetCollection;
}
Loading