Skip to content

Commit

Permalink
feat: add support for generic paginators (#1372)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgaal committed Nov 4, 2022
1 parent 112636d commit 42eb41b
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- fix: Eloquent builder `whereRelation()` losing the TModelClass generic type by @mad-briller.
- feat: updated return type of the Validator::safe and FormRequest::safe method by @jdjfisher
- feat: Added stub for the DB::transaction method by @jdjfisher
- feat: add support for generic paginators by @erikgaal

## [2.2.0] - 2022-08-31

Expand Down
33 changes: 33 additions & 0 deletions stubs/BelongsToMany.stub
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,37 @@ class BelongsToMany extends Relation
* @phpstan-return \Traversable<int, TRelatedModel>
*/
public function getResults();

/**
* Get a paginator for the "select" statement.
*
* @param int|null $perPage
* @param array<int, mixed> $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Pagination\LengthAwarePaginator<TRelatedModel>
*/
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);

/**
* Paginate the given query into a simple paginator.
*
* @param int|null $perPage
* @param array<int, mixed> $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Pagination\Paginator<TRelatedModel>
*/
public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);

/**
* Paginate the given query into a cursor paginator.
*
* @param int|null $perPage
* @param array<int, mixed> $columns
* @param string $cursorName
* @param string|null $cursor
* @return \Illuminate\Pagination\CursorPaginator<TRelatedModel>
*/
public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null);
}
27 changes: 18 additions & 9 deletions stubs/Contracts/Pagination.stub
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,31 @@
namespace Illuminate\Contracts\Pagination;

/**
* @mixin \Illuminate\Support\Collection
* @mixin \Illuminate\Pagination\Paginator
* @template TItem
*/
interface Paginator
{}
{
/**
* @return array<TItem>
*/
public function items(): array;
}

/**
* @mixin \Illuminate\Support\Collection
* @mixin \Illuminate\Pagination\LengthAwarePaginator
* @template TItem
*
* @extends Paginator<TItem>
*/
interface LengthAwarePaginator extends Paginator
{}

/**
* @mixin \Illuminate\Support\Collection
* @mixin \Illuminate\Pagination\CursorPaginator
* @template TItem
*/
interface CursorPaginator extends Paginator
{}
interface CursorPaginator
{
/**
* @return array<TItem>
*/
public function items(): array;
}
6 changes: 3 additions & 3 deletions stubs/EloquentBuilder.stub
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ class Builder
* @param array<array-key, mixed> $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Pagination\LengthAwarePaginator
* @return \Illuminate\Pagination\LengthAwarePaginator<TModelClass>
*
* @throws \InvalidArgumentException
*/
Expand All @@ -453,7 +453,7 @@ class Builder
* @param array<array-key, mixed> $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Pagination\Paginator
* @return \Illuminate\Pagination\Paginator<TModelClass>
*/
public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);

Expand All @@ -464,7 +464,7 @@ class Builder
* @param array<array-key, mixed> $columns
* @param string $cursorName
* @param \Illuminate\Pagination\Cursor|string|null $cursor
* @return \Illuminate\Pagination\CursorPaginator
* @return \Illuminate\Pagination\CursorPaginator<TModelClass>
*/
public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null);

Expand Down
33 changes: 33 additions & 0 deletions stubs/HasManyThrough.stub
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,37 @@ class HasManyThrough extends Relation
* @phpstan-return \Traversable<int, TRelatedModel>
*/
public function getResults();

/**
* Get a paginator for the "select" statement.
*
* @param int|null $perPage
* @param array<int, mixed> $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Pagination\LengthAwarePaginator<TRelatedModel>
*/
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);

/**
* Paginate the given query into a simple paginator.
*
* @param int|null $perPage
* @param array<int, mixed> $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Pagination\Paginator<TRelatedModel>
*/
public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);

/**
* Paginate the given query into a cursor paginator.
*
* @param int|null $perPage
* @param array<int, mixed> $columns
* @param string $cursorName
* @param string|null $cursor
* @return \Illuminate\Pagination\CursorPaginator<TRelatedModel>
*/
public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null);
}
107 changes: 95 additions & 12 deletions stubs/Pagination.stub
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,116 @@
namespace Illuminate\Pagination;

/**
* @mixin \Illuminate\Support\Collection
* @template TValue
*
* @mixin \Illuminate\Support\Collection<mixed, TValue>
*/
abstract class AbstractPaginator implements \Illuminate\Contracts\Support\Htmlable
{}
{
/**
* @return array<TValue>
*/
public function items(): array;

/**
* @return \Illuminate\Support\Collection<array-key, TValue>
*/
public function getCollection(): \Illuminate\Support\Collection;

/**
* @return \ArrayIterator<array-key, TValue>
*/
public function getIterator(): \Traversable;

public function offsetExists(mixed $offset): bool;

/**
* @return TValue|null
*/
public function offsetGet(mixed $offset): mixed;

/**
* @param TValue $value
*/
public function offsetSet(mixed $offset, $value): void;

public function offsetUnset(mixed $offset): void;
}

/**
* @implements \ArrayAccess<mixed, mixed>
* @implements \IteratorAggregate<mixed, mixed>
* @implements \Illuminate\Contracts\Support\Arrayable<array-key, mixed>
* @template TValue
*
* @implements \ArrayAccess<array-key, TValue>
* @implements \IteratorAggregate<array-key, TValue>
* @implements \Illuminate\Contracts\Support\Arrayable<array-key, TValue>
* @implements \Illuminate\Contracts\Pagination\Paginator<TValue>
*
* @extends AbstractPaginator<TValue>
*/
class Paginator extends AbstractPaginator implements \Illuminate\Contracts\Support\Arrayable, \ArrayAccess, \Countable, \IteratorAggregate, \Illuminate\Contracts\Support\Jsonable, \JsonSerializable, \Illuminate\Contracts\Pagination\Paginator
{}

/**
* @implements \ArrayAccess<mixed, mixed>
* @implements \IteratorAggregate<mixed, mixed>
* @implements \Illuminate\Contracts\Support\Arrayable<array-key, mixed>
* @template TValue
*
* @implements \ArrayAccess<array-key, TValue>
* @implements \IteratorAggregate<array-key, TValue>
* @implements \Illuminate\Contracts\Support\Arrayable<array-key, TValue>
* @implements \Illuminate\Contracts\Pagination\LengthAwarePaginator<TValue>
*
* @extends AbstractPaginator<TValue>
*/
class LengthAwarePaginator extends AbstractPaginator implements \Illuminate\Contracts\Support\Arrayable, \ArrayAccess, \Countable, \IteratorAggregate, \Illuminate\Contracts\Support\Jsonable, \JsonSerializable, \Illuminate\Contracts\Pagination\LengthAwarePaginator
{}

/**
* @implements \ArrayAccess<mixed, mixed>
* @implements \IteratorAggregate<mixed, mixed>
* @implements \Illuminate\Contracts\Support\Arrayable<array-key, mixed>
* @template TValue
*
* @mixin \Illuminate\Support\Collection<mixed, TValue>
*/
abstract class AbstractCursorPaginator implements \Illuminate\Contracts\Support\Htmlable
{
/**
* @return array<TValue>
*/
public function items(): array;

/**
* @return \Illuminate\Support\Collection<array-key, TValue>
*/
public function getCollection(): \Illuminate\Support\Collection;

/**
* @return \ArrayIterator<array-key, TValue>
*/
public function getIterator(): \Traversable;

public function offsetExists(mixed $offset): bool;

/**
* @return TValue|null
*/
public function offsetGet(mixed $offset): mixed;

/**
* @param TValue $value
*/
public function offsetSet(mixed $offset, $value): void;

public function offsetUnset(mixed $offset): void;
}

/**
* @template TValue
*
* @implements \ArrayAccess<array-key, TValue>
* @implements \IteratorAggregate<array-key, TValue>
* @implements \Illuminate\Contracts\Support\Arrayable<array-key, TValue>
* @implements \Illuminate\Contracts\Pagination\CursorPaginator<TValue>
*
* @extends AbstractCursorPaginator<TValue>
*/
class CursorPaginator extends AbstractPaginator implements \Illuminate\Contracts\Support\Arrayable, \ArrayAccess, \Countable, \IteratorAggregate, \Illuminate\Contracts\Support\Jsonable, \JsonSerializable, \Illuminate\Contracts\Pagination\CursorPaginator
class CursorPaginator extends AbstractCursorPaginator implements \Illuminate\Contracts\Support\Arrayable, \ArrayAccess, \Countable, \IteratorAggregate, \Illuminate\Contracts\Support\Jsonable, \JsonSerializable, \Illuminate\Contracts\Pagination\CursorPaginator
{}

/**
Expand Down
17 changes: 17 additions & 0 deletions tests/Features/Methods/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use function PHPStan\Testing\assertType;

Expand Down Expand Up @@ -345,4 +346,20 @@ public function testQueryBuilderOnEloquentBuilderWithBaseModel(EloquentBuilder $
{
assertType('Illuminate\Database\Eloquent\Builder<Illuminate\Database\Eloquent\Model>', $query->select());
}

/**
* @phpstan-return LengthAwarePaginator<User>
*/
public function testPaginate()
{
return User::query()->paginate();
}

/**
* @phpstan-return array<User>
*/
public function testPaginateItems()
{
return User::query()->paginate()->items();
}
}
5 changes: 4 additions & 1 deletion tests/Features/Models/Relations.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use App\Post;
use App\Role;
use App\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
Expand All @@ -19,6 +18,7 @@
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Pagination\LengthAwarePaginator;
use function PHPStan\Testing\assertType;

class Relations
Expand Down Expand Up @@ -86,6 +86,9 @@ public function testDecrementWithAmountOnRelation(User $user): int
return $user->accounts()->decrement('id', 5);
}

/**
* @return LengthAwarePaginator<Account>
*/
public function testPaginate(User $user): LengthAwarePaginator
{
return $user->accounts()->paginate(5);
Expand Down
25 changes: 22 additions & 3 deletions tests/Type/data/paginator-extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@
use App\User;
use function PHPStan\Testing\assertType;

assertType('array', User::paginate()->all());
assertType('array', User::simplePaginate()->all());
assertType('array', User::cursorPaginate()->all());
assertType('Illuminate\Pagination\LengthAwarePaginator<App\User>', User::paginate());
assertType('array<App\User>', User::paginate()->all());
assertType('array<App\User>', User::paginate()->items());
assertType('App\User|null', User::paginate()[0]);

assertType('Illuminate\Pagination\Paginator<App\User>', User::simplePaginate());
assertType('array<App\User>', User::simplePaginate()->all());
assertType('array<App\User>', User::simplePaginate()->items());
assertType('App\User|null', User::simplePaginate()[0]);

assertType('Illuminate\Pagination\CursorPaginator<App\User>', User::cursorPaginate());
assertType('array<App\User>', User::cursorPaginate()->all());
assertType('array<App\User>', User::cursorPaginate()->items());
assertType('App\User|null', User::cursorPaginate()[0]);

assertType('ArrayIterator<(int|string), App\User>', User::query()->paginate()->getIterator());

// HasMany
assertType('Illuminate\Pagination\LengthAwarePaginator<App\Account>', (new User())->accounts()->paginate());

// BelongsToMany
assertType('Illuminate\Pagination\LengthAwarePaginator<App\Post>', (new User())->posts()->paginate());

0 comments on commit 42eb41b

Please sign in to comment.