diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b01a5f0..c31efa082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/stubs/BelongsToMany.stub b/stubs/BelongsToMany.stub index c79d7831f..105dd8c0e 100644 --- a/stubs/BelongsToMany.stub +++ b/stubs/BelongsToMany.stub @@ -109,4 +109,37 @@ class BelongsToMany extends Relation * @phpstan-return \Traversable */ public function getResults(); + + /** + * Get a paginator for the "select" statement. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null); + + /** + * Paginate the given query into a simple paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null); + + /** + * Paginate the given query into a cursor paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $cursorName + * @param string|null $cursor + * @return \Illuminate\Pagination\CursorPaginator + */ + public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null); } diff --git a/stubs/Contracts/Pagination.stub b/stubs/Contracts/Pagination.stub index 674838285..b37af9925 100644 --- a/stubs/Contracts/Pagination.stub +++ b/stubs/Contracts/Pagination.stub @@ -3,22 +3,31 @@ namespace Illuminate\Contracts\Pagination; /** - * @mixin \Illuminate\Support\Collection - * @mixin \Illuminate\Pagination\Paginator + * @template TItem */ interface Paginator -{} +{ + /** + * @return array + */ + public function items(): array; +} /** - * @mixin \Illuminate\Support\Collection - * @mixin \Illuminate\Pagination\LengthAwarePaginator + * @template TItem + * + * @extends Paginator */ interface LengthAwarePaginator extends Paginator {} /** - * @mixin \Illuminate\Support\Collection - * @mixin \Illuminate\Pagination\CursorPaginator + * @template TItem */ -interface CursorPaginator extends Paginator -{} +interface CursorPaginator +{ + /** + * @return array + */ + public function items(): array; +} diff --git a/stubs/EloquentBuilder.stub b/stubs/EloquentBuilder.stub index 9046d52fb..e3890e58c 100644 --- a/stubs/EloquentBuilder.stub +++ b/stubs/EloquentBuilder.stub @@ -440,7 +440,7 @@ class Builder * @param array $columns * @param string $pageName * @param int|null $page - * @return \Illuminate\Pagination\LengthAwarePaginator + * @return \Illuminate\Pagination\LengthAwarePaginator * * @throws \InvalidArgumentException */ @@ -453,7 +453,7 @@ class Builder * @param array $columns * @param string $pageName * @param int|null $page - * @return \Illuminate\Pagination\Paginator + * @return \Illuminate\Pagination\Paginator */ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null); @@ -464,7 +464,7 @@ class Builder * @param array $columns * @param string $cursorName * @param \Illuminate\Pagination\Cursor|string|null $cursor - * @return \Illuminate\Pagination\CursorPaginator + * @return \Illuminate\Pagination\CursorPaginator */ public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null); diff --git a/stubs/HasManyThrough.stub b/stubs/HasManyThrough.stub index db0323e8a..c37805d44 100644 --- a/stubs/HasManyThrough.stub +++ b/stubs/HasManyThrough.stub @@ -14,4 +14,37 @@ class HasManyThrough extends Relation * @phpstan-return \Traversable */ public function getResults(); + + /** + * Get a paginator for the "select" statement. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null); + + /** + * Paginate the given query into a simple paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null); + + /** + * Paginate the given query into a cursor paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $cursorName + * @param string|null $cursor + * @return \Illuminate\Pagination\CursorPaginator + */ + public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null); } diff --git a/stubs/Pagination.stub b/stubs/Pagination.stub index 5a0cd1002..4b92dcfe7 100644 --- a/stubs/Pagination.stub +++ b/stubs/Pagination.stub @@ -3,33 +3,116 @@ namespace Illuminate\Pagination; /** - * @mixin \Illuminate\Support\Collection + * @template TValue + * + * @mixin \Illuminate\Support\Collection */ abstract class AbstractPaginator implements \Illuminate\Contracts\Support\Htmlable -{} +{ + /** + * @return array + */ + public function items(): array; + + /** + * @return \Illuminate\Support\Collection + */ + public function getCollection(): \Illuminate\Support\Collection; + + /** + * @return \ArrayIterator + */ + 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 - * @implements \IteratorAggregate - * @implements \Illuminate\Contracts\Support\Arrayable + * @template TValue + * + * @implements \ArrayAccess + * @implements \IteratorAggregate + * @implements \Illuminate\Contracts\Support\Arrayable + * @implements \Illuminate\Contracts\Pagination\Paginator + * + * @extends AbstractPaginator */ class Paginator extends AbstractPaginator implements \Illuminate\Contracts\Support\Arrayable, \ArrayAccess, \Countable, \IteratorAggregate, \Illuminate\Contracts\Support\Jsonable, \JsonSerializable, \Illuminate\Contracts\Pagination\Paginator {} /** - * @implements \ArrayAccess - * @implements \IteratorAggregate - * @implements \Illuminate\Contracts\Support\Arrayable + * @template TValue + * + * @implements \ArrayAccess + * @implements \IteratorAggregate + * @implements \Illuminate\Contracts\Support\Arrayable + * @implements \Illuminate\Contracts\Pagination\LengthAwarePaginator + * + * @extends AbstractPaginator */ class LengthAwarePaginator extends AbstractPaginator implements \Illuminate\Contracts\Support\Arrayable, \ArrayAccess, \Countable, \IteratorAggregate, \Illuminate\Contracts\Support\Jsonable, \JsonSerializable, \Illuminate\Contracts\Pagination\LengthAwarePaginator {} /** - * @implements \ArrayAccess - * @implements \IteratorAggregate - * @implements \Illuminate\Contracts\Support\Arrayable + * @template TValue + * + * @mixin \Illuminate\Support\Collection + */ +abstract class AbstractCursorPaginator implements \Illuminate\Contracts\Support\Htmlable +{ + /** + * @return array + */ + public function items(): array; + + /** + * @return \Illuminate\Support\Collection + */ + public function getCollection(): \Illuminate\Support\Collection; + + /** + * @return \ArrayIterator + */ + 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 + * @implements \IteratorAggregate + * @implements \Illuminate\Contracts\Support\Arrayable + * @implements \Illuminate\Contracts\Pagination\CursorPaginator + * + * @extends AbstractCursorPaginator */ -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 {} /** diff --git a/tests/Features/Methods/Builder.php b/tests/Features/Methods/Builder.php index 004e69fd2..5eece37f4 100644 --- a/tests/Features/Methods/Builder.php +++ b/tests/Features/Methods/Builder.php @@ -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; @@ -345,4 +346,20 @@ public function testQueryBuilderOnEloquentBuilderWithBaseModel(EloquentBuilder $ { assertType('Illuminate\Database\Eloquent\Builder', $query->select()); } + + /** + * @phpstan-return LengthAwarePaginator + */ + public function testPaginate() + { + return User::query()->paginate(); + } + + /** + * @phpstan-return array + */ + public function testPaginateItems() + { + return User::query()->paginate()->items(); + } } diff --git a/tests/Features/Models/Relations.php b/tests/Features/Models/Relations.php index ceea00d9b..b5c63cae6 100644 --- a/tests/Features/Models/Relations.php +++ b/tests/Features/Models/Relations.php @@ -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; @@ -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 @@ -86,6 +86,9 @@ public function testDecrementWithAmountOnRelation(User $user): int return $user->accounts()->decrement('id', 5); } + /** + * @return LengthAwarePaginator + */ public function testPaginate(User $user): LengthAwarePaginator { return $user->accounts()->paginate(5); diff --git a/tests/Type/data/paginator-extension.php b/tests/Type/data/paginator-extension.php index 86e45a776..56f6f31af 100644 --- a/tests/Type/data/paginator-extension.php +++ b/tests/Type/data/paginator-extension.php @@ -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', User::paginate()); +assertType('array', User::paginate()->all()); +assertType('array', User::paginate()->items()); +assertType('App\User|null', User::paginate()[0]); + +assertType('Illuminate\Pagination\Paginator', User::simplePaginate()); +assertType('array', User::simplePaginate()->all()); +assertType('array', User::simplePaginate()->items()); +assertType('App\User|null', User::simplePaginate()[0]); + +assertType('Illuminate\Pagination\CursorPaginator', User::cursorPaginate()); +assertType('array', User::cursorPaginate()->all()); +assertType('array', 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', (new User())->accounts()->paginate()); + +// BelongsToMany +assertType('Illuminate\Pagination\LengthAwarePaginator', (new User())->posts()->paginate());