diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 2e5a071faa2e..9e8a30c8de95 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -20,9 +20,12 @@ /** * @template TRelatedModel of \Illuminate\Database\Eloquent\Model * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model - * @template TPivotModel of \Illuminate\Database\Eloquent\Relations\Pivot + * @template TPivotModel of \Illuminate\Database\Eloquent\Relations\Pivot = \Illuminate\Database\Eloquent\Relations\Pivot + * @template TAccessor of string = 'pivot' * - * @extends \Illuminate\Database\Eloquent\Relations\Relation> + * @extends \Illuminate\Database\Eloquent\Relations\Relation> + * + * @todo use TAccessor when PHPStan bug is fixed: https://github.com/phpstan/phpstan/issues/12756 */ class BelongsToMany extends Relation { @@ -136,7 +139,7 @@ class BelongsToMany extends Relation /** * The name of the accessor to use for the "pivot" relationship. * - * @var string + * @var TAccessor */ protected $accessor = 'pivot'; @@ -327,8 +330,12 @@ public function getPivotClass() /** * Specify the custom pivot model to use for the relationship. * - * @param class-string $class + * @template TNewPivotModel of \Illuminate\Database\Eloquent\Relations\Pivot + * + * @param class-string $class * @return $this + * + * @phpstan-this-out static */ public function using($class) { @@ -340,8 +347,12 @@ public function using($class) /** * Specify the custom pivot accessor to use for the relationship. * - * @param string $accessor + * @template TNewAccessor of string + * + * @param TNewAccessor $accessor * @return $this + * + * @phpstan-this-out static */ public function as($accessor) { @@ -580,7 +591,11 @@ public function orderByPivot($column, $direction = 'asc') * * @param mixed $id * @param array $columns - * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel) + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : TRelatedModel&object{pivot: TPivotModel} + * ) */ public function findOrNew($id, $columns = ['*']) { @@ -596,7 +611,7 @@ public function findOrNew($id, $columns = ['*']) * * @param array $attributes * @param array $values - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function firstOrNew(array $attributes = [], array $values = []) { @@ -614,7 +629,7 @@ public function firstOrNew(array $attributes = [], array $values = []) * @param array $values * @param array $joining * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function firstOrCreate(array $attributes = [], array $values = [], array $joining = [], $touch = true) { @@ -640,7 +655,7 @@ public function firstOrCreate(array $attributes = [], array $values = [], array * @param array $values * @param array $joining * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true) { @@ -666,7 +681,7 @@ public function createOrFirst(array $attributes = [], array $values = [], array * @param array $values * @param array $joining * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true) { @@ -684,7 +699,11 @@ public function updateOrCreate(array $attributes, array $values = [], array $joi * * @param mixed $id * @param array $columns - * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel|null) + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : (TRelatedModel&object{pivot: TPivotModel})|null + * ) */ public function find($id, $columns = ['*']) { @@ -702,7 +721,7 @@ public function find($id, $columns = ['*']) * * @param mixed $id * @param array $columns - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException @@ -719,7 +738,7 @@ public function findSole($id, $columns = ['*']) * * @param \Illuminate\Contracts\Support\Arrayable|array $ids * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function findMany($ids, $columns = ['*']) { @@ -739,7 +758,11 @@ public function findMany($ids, $columns = ['*']) * * @param mixed $id * @param array $columns - * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel) + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : TRelatedModel&object{pivot: TPivotModel} + * ) * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ @@ -770,8 +793,8 @@ public function findOrFail($id, $columns = ['*']) * @param (\Closure(): TValue)|null $callback * @return ( * $id is (\Illuminate\Contracts\Support\Arrayable|array) - * ? \Illuminate\Database\Eloquent\Collection|TValue - * : TRelatedModel|TValue + * ? \Illuminate\Database\Eloquent\Collection|TValue + * : (TRelatedModel&object{pivot: TPivotModel})|TValue * ) */ public function findOr($id, $columns = ['*'], ?Closure $callback = null) @@ -804,7 +827,7 @@ public function findOr($id, $columns = ['*'], ?Closure $callback = null) * @param mixed $operator * @param mixed $value * @param string $boolean - * @return TRelatedModel|null + * @return (TRelatedModel&object{pivot: TPivotModel})|null */ public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') { @@ -815,7 +838,7 @@ public function firstWhere($column, $operator = null, $value = null, $boolean = * Execute the query and get the first result. * * @param array $columns - * @return TRelatedModel|null + * @return (TRelatedModel&object{pivot: TPivotModel})|null */ public function first($columns = ['*']) { @@ -828,7 +851,7 @@ public function first($columns = ['*']) * Execute the query and get the first result or throw an exception. * * @param array $columns - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ @@ -848,7 +871,7 @@ public function firstOrFail($columns = ['*']) * * @param (\Closure(): TValue)|list $columns * @param (\Closure(): TValue)|null $callback - * @return TRelatedModel|TValue + * @return (TRelatedModel&object{pivot: TPivotModel})|TValue */ public function firstOr($columns = ['*'], ?Closure $callback = null) { @@ -942,7 +965,7 @@ protected function aliasedPivotColumns() * @param array $columns * @param string $pageName * @param int|null $page - * @return \Illuminate\Pagination\LengthAwarePaginator + * @return \Illuminate\Pagination\LengthAwarePaginator */ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { @@ -960,7 +983,7 @@ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', * @param array $columns * @param string $pageName * @param int|null $page - * @return \Illuminate\Contracts\Pagination\Paginator + * @return \Illuminate\Contracts\Pagination\Paginator */ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { @@ -978,7 +1001,7 @@ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'p * @param array $columns * @param string $cursorName * @param string|null $cursor - * @return \Illuminate\Contracts\Pagination\CursorPaginator + * @return \Illuminate\Contracts\Pagination\CursorPaginator */ public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) { @@ -1100,7 +1123,7 @@ public function each(callable $callback, $count = 1000) * Query lazily, by chunks of the given size. * * @param int $chunkSize - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function lazy($chunkSize = 1000) { @@ -1117,7 +1140,7 @@ public function lazy($chunkSize = 1000) * @param int $chunkSize * @param string|null $column * @param string|null $alias - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function lazyById($chunkSize = 1000, $column = null, $alias = null) { @@ -1140,7 +1163,7 @@ public function lazyById($chunkSize = 1000, $column = null, $alias = null) * @param int $chunkSize * @param string|null $column * @param string|null $alias - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) { @@ -1160,7 +1183,7 @@ public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) /** * Get a lazy collection for the given query. * - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function cursor() { @@ -1300,7 +1323,7 @@ public function allRelatedIds() * @param TRelatedModel $model * @param array $pivotAttributes * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function save(Model $model, array $pivotAttributes = [], $touch = true) { @@ -1317,7 +1340,7 @@ public function save(Model $model, array $pivotAttributes = [], $touch = true) * @param TRelatedModel $model * @param array $pivotAttributes * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function saveQuietly(Model $model, array $pivotAttributes = [], $touch = true) { @@ -1368,7 +1391,7 @@ public function saveManyQuietly($models, array $pivotAttributes = []) * @param array $attributes * @param array $joining * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function create(array $attributes = [], array $joining = [], $touch = true) { @@ -1391,7 +1414,7 @@ public function create(array $attributes = [], array $joining = [], $touch = tru * * @param iterable $records * @param array $joinings - * @return array + * @return array */ public function createMany(iterable $records, array $joinings = []) { @@ -1625,7 +1648,7 @@ public function getRelationName() /** * Get the name of the pivot accessor for this relationship. * - * @return string + * @return TAccessor */ public function getPivotAccessor() { diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php index dd5deb2fc0de..adb985e5ae73 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php @@ -10,8 +10,10 @@ /** * @template TRelatedModel of \Illuminate\Database\Eloquent\Model * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * @template TPivotModel of \Illuminate\Database\Eloquent\Relations\Pivot = \Illuminate\Database\Eloquent\Relations\MorphPivot + * @template TAccessor of string = 'pivot' * - * @extends \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @extends \Illuminate\Database\Eloquent\Relations\BelongsToMany */ class MorphToMany extends BelongsToMany { @@ -122,7 +124,7 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, /** * Get the pivot models that are currently attached. * - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ protected function getCurrentlyAttachedPivots() { @@ -149,7 +151,7 @@ public function newPivotQuery() * * @param array $attributes * @param bool $exists - * @return \Illuminate\Database\Eloquent\Relations\Pivot + * @return TPivotModel */ public function newPivot(array $attributes = [], $exists = false) { diff --git a/types/Database/Eloquent/Relations.php b/types/Database/Eloquent/Relations.php index 416c64adbc53..a9d305707c43 100644 --- a/types/Database/Eloquent/Relations.php +++ b/types/Database/Eloquent/Relations.php @@ -41,43 +41,43 @@ function test(User $user, Post $post, Comment $comment, ChildUser $child): void assertType('Illuminate\Types\Relations\Post|false', $user->posts()->save(new Post())); assertType('Illuminate\Types\Relations\Post|false', $user->posts()->saveQuietly(new Post())); - assertType('Illuminate\Database\Eloquent\Relations\BelongsToMany', $user->roles()); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->getResults()); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->find([1])); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findMany([1, 2, 3])); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findOrNew([1])); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findOrFail([1])); - assertType('42|Illuminate\Database\Eloquent\Collection', $user->roles()->findOr([1], fn () => 42)); - assertType('42|Illuminate\Database\Eloquent\Collection', $user->roles()->findOr([1], callback: fn () => 42)); - assertType('Illuminate\Types\Relations\Role', $user->roles()->findOrNew(1)); - assertType('Illuminate\Types\Relations\Role', $user->roles()->findOrFail(1)); - assertType('Illuminate\Types\Relations\Role|null', $user->roles()->find(1)); - assertType('42|Illuminate\Types\Relations\Role', $user->roles()->findOr(1, fn () => 42)); - assertType('42|Illuminate\Types\Relations\Role', $user->roles()->findOr(1, callback: fn () => 42)); - assertType('Illuminate\Types\Relations\Role|null', $user->roles()->first()); - assertType('42|Illuminate\Types\Relations\Role', $user->roles()->firstOr(fn () => 42)); - assertType('42|Illuminate\Types\Relations\Role', $user->roles()->firstOr(callback: fn () => 42)); - assertType('Illuminate\Types\Relations\Role|null', $user->roles()->firstWhere('foo')); - assertType('Illuminate\Types\Relations\Role', $user->roles()->firstOrNew()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->firstOrFail()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->firstOrCreate()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->create()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->createOrFirst()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->updateOrCreate([])); - assertType('Illuminate\Types\Relations\Role', $user->roles()->save(new Role())); - assertType('Illuminate\Types\Relations\Role', $user->roles()->saveQuietly(new Role())); + assertType("Illuminate\Database\Eloquent\Relations\BelongsToMany", $user->roles()); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->getResults()); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->find([1])); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findMany([1, 2, 3])); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findOrNew([1])); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findOrFail([1])); + assertType('42|Illuminate\Database\Eloquent\Collection', $user->roles()->findOr([1], fn () => 42)); + assertType('42|Illuminate\Database\Eloquent\Collection', $user->roles()->findOr([1], callback: fn () => 42)); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->findOrNew(1)); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->findOrFail(1)); + assertType('(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})|null', $user->roles()->find(1)); + assertType('42|(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})', $user->roles()->findOr(1, fn () => 42)); + assertType('42|(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})', $user->roles()->findOr(1, callback: fn () => 42)); + assertType('(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})|null', $user->roles()->first()); + assertType('42|(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})', $user->roles()->firstOr(fn () => 42)); + assertType('42|(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})', $user->roles()->firstOr(callback: fn () => 42)); + assertType('(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})|null', $user->roles()->firstWhere('foo')); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->firstOrNew()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->firstOrFail()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->firstOrCreate()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->create()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->createOrFirst()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->updateOrCreate([])); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->save(new Role())); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->saveQuietly(new Role())); $roles = $user->roles()->getResults(); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->saveMany($roles)); - assertType('array', $user->roles()->saveMany($roles->all())); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->saveManyQuietly($roles)); - assertType('array', $user->roles()->saveManyQuietly($roles->all())); - assertType('array', $user->roles()->createMany($roles)); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->saveMany($roles)); + assertType('array', $user->roles()->saveMany($roles->all())); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->saveManyQuietly($roles)); + assertType('array', $user->roles()->saveManyQuietly($roles->all())); + assertType('array', $user->roles()->createMany($roles)); assertType('array{attached: array, detached: array, updated: array}', $user->roles()->sync($roles)); assertType('array{attached: array, detached: array, updated: array}', $user->roles()->syncWithoutDetaching($roles)); assertType('array{attached: array, detached: array, updated: array}', $user->roles()->syncWithPivotValues($roles, [])); - assertType('Illuminate\Support\LazyCollection', $user->roles()->lazy()); - assertType('Illuminate\Support\LazyCollection', $user->roles()->lazyById()); - assertType('Illuminate\Support\LazyCollection', $user->roles()->cursor()); + assertType('Illuminate\Support\LazyCollection', $user->roles()->lazy()); + assertType('Illuminate\Support\LazyCollection', $user->roles()->lazyById()); + assertType('Illuminate\Support\LazyCollection', $user->roles()->cursor()); assertType('Illuminate\Database\Eloquent\Relations\HasOneThrough', $user->car()); assertType('Illuminate\Types\Relations\Car|null', $user->car()->getResults()); @@ -122,8 +122,8 @@ function test(User $user, Post $post, Comment $comment, ChildUser $child): void assertType('Illuminate\Types\Relations\Comment', $comment->commentable()->associate(new Post())); assertType('Illuminate\Types\Relations\Comment', $comment->commentable()->dissociate()); - assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $post->tags()); - assertType('Illuminate\Database\Eloquent\Collection', $post->tags()->getResults()); + assertType("Illuminate\Database\Eloquent\Relations\MorphToMany", $post->tags()); + assertType('Illuminate\Database\Eloquent\Collection', $post->tags()->getResults()); assertType('42', Relation::noConstraints(fn () => 42)); } @@ -157,11 +157,11 @@ public function latestPost(): HasOne return $post; } - /** @return BelongsToMany */ + /** @return BelongsToMany */ public function roles(): BelongsToMany { $belongsToMany = $this->belongsToMany(Role::class); - assertType('Illuminate\Database\Eloquent\Relations\BelongsToMany', $belongsToMany); + assertType('Illuminate\Database\Eloquent\Relations\BelongsToMany', $belongsToMany); return $belongsToMany; } @@ -298,7 +298,7 @@ public function latestComment(): MorphOne public function tags(): MorphToMany { $morphToMany = $this->morphedByMany(Tag::class, 'taggable'); - assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); + assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); return $morphToMany; } @@ -322,7 +322,7 @@ class Tag extends Model public function posts(): MorphToMany { $morphToMany = $this->morphToMany(Post::class, 'taggable'); - assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); + assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); return $morphToMany; }