From 9bf31139027505be3f5cf28edb93812d639f1afa Mon Sep 17 00:00:00 2001 From: Caleb White Date: Sat, 15 Jun 2024 02:12:43 -0500 Subject: [PATCH 1/4] feat: add builder and relation generics --- composer.json | 2 +- src/Illuminate/Auth/EloquentUserProvider.php | 12 +- .../Database/Concerns/BuildsQueries.php | 12 +- src/Illuminate/Database/Eloquent/Builder.php | 77 +- .../Database/Eloquent/Collection.php | 26 +- .../Eloquent/Concerns/HasRelationships.php | 174 ++-- .../Database/Eloquent/Concerns/HasUlids.php | 2 +- .../Database/Eloquent/Concerns/HasUuids.php | 2 +- .../Concerns/QueriesRelationships.php | 108 +-- .../Eloquent/HigherOrderBuilderProxy.php | 4 +- .../Database/Eloquent/MassPrunable.php | 2 +- src/Illuminate/Database/Eloquent/Model.php | 49 +- .../PendingHasThroughRelationship.php | 26 +- src/Illuminate/Database/Eloquent/Prunable.php | 2 +- .../Database/Eloquent/Relations/BelongsTo.php | 81 +- .../Eloquent/Relations/BelongsToMany.php | 137 ++- .../Eloquent/Relations/Concerns/AsPivot.php | 14 +- .../Relations/Concerns/CanBeOneOfMany.php | 16 +- .../Database/Eloquent/Relations/HasMany.php | 31 +- .../Eloquent/Relations/HasManyThrough.php | 869 +----------------- .../Database/Eloquent/Relations/HasOne.php | 50 +- .../Eloquent/Relations/HasOneOrMany.php | 99 +- .../Relations/HasOneOrManyThrough.php | 837 +++++++++++++++++ .../Eloquent/Relations/HasOneThrough.php | 36 +- .../Database/Eloquent/Relations/MorphMany.php | 50 +- .../Database/Eloquent/Relations/MorphOne.php | 48 +- .../Eloquent/Relations/MorphOneOrMany.php | 31 +- .../Eloquent/Relations/MorphPivot.php | 12 +- .../Database/Eloquent/Relations/MorphTo.php | 61 +- .../Eloquent/Relations/MorphToMany.php | 28 +- .../Database/Eloquent/Relations/Relation.php | 67 +- .../Database/Eloquent/SoftDeletes.php | 8 +- .../Database/Eloquent/SoftDeletingScope.php | 22 +- src/Illuminate/Database/Query/Builder.php | 43 +- .../Notifications/DatabaseNotification.php | 10 +- .../HasDatabaseNotifications.php | 2 +- .../SerializesAndRestoresModelIdentifiers.php | 6 +- types/Database/Eloquent/Builder.php | 34 + types/Database/Eloquent/Model.php | 5 +- types/Database/Eloquent/Relations.php | 264 ++++++ 40 files changed, 1795 insertions(+), 1564 deletions(-) create mode 100644 src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php create mode 100644 types/Database/Eloquent/Builder.php create mode 100644 types/Database/Eloquent/Relations.php diff --git a/composer.json b/composer.json index ad1ddeff5906..8267ae535ea7 100644 --- a/composer.json +++ b/composer.json @@ -108,7 +108,7 @@ "nyholm/psr7": "^1.2", "orchestra/testbench-core": "^9.1.5", "pda/pheanstalk": "^5.0", - "phpstan/phpstan": "^1.4.7", + "phpstan/phpstan": "^1.11.5", "phpunit/phpunit": "^10.5|^11.0", "predis/predis": "^2.0.2", "resend/resend-php": "^0.10.0", diff --git a/src/Illuminate/Auth/EloquentUserProvider.php b/src/Illuminate/Auth/EloquentUserProvider.php index 646c2187f595..a1455abb64da 100755 --- a/src/Illuminate/Auth/EloquentUserProvider.php +++ b/src/Illuminate/Auth/EloquentUserProvider.php @@ -27,7 +27,7 @@ class EloquentUserProvider implements UserProvider /** * The callback that may modify the user retrieval queries. * - * @var (\Closure(\Illuminate\Database\Eloquent\Builder):mixed)|null + * @var (\Closure(\Illuminate\Database\Eloquent\Builder<*>):mixed)|null */ protected $queryCallback; @@ -177,8 +177,10 @@ public function rehashPasswordIfRequired(UserContract $user, array $credentials, /** * Get a new query builder for the model instance. * - * @param \Illuminate\Database\Eloquent\Model|null $model - * @return \Illuminate\Database\Eloquent\Builder + * @template TModel of \Illuminate\Database\Eloquent\Model + * + * @param TModel|null $model + * @return \Illuminate\Database\Eloquent\Builder */ protected function newModelQuery($model = null) { @@ -252,7 +254,7 @@ public function setModel($model) /** * Get the callback that modifies the query before retrieving users. * - * @return \Closure|null + * @param (\Closure(\Illuminate\Database\Eloquent\Builder<*>):mixed)|null */ public function getQueryCallback() { @@ -262,7 +264,7 @@ public function getQueryCallback() /** * Sets the callback to modify the query before retrieving users. * - * @param (\Closure(\Illuminate\Database\Eloquent\Builder):mixed)|null $queryCallback + * @param (\Closure(\Illuminate\Database\Eloquent\Builder<*>):mixed)|null $queryCallback * @return $this */ public function withQuery($queryCallback = null) diff --git a/src/Illuminate/Database/Concerns/BuildsQueries.php b/src/Illuminate/Database/Concerns/BuildsQueries.php index a2f43a097658..b37eb9c17004 100644 --- a/src/Illuminate/Database/Concerns/BuildsQueries.php +++ b/src/Illuminate/Database/Concerns/BuildsQueries.php @@ -18,6 +18,12 @@ use InvalidArgumentException; use RuntimeException; +/** + * @template TValue + * + * @mixin \Illuminate\Database\Eloquent\Builder + * @mixin \Illuminate\Database\Query\Builder + */ trait BuildsQueries { use Conditionable; @@ -328,7 +334,7 @@ protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = n * Execute the query and get the first result. * * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model|object|static|null + * @return TValue|null */ public function first($columns = ['*']) { @@ -339,7 +345,7 @@ public function first($columns = ['*']) * Execute the query and get the first result if it's the sole matching record. * * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model|object|static|null + * @return TValue * * @throws \Illuminate\Database\RecordsNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException @@ -463,7 +469,7 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = /** * Get the original column name of the given column, without any aliasing. * - * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*> $builder * @param string $parameter * @return string */ diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index d7799b1cae78..4f29ab1d4e2c 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -23,6 +23,8 @@ use ReflectionMethod; /** + * @template TModel of \Illuminate\Database\Eloquent\Model + * * @property-read HigherOrderBuilderProxy $orWhere * @property-read HigherOrderBuilderProxy $whereNot * @property-read HigherOrderBuilderProxy $orWhereNot @@ -31,6 +33,7 @@ */ class Builder implements BuilderContract { + /** @use \Illuminate\Database\Concerns\BuildsQueries */ use BuildsQueries, ForwardsCalls, QueriesRelationships { BuildsQueries::sole as baseSole; } @@ -45,7 +48,7 @@ class Builder implements BuilderContract /** * The model being queried. * - * @var \Illuminate\Database\Eloquent\Model + * @var TModel */ protected $model; @@ -160,7 +163,7 @@ public function __construct(QueryBuilder $query) * Create and return an un-saved model instance. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel */ public function make(array $attributes = []) { @@ -320,7 +323,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' * @param mixed $operator * @param mixed $value * @param string $boolean - * @return \Illuminate\Database\Eloquent\Model|static|null + * @return TModel|null */ public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') { @@ -409,7 +412,7 @@ public function oldest($column = null) * Create a collection of models from plain arrays. * * @param array $items - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function hydrate(array $items) { @@ -431,7 +434,7 @@ public function hydrate(array $items) * * @param string $query * @param array $bindings - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function fromQuery($query, $bindings = []) { @@ -445,7 +448,7 @@ public function fromQuery($query, $bindings = []) * * @param mixed $id * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null + * @return TModel|\Illuminate\Database\Eloquent\Collection|null */ public function find($id, $columns = ['*']) { @@ -461,7 +464,7 @@ public function find($id, $columns = ['*']) * * @param \Illuminate\Contracts\Support\Arrayable|array $ids * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function findMany($ids, $columns = ['*']) { @@ -479,9 +482,9 @@ public function findMany($ids, $columns = ['*']) * * @param mixed $id * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[] + * @return TModel|\Illuminate\Database\Eloquent\Collection * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function findOrFail($id, $columns = ['*']) { @@ -513,7 +516,7 @@ public function findOrFail($id, $columns = ['*']) * * @param mixed $id * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel */ public function findOrNew($id, $columns = ['*']) { @@ -530,7 +533,7 @@ public function findOrNew($id, $columns = ['*']) * @param mixed $id * @param \Closure|array|string $columns * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|mixed + * @return TModel|\Illuminate\Database\Eloquent\Collection|mixed */ public function findOr($id, $columns = ['*'], ?Closure $callback = null) { @@ -552,7 +555,7 @@ public function findOr($id, $columns = ['*'], ?Closure $callback = null) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel */ public function firstOrNew(array $attributes = [], array $values = []) { @@ -568,7 +571,7 @@ public function firstOrNew(array $attributes = [], array $values = []) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel */ public function firstOrCreate(array $attributes = [], array $values = []) { @@ -584,7 +587,7 @@ public function firstOrCreate(array $attributes = [], array $values = []) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel */ public function createOrFirst(array $attributes = [], array $values = []) { @@ -600,7 +603,7 @@ public function createOrFirst(array $attributes = [], array $values = []) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel */ public function updateOrCreate(array $attributes, array $values = []) { @@ -615,9 +618,9 @@ public function updateOrCreate(array $attributes, array $values = []) * Execute the query and get the first result or throw an exception. * * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function firstOrFail($columns = ['*']) { @@ -633,7 +636,7 @@ public function firstOrFail($columns = ['*']) * * @param \Closure|array|string $columns * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Model|static|mixed + * @return TModel|mixed */ public function firstOr($columns = ['*'], ?Closure $callback = null) { @@ -654,9 +657,9 @@ public function firstOr($columns = ['*'], ?Closure $callback = null) * Execute the query and get the first result if it's the sole matching record. * * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model + * @return TModel * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function sole($columns = ['*']) @@ -689,7 +692,7 @@ public function value($column) * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @return mixed * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function soleValue($column) @@ -705,7 +708,7 @@ public function soleValue($column) * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @return mixed * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function valueOrFail($column) { @@ -718,7 +721,7 @@ public function valueOrFail($column) * Execute the query as a "select" statement. * * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Collection|static[] + * @return \Illuminate\Database\Eloquent\Collection */ public function get($columns = ['*']) { @@ -740,7 +743,7 @@ public function get($columns = ['*']) * Get the hydrated models without eager loading. * * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model[]|static[] + * @return array */ public function getModels($columns = ['*']) { @@ -752,8 +755,8 @@ public function getModels($columns = ['*']) /** * Eager load the relationships for the models. * - * @param array $models - * @return array + * @param array $models + * @return array */ public function eagerLoadRelations(array $models) { @@ -801,7 +804,7 @@ protected function eagerLoadRelation(array $models, $name, Closure $constraints) * Get the relation instance for the given relation name. * * @param string $name - * @return \Illuminate\Database\Eloquent\Relations\Relation + * @return \Illuminate\Database\Eloquent\Relations\Relation<\Illuminate\Database\Eloquent\Model, TModel, *> */ public function getRelation($name) { @@ -893,7 +896,7 @@ public function applyAfterQueryCallbacks($result) /** * Get a lazy collection for the given query. * - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function cursor() { @@ -921,7 +924,7 @@ protected function enforceOrderBy() * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @param string|null $key - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function pluck($column, $key = null) { @@ -1060,7 +1063,7 @@ protected function ensureOrderForCursorPagination($shouldReverse = false) * Save a new model and return the instance. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model|$this + * @return TModel */ public function create(array $attributes = []) { @@ -1073,7 +1076,7 @@ public function create(array $attributes = []) * Save a new model and return the instance. Allow mass-assignment. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model|$this + * @return TModel */ public function forceCreate(array $attributes) { @@ -1086,7 +1089,7 @@ public function forceCreate(array $attributes) * Save a new model instance with mass assignment without raising model events. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model|$this + * @return TModel */ public function forceCreateQuietly(array $attributes = []) { @@ -1575,7 +1578,7 @@ public function withOnly($relations) * Create a new instance of the model being queried. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel */ public function newModelInstance($attributes = []) { @@ -1875,7 +1878,7 @@ protected function defaultKeyName() /** * Get the model instance being queried. * - * @return \Illuminate\Database\Eloquent\Model|static + * @return TModel */ public function getModel() { @@ -1885,8 +1888,10 @@ public function getModel() /** * Set a model instance for the model being queried. * - * @param \Illuminate\Database\Eloquent\Model $model - * @return $this + * @template TModelNew of \Illuminate\Database\Eloquent\Model + * + * @param TModelNew $model + * @return static */ public function setModel(Model $model) { diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index 28a90cd0a8fb..9640e41c540b 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -53,7 +53,7 @@ public function find($key, $default = null) /** * Load a set of relationships onto the collection. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function load($relations) @@ -74,7 +74,7 @@ public function load($relations) /** * Load a set of aggregations over relationship's column onto the collection. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @param string|null $function * @return $this @@ -111,7 +111,7 @@ public function loadAggregate($relations, $column, $function = null) /** * Load a set of relationship counts onto the collection. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadCount($relations) @@ -122,7 +122,7 @@ public function loadCount($relations) /** * Load a set of relationship's max column values onto the collection. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -134,7 +134,7 @@ public function loadMax($relations, $column) /** * Load a set of relationship's min column values onto the collection. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -146,7 +146,7 @@ public function loadMin($relations, $column) /** * Load a set of relationship's column summations onto the collection. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -158,7 +158,7 @@ public function loadSum($relations, $column) /** * Load a set of relationship's average column values onto the collection. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -170,7 +170,7 @@ public function loadAvg($relations, $column) /** * Load a set of related existences onto the collection. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadExists($relations) @@ -181,7 +181,7 @@ public function loadExists($relations) /** * Load a set of relationships onto the collection if they are not already eager loaded. * - * @param array|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadMissing($relations) @@ -220,7 +220,7 @@ public function loadMissing($relations) /** * Load a relationship path if it is not already eager loaded. * - * @param \Illuminate\Database\Eloquent\Collection $models + * @param \Illuminate\Database\Eloquent\Collection $models * @param array $path * @return void */ @@ -253,7 +253,7 @@ protected function loadMissingRelation(self $models, array $path) * Load a set of relationships onto the mixed relationship collection. * * @param string $relation - * @param array $relations + * @param array): mixed)|string> $relations * @return $this */ public function loadMorph($relation, $relations) @@ -270,7 +270,7 @@ public function loadMorph($relation, $relations) * Load a set of relationship counts onto the mixed relationship collection. * * @param string $relation - * @param array $relations + * @param array): mixed)|string> $relations * @return $this */ public function loadMorphCount($relation, $relations) @@ -766,7 +766,7 @@ public function getQueueableConnection() /** * Get the Eloquent query builder from the collection. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder * * @throws \LogicException */ diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php index a9f71e39ecf4..bab460f50054 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php @@ -93,10 +93,12 @@ public static function resolveRelationUsing($name, Closure $callback) /** * Define a one-to-one relationship. * - * @param string $related + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related * @param string|null $foreignKey * @param string|null $localKey - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function hasOne($related, $foreignKey = null, $localKey = null) { @@ -112,11 +114,14 @@ public function hasOne($related, $foreignKey = null, $localKey = null) /** * Instantiate a new HasOne relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $foreignKey * @param string $localKey - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey) { @@ -126,13 +131,16 @@ protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localK /** * Define a has-one-through relationship. * - * @param string $related - * @param string $through + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related + * @param class-string $through * @param string|null $firstKey * @param string|null $secondKey * @param string|null $localKey * @param string|null $secondLocalKey - * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough + * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough */ public function hasOneThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null) { @@ -152,14 +160,18 @@ public function hasOneThrough($related, $through, $firstKey = null, $secondKey = /** * Instantiate a new HasOneThrough relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $farParent - * @param \Illuminate\Database\Eloquent\Model $throughParent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $farParent + * @param TIntermediateModel $throughParent * @param string $firstKey * @param string $secondKey * @param string $localKey * @param string $secondLocalKey - * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough + * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough */ protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) { @@ -169,12 +181,14 @@ protected function newHasOneThrough(Builder $query, Model $farParent, Model $thr /** * Define a polymorphic one-to-one relationship. * - * @param string $related + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related * @param string $name * @param string|null $type * @param string|null $id * @param string|null $localKey - * @return \Illuminate\Database\Eloquent\Relations\MorphOne + * @return \Illuminate\Database\Eloquent\Relations\MorphOne */ public function morphOne($related, $name, $type = null, $id = null, $localKey = null) { @@ -192,12 +206,15 @@ public function morphOne($related, $name, $type = null, $id = null, $localKey = /** * Instantiate a new MorphOne relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $type * @param string $id * @param string $localKey - * @return \Illuminate\Database\Eloquent\Relations\MorphOne + * @return \Illuminate\Database\Eloquent\Relations\MorphOne */ protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey) { @@ -207,11 +224,13 @@ protected function newMorphOne(Builder $query, Model $parent, $type, $id, $local /** * Define an inverse one-to-one or many relationship. * - * @param string $related + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related * @param string|null $foreignKey * @param string|null $ownerKey * @param string|null $relation - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null) { @@ -244,12 +263,15 @@ public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relat /** * Instantiate a new BelongsTo relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $child + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $child * @param string $foreignKey * @param string $ownerKey * @param string $relation - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation) { @@ -263,7 +285,7 @@ protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $owne * @param string|null $type * @param string|null $id * @param string|null $ownerKey - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this> */ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null) { @@ -291,7 +313,7 @@ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null * @param string $type * @param string $id * @param string $ownerKey - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this> */ protected function morphEagerTo($name, $type, $id, $ownerKey) { @@ -308,7 +330,7 @@ protected function morphEagerTo($name, $type, $id, $ownerKey) * @param string $type * @param string $id * @param string $ownerKey - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this> */ protected function morphInstanceTo($target, $name, $type, $id, $ownerKey) { @@ -324,13 +346,16 @@ protected function morphInstanceTo($target, $name, $type, $id, $ownerKey) /** * Instantiate a new MorphTo relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $foreignKey * @param string $ownerKey * @param string $type * @param string $relation - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation) { @@ -363,8 +388,10 @@ protected function guessBelongsToRelation() /** * Create a pending has-many-through or has-one-through relationship. * - * @param string|\Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne $relationship - * @return \Illuminate\Database\Eloquent\PendingHasThroughRelationship + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * + * @param string|\Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne $relationship + * @return \Illuminate\Database\Eloquent\PendingHasThroughRelationship */ public function through($relationship) { @@ -378,10 +405,12 @@ public function through($relationship) /** * Define a one-to-many relationship. * - * @param string $related + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related * @param string|null $foreignKey * @param string|null $localKey - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function hasMany($related, $foreignKey = null, $localKey = null) { @@ -399,11 +428,14 @@ public function hasMany($related, $foreignKey = null, $localKey = null) /** * Instantiate a new HasMany relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $foreignKey * @param string $localKey - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey) { @@ -413,13 +445,16 @@ protected function newHasMany(Builder $query, Model $parent, $foreignKey, $local /** * Define a has-many-through relationship. * - * @param string $related - * @param string $through + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related + * @param class-string $through * @param string|null $firstKey * @param string|null $secondKey * @param string|null $localKey * @param string|null $secondLocalKey - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough */ public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null) { @@ -443,14 +478,18 @@ public function hasManyThrough($related, $through, $firstKey = null, $secondKey /** * Instantiate a new HasManyThrough relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $farParent - * @param \Illuminate\Database\Eloquent\Model $throughParent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $farParent + * @param TIntermediateModel $throughParent * @param string $firstKey * @param string $secondKey * @param string $localKey * @param string $secondLocalKey - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough */ protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) { @@ -460,12 +499,14 @@ protected function newHasManyThrough(Builder $query, Model $farParent, Model $th /** * Define a polymorphic one-to-many relationship. * - * @param string $related + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related * @param string $name * @param string|null $type * @param string|null $id * @param string|null $localKey - * @return \Illuminate\Database\Eloquent\Relations\MorphMany + * @return \Illuminate\Database\Eloquent\Relations\MorphMany */ public function morphMany($related, $name, $type = null, $id = null, $localKey = null) { @@ -486,12 +527,15 @@ public function morphMany($related, $name, $type = null, $id = null, $localKey = /** * Instantiate a new MorphMany relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $type * @param string $id * @param string $localKey - * @return \Illuminate\Database\Eloquent\Relations\MorphMany + * @return \Illuminate\Database\Eloquent\Relations\MorphMany */ protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey) { @@ -501,14 +545,16 @@ protected function newMorphMany(Builder $query, Model $parent, $type, $id, $loca /** * Define a many-to-many relationship. * - * @param string $related + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related * @param string|class-string<\Illuminate\Database\Eloquent\Model>|null $table * @param string|null $foreignPivotKey * @param string|null $relatedPivotKey * @param string|null $parentKey * @param string|null $relatedKey * @param string|null $relation - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null) @@ -546,15 +592,18 @@ public function belongsToMany($related, $table = null, $foreignPivotKey = null, /** * Instantiate a new BelongsToMany relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string|class-string<\Illuminate\Database\Eloquent\Model> $table * @param string $foreignPivotKey * @param string $relatedPivotKey * @param string $parentKey * @param string $relatedKey * @param string|null $relationName - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName = null) @@ -565,7 +614,9 @@ protected function newBelongsToMany(Builder $query, Model $parent, $table, $fore /** * Define a polymorphic many-to-many relationship. * - * @param string $related + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related * @param string $name * @param string|null $table * @param string|null $foreignPivotKey @@ -574,7 +625,7 @@ protected function newBelongsToMany(Builder $query, Model $parent, $table, $fore * @param string|null $relatedKey * @param string|null $relation * @param bool $inverse - * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function morphToMany($related, $name, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, @@ -612,8 +663,11 @@ public function morphToMany($related, $name, $table = null, $foreignPivotKey = n /** * Instantiate a new MorphToMany relationship. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $name * @param string $table * @param string $foreignPivotKey @@ -622,7 +676,7 @@ public function morphToMany($related, $name, $table = null, $foreignPivotKey = n * @param string $relatedKey * @param string|null $relationName * @param bool $inverse - * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, @@ -635,7 +689,9 @@ protected function newMorphToMany(Builder $query, Model $parent, $name, $table, /** * Define a polymorphic, inverse many-to-many relationship. * - * @param string $related + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param class-string $related * @param string $name * @param string|null $table * @param string|null $foreignPivotKey @@ -643,7 +699,7 @@ protected function newMorphToMany(Builder $query, Model $parent, $name, $table, * @param string|null $parentKey * @param string|null $relatedKey * @param string|null $relation - * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php b/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php index a4a47fbafabf..85a810db5ee1 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php @@ -40,7 +40,7 @@ public function newUniqueId() /** * Retrieve the model for a bound value. * - * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query + * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $query * @param mixed $value * @param string|null $field * @return \Illuminate\Contracts\Database\Eloquent\Builder diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php b/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php index be0512cc8072..55d1acfe770e 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php @@ -40,7 +40,7 @@ public function newUniqueId() /** * Retrieve the model for a bound value. * - * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query + * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $query * @param mixed $value * @param string|null $field * @return \Illuminate\Contracts\Database\Eloquent\Builder diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index 4322327cb472..49091f2b601c 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -15,17 +15,18 @@ use Illuminate\Support\Str; use InvalidArgumentException; +/** @mixin \Illuminate\Database\Eloquent\Builder */ trait QueriesRelationships { /** * Add a relationship count / exists condition to the query. * - * @param \Illuminate\Database\Eloquent\Relations\Relation|string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param string $operator * @param int $count * @param string $boolean * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this * * @throws \RuntimeException */ @@ -76,7 +77,7 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', ? * @param int $count * @param string $boolean * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) { @@ -107,7 +108,7 @@ protected function hasNested($relations, $operator = '>=', $count = 1, $boolean * @param string $relation * @param string $operator * @param int $count - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orHas($relation, $operator = '>=', $count = 1) { @@ -120,7 +121,7 @@ public function orHas($relation, $operator = '>=', $count = 1) * @param string $relation * @param string $boolean * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function doesntHave($relation, $boolean = 'and', ?Closure $callback = null) { @@ -131,7 +132,7 @@ public function doesntHave($relation, $boolean = 'and', ?Closure $callback = nul * Add a relationship count / exists condition to the query with an "or". * * @param string $relation - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orDoesntHave($relation) { @@ -145,7 +146,7 @@ public function orDoesntHave($relation) * @param \Closure|null $callback * @param string $operator * @param int $count - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function whereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1) { @@ -161,7 +162,7 @@ public function whereHas($relation, ?Closure $callback = null, $operator = '>=', * @param \Closure|null $callback * @param string $operator * @param int $count - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function withWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1) { @@ -176,7 +177,7 @@ public function withWhereHas($relation, ?Closure $callback = null, $operator = ' * @param \Closure|null $callback * @param string $operator * @param int $count - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1) { @@ -188,7 +189,7 @@ public function orWhereHas($relation, ?Closure $callback = null, $operator = '>= * * @param string $relation * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function whereDoesntHave($relation, ?Closure $callback = null) { @@ -200,7 +201,7 @@ public function whereDoesntHave($relation, ?Closure $callback = null) * * @param string $relation * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orWhereDoesntHave($relation, ?Closure $callback = null) { @@ -210,13 +211,13 @@ public function orWhereDoesntHave($relation, ?Closure $callback = null) /** * Add a polymorphic relationship count / exists condition to the query. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param string $operator * @param int $count * @param string $boolean * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null) { @@ -259,9 +260,12 @@ public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boole /** * Get the BelongsTo relationship for a single polymorphic type. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo $relation - * @param string $type - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, TDeclaringModel> $relation + * @param class-string $type + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ protected function getBelongsToRelation(MorphTo $relation, $type) { @@ -281,11 +285,11 @@ protected function getBelongsToRelation(MorphTo $relation, $type) /** * Add a polymorphic relationship count / exists condition to the query with an "or". * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param string $operator * @param int $count - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orHasMorph($relation, $types, $operator = '>=', $count = 1) { @@ -295,11 +299,11 @@ public function orHasMorph($relation, $types, $operator = '>=', $count = 1) /** * Add a polymorphic relationship count / exists condition to the query. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param string $boolean * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function doesntHaveMorph($relation, $types, $boolean = 'and', ?Closure $callback = null) { @@ -309,9 +313,9 @@ public function doesntHaveMorph($relation, $types, $boolean = 'and', ?Closure $c /** * Add a polymorphic relationship count / exists condition to the query with an "or". * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orDoesntHaveMorph($relation, $types) { @@ -321,12 +325,12 @@ public function orDoesntHaveMorph($relation, $types) /** * Add a polymorphic relationship count / exists condition to the query with where clauses. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param \Closure|null $callback * @param string $operator * @param int $count - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function whereHasMorph($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1) { @@ -336,12 +340,12 @@ public function whereHasMorph($relation, $types, ?Closure $callback = null, $ope /** * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or". * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param \Closure|null $callback * @param string $operator * @param int $count - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orWhereHasMorph($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1) { @@ -351,10 +355,10 @@ public function orWhereHasMorph($relation, $types, ?Closure $callback = null, $o /** * Add a polymorphic relationship count / exists condition to the query with where clauses. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function whereDoesntHaveMorph($relation, $types, ?Closure $callback = null) { @@ -364,10 +368,10 @@ public function whereDoesntHaveMorph($relation, $types, ?Closure $callback = nul /** * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or". * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orWhereDoesntHaveMorph($relation, $types, ?Closure $callback = null) { @@ -381,7 +385,7 @@ public function orWhereDoesntHaveMorph($relation, $types, ?Closure $callback = n * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function whereRelation($relation, $column, $operator = null, $value = null) { @@ -401,7 +405,7 @@ public function whereRelation($relation, $column, $operator = null, $value = nul * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orWhereRelation($relation, $column, $operator = null, $value = null) { @@ -417,12 +421,12 @@ public function orWhereRelation($relation, $column, $operator = null, $value = n /** * Add a polymorphic relationship condition to the query with a where clause. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function whereMorphRelation($relation, $types, $column, $operator = null, $value = null) { @@ -434,12 +438,12 @@ public function whereMorphRelation($relation, $types, $column, $operator = null, /** * Add a polymorphic relationship condition to the query with an "or where" clause. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param string|array $types * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orWhereMorphRelation($relation, $types, $column, $operator = null, $value = null) { @@ -451,9 +455,9 @@ public function orWhereMorphRelation($relation, $types, $column, $operator = nul /** * Add a morph-to relationship condition to the query. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param \Illuminate\Database\Eloquent\Model|string|null $model - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function whereMorphedTo($relation, $model, $boolean = 'and') { @@ -484,9 +488,9 @@ public function whereMorphedTo($relation, $model, $boolean = 'and') /** * Add a not morph-to relationship condition to the query. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param \Illuminate\Database\Eloquent\Model|string $model - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function whereNotMorphedTo($relation, $model, $boolean = 'and') { @@ -513,9 +517,9 @@ public function whereNotMorphedTo($relation, $model, $boolean = 'and') /** * Add a morph-to relationship condition to the query with an "or where" clause. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param \Illuminate\Database\Eloquent\Model|string|null $model - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orWhereMorphedTo($relation, $model) { @@ -525,9 +529,9 @@ public function orWhereMorphedTo($relation, $model) /** * Add a not morph-to relationship condition to the query with an "or where" clause. * - * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation * @param \Illuminate\Database\Eloquent\Model|string $model - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ public function orWhereNotMorphedTo($relation, $model) { @@ -537,7 +541,7 @@ public function orWhereNotMorphedTo($relation, $model) /** * Add a "belongs to" relationship where clause to the query. * - * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection<\Illuminate\Database\Eloquent\Model> $related + * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection $related * @param string|null $relationshipName * @param string $boolean * @return $this @@ -694,7 +698,7 @@ public function withAggregate($relations, $column, $function = null) * Get the relation hashed column name for the given column and relation. * * @param string $column - * @param \Illuminate\Database\Eloquent\Relations\Relation $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $relation * @return string */ protected function getRelationHashedColumn($column, $relation) @@ -781,12 +785,12 @@ public function withExists($relation) /** * Add the "has" condition where clause to the query. * - * @param \Illuminate\Database\Eloquent\Builder $hasQuery - * @param \Illuminate\Database\Eloquent\Relations\Relation $relation + * @param \Illuminate\Database\Eloquent\Builder<*> $hasQuery + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $relation * @param string $operator * @param int $count * @param string $boolean - * @return \Illuminate\Database\Eloquent\Builder|static + * @return $this */ protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean) { @@ -800,8 +804,8 @@ protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, /** * Merge the where constraints from another query to the current query. * - * @param \Illuminate\Database\Eloquent\Builder $from - * @return \Illuminate\Database\Eloquent\Builder|static + * @param \Illuminate\Database\Eloquent\Builder<*> $from + * @return $this */ public function mergeConstraintsFrom(Builder $from) { @@ -868,7 +872,7 @@ protected function addWhereCountQuery(QueryBuilder $query, $operator = '>=', $co * Get the "has relation" base query instance. * * @param string $relation - * @return \Illuminate\Database\Eloquent\Relations\Relation + * @return \Illuminate\Database\Eloquent\Relations\Relation<*, *, *> */ protected function getRelationWithoutConstraints($relation) { diff --git a/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php b/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php index 16b49a1b4d55..1c49ba28b7c4 100644 --- a/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php +++ b/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php @@ -10,7 +10,7 @@ class HigherOrderBuilderProxy /** * The collection being operated on. * - * @var \Illuminate\Database\Eloquent\Builder + * @var \Illuminate\Database\Eloquent\Builder<*> */ protected $builder; @@ -24,7 +24,7 @@ class HigherOrderBuilderProxy /** * Create a new proxy instance. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @param string $method * @return void */ diff --git a/src/Illuminate/Database/Eloquent/MassPrunable.php b/src/Illuminate/Database/Eloquent/MassPrunable.php index 254ca9bd29f0..e2321343e62a 100644 --- a/src/Illuminate/Database/Eloquent/MassPrunable.php +++ b/src/Illuminate/Database/Eloquent/MassPrunable.php @@ -39,7 +39,7 @@ public function pruneAll(int $chunkSize = 1000) /** * Get the prunable model query. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function prunable() { diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index c8360805d576..51c7f347422f 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -640,7 +640,7 @@ public function newFromBuilder($attributes = [], $connection = null) * Begin querying the model on a given connection. * * @param string|null $connection - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public static function on($connection = null) { @@ -657,7 +657,7 @@ public static function on($connection = null) /** * Begin querying the model on the write connection. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public static function onWriteConnection() { @@ -681,7 +681,7 @@ public static function all($columns = ['*']) * Begin querying a model with eager loading. * * @param array|string $relations - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public static function with($relations) { @@ -1190,7 +1190,7 @@ protected function finishSave(array $options) /** * Perform a model update operation. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @return bool */ protected function performUpdate(Builder $query) @@ -1228,8 +1228,8 @@ protected function performUpdate(Builder $query) /** * Set the keys for a select query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSelectQuery($query) { @@ -1251,8 +1251,8 @@ protected function getKeyForSelectQuery() /** * Set the keys for a save update query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSaveQuery($query) { @@ -1274,7 +1274,7 @@ protected function getKeyForSaveQuery() /** * Perform a model insert operation. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @return bool */ protected function performInsert(Builder $query) @@ -1329,7 +1329,7 @@ protected function performInsert(Builder $query) /** * Insert the given attributes and set the ID on the model. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @param array $attributes * @return void */ @@ -1472,7 +1472,7 @@ protected function performDeleteOnModel() /** * Begin querying the model. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public static function query() { @@ -1482,7 +1482,7 @@ public static function query() /** * Get a new query builder for the model's table. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function newQuery() { @@ -1492,7 +1492,7 @@ public function newQuery() /** * Get a new query builder that doesn't have any global scopes or eager loading. * - * @return \Illuminate\Database\Eloquent\Builder|static + * @return \Illuminate\Database\Eloquent\Builder */ public function newModelQuery() { @@ -1504,7 +1504,7 @@ public function newModelQuery() /** * Get a new query builder with no relationships loaded. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryWithoutRelationships() { @@ -1514,8 +1514,8 @@ public function newQueryWithoutRelationships() /** * Register the global scopes for this builder instance. * - * @param \Illuminate\Database\Eloquent\Builder $builder - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return \Illuminate\Database\Eloquent\Builder */ public function registerGlobalScopes($builder) { @@ -1529,7 +1529,7 @@ public function registerGlobalScopes($builder) /** * Get a new query builder that doesn't have any global scopes. * - * @return \Illuminate\Database\Eloquent\Builder|static + * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryWithoutScopes() { @@ -1542,7 +1542,7 @@ public function newQueryWithoutScopes() * Get a new query instance without a given scope. * * @param \Illuminate\Database\Eloquent\Scope|string $scope - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryWithoutScope($scope) { @@ -1553,7 +1553,7 @@ public function newQueryWithoutScope($scope) * Get a new query to restore one or more models by their queueable IDs. * * @param array|int $ids - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryForRestoration($ids) { @@ -1564,7 +1564,7 @@ public function newQueryForRestoration($ids) * Create a new Eloquent query builder for the model. * * @param \Illuminate\Database\Query\Builder $query - * @return \Illuminate\Database\Eloquent\Builder|static + * @return \Illuminate\Database\Eloquent\Builder */ public function newEloquentBuilder($query) { @@ -1584,8 +1584,11 @@ protected function newBaseQueryBuilder() /** * Create a new Eloquent Collection instance. * - * @param array $models - * @return \Illuminate\Database\Eloquent\Collection + * @template TKey of array-key + * @template TModel of \Illuminate\Database\Eloquent\Model + * + * @param array $models + * @return \Illuminate\Database\Eloquent\Collection */ public function newCollection(array $models = []) { @@ -2096,7 +2099,7 @@ public function resolveSoftDeletableChildRouteBinding($childType, $value, $field * @param string $childType * @param mixed $value * @param string|null $field - * @return \Illuminate\Database\Eloquent\Relations\Relation + * @return \Illuminate\Database\Eloquent\Relations\Relation<\Illuminate\Database\Eloquent\Model, $this, *> */ protected function resolveChildRouteBindingQuery($childType, $value, $field) { diff --git a/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php b/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php index 612c51e38863..a768afa44c1d 100644 --- a/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php +++ b/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php @@ -6,27 +6,31 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Str; +/** + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ class PendingHasThroughRelationship { /** * The root model that the relationship exists on. * - * @var \Illuminate\Database\Eloquent\Model + * @var TDeclaringModel */ protected $rootModel; /** * The local relationship. * - * @var \Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne + * @var \Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne */ protected $localRelationship; /** * Create a pending has-many-through or has-one-through relationship. * - * @param \Illuminate\Database\Eloquent\Model $rootModel - * @param \Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne $localRelationship + * @param TDeclaringModel $rootModel + * @param \Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne $localRelationship */ public function __construct($rootModel, $localRelationship) { @@ -38,8 +42,18 @@ public function __construct($rootModel, $localRelationship) /** * Define the distant relationship that this model has. * - * @param string|(callable(\Illuminate\Database\Eloquent\Model): (\Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Eloquent\Relations\HasMany)) $callback - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough|\Illuminate\Database\Eloquent\Relations\HasOneThrough + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * + * @param string|(callable(TIntermediateModel): (\Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Eloquent\Relations\HasMany)) $callback + * @return ( + * $callback is string + * ? \Illuminate\Database\Eloquent\Relations\HasManyThrough|\Illuminate\Database\Eloquent\Relations\HasOneThrough + * : ( + * $callback is callable(TIntermediateModel): \Illuminate\Database\Eloquent\Relations\HasOne + * ? \Illuminate\Database\Eloquent\Relations\HasOneThrough + * : \Illuminate\Database\Eloquent\Relations\HasManyThrough + * ) + * ) */ public function has($callback) { diff --git a/src/Illuminate/Database/Eloquent/Prunable.php b/src/Illuminate/Database/Eloquent/Prunable.php index b4ce1b03403a..be95b3b269cd 100644 --- a/src/Illuminate/Database/Eloquent/Prunable.php +++ b/src/Illuminate/Database/Eloquent/Prunable.php @@ -34,7 +34,7 @@ public function pruneAll(int $chunkSize = 1000) /** * Get the prunable model query. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function prunable() { diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php b/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php index 112a0edba022..b893e7d1b49a 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php @@ -10,6 +10,12 @@ use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\Relation + */ class BelongsTo extends Relation { use ComparesRelatedModels, @@ -19,7 +25,7 @@ class BelongsTo extends Relation /** * The child model instance of the relation. * - * @var \Illuminate\Database\Eloquent\Model + * @var TDeclaringModel */ protected $child; @@ -47,8 +53,8 @@ class BelongsTo extends Relation /** * Create a new belongs to relationship instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $child + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $child * @param string $foreignKey * @param string $ownerKey * @param string $relationName @@ -68,11 +74,7 @@ public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey parent::__construct($query, $child); } - /** - * Get the results of the relationship. - * - * @return mixed - */ + /** @inheritDoc */ public function getResults() { if (is_null($this->getForeignKeyFrom($this->child))) { @@ -99,12 +101,7 @@ public function addConstraints() } } - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ + /** @inheritDoc */ public function addEagerConstraints(array $models) { // We'll grab the primary key name of the related models since it could be set to @@ -120,7 +117,7 @@ public function addEagerConstraints(array $models) /** * Gather the keys from an array of related models. * - * @param array $models + * @param array $models * @return array */ protected function getEagerModelKeys(array $models) @@ -141,13 +138,7 @@ protected function getEagerModelKeys(array $models) return array_values(array_unique($keys)); } - /** - * Initialize the relation on a set of models. - * - * @param array $models - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function initRelation(array $models, $relation) { foreach ($models as $model) { @@ -157,14 +148,7 @@ public function initRelation(array $models, $relation) return $models; } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { // First we will get to build a dictionary of the child models by their primary @@ -195,8 +179,8 @@ public function match(array $models, Collection $results, $relation) /** * Associate the model instance to the given parent. * - * @param \Illuminate\Database\Eloquent\Model|int|string|null $model - * @return \Illuminate\Database\Eloquent\Model + * @param TRelatedModel|int|string|null $model + * @return TDeclaringModel */ public function associate($model) { @@ -216,7 +200,7 @@ public function associate($model) /** * Dissociate previously associated model from the given parent. * - * @return \Illuminate\Database\Eloquent\Model + * @return TDeclaringModel */ public function dissociate() { @@ -228,21 +212,14 @@ public function dissociate() /** * Alias of "dissociate" method. * - * @return \Illuminate\Database\Eloquent\Model + * @return TDeclaringModel */ public function disassociate() { return $this->dissociate(); } - /** - * Add the constraints for a relationship query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($parentQuery->getQuery()->from == $query->getQuery()->from) { @@ -257,10 +234,10 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, /** * Add the constraints for a relationship query on the same table. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -289,8 +266,8 @@ protected function relationHasIncrementingId() /** * Make a new related instance for the given model. * - * @param \Illuminate\Database\Eloquent\Model $parent - * @return \Illuminate\Database\Eloquent\Model + * @param TDeclaringModel $parent + * @return TRelatedModel */ protected function newRelatedInstanceFor(Model $parent) { @@ -300,7 +277,7 @@ protected function newRelatedInstanceFor(Model $parent) /** * Get the child of the relationship. * - * @return \Illuminate\Database\Eloquent\Model + * @return TDeclaringModel */ public function getChild() { @@ -358,10 +335,10 @@ public function getQualifiedOwnerKeyName() } /** - * Get the value of the model's associated key. + * Get the value of the model's foreign key. * - * @param \Illuminate\Database\Eloquent\Model $model - * @return mixed + * @param TRelatedModel $model + * @return int|string */ protected function getRelatedKeyFrom(Model $model) { @@ -371,7 +348,7 @@ protected function getRelatedKeyFrom(Model $model) /** * Get the value of the model's foreign key. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param TDeclaringModel $model * @return mixed */ protected function getForeignKeyFrom(Model $model) diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index ef17cce6371c..1593d84c0dc0 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -16,6 +16,12 @@ use Illuminate\Support\Str; use InvalidArgumentException; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\Relation> + */ class BelongsToMany extends Relation { use InteractsWithDictionary, InteractsWithPivotTable; @@ -135,9 +141,9 @@ class BelongsToMany extends Relation /** * Create a new belongs to many relationship instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent - * @param string|class-string<\Illuminate\Database\Eloquent\Model> $table + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent + * @param string|class-string $table * @param string $foreignPivotKey * @param string $relatedPivotKey * @param string $parentKey @@ -200,7 +206,7 @@ public function addConstraints() /** * Set the join clause for the relation query. * - * @param \Illuminate\Database\Eloquent\Builder|null $query + * @param \Illuminate\Database\Eloquent\Builder|null $query * @return $this */ protected function performJoin($query = null) @@ -234,12 +240,7 @@ protected function addWhereConstraints() return $this; } - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ + /** @inheritDoc */ public function addEagerConstraints(array $models) { $whereIn = $this->whereInMethod($this->parent, $this->parentKey); @@ -251,13 +252,7 @@ public function addEagerConstraints(array $models) ); } - /** - * Initialize the relation on a set of models. - * - * @param array $models - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function initRelation(array $models, $relation) { foreach ($models as $model) { @@ -267,14 +262,7 @@ public function initRelation(array $models, $relation) return $models; } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { $dictionary = $this->buildDictionary($results); @@ -298,8 +286,8 @@ public function match(array $models, Collection $results, $relation) /** * Build model dictionary keyed by the relation's foreign key. * - * @param \Illuminate\Database\Eloquent\Collection $results - * @return array + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array */ protected function buildDictionary(Collection $results) { @@ -583,7 +571,7 @@ public function orderByPivot($column, $direction = 'asc') * * @param mixed $id * @param array $columns - * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model + * @return \Illuminate\Support\Collection|TRelatedModel */ public function findOrNew($id, $columns = ['*']) { @@ -599,7 +587,7 @@ public function findOrNew($id, $columns = ['*']) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function firstOrNew(array $attributes = [], array $values = []) { @@ -617,7 +605,7 @@ public function firstOrNew(array $attributes = [], array $values = []) * @param array $values * @param array $joining * @param bool $touch - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function firstOrCreate(array $attributes = [], array $values = [], array $joining = [], $touch = true) { @@ -643,7 +631,7 @@ public function firstOrCreate(array $attributes = [], array $values = [], array * @param array $values * @param array $joining * @param bool $touch - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true) { @@ -669,7 +657,7 @@ public function createOrFirst(array $attributes = [], array $values = [], array * @param array $values * @param array $joining * @param bool $touch - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true) { @@ -687,7 +675,7 @@ public function updateOrCreate(array $attributes, array $values = [], array $joi * * @param mixed $id * @param array $columns - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null + * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection|null */ public function find($id, $columns = ['*']) { @@ -705,7 +693,7 @@ public function find($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 = ['*']) { @@ -725,9 +713,9 @@ public function findMany($ids, $columns = ['*']) * * @param mixed $id * @param array $columns - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection + * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function findOrFail($id, $columns = ['*']) { @@ -752,7 +740,7 @@ public function findOrFail($id, $columns = ['*']) * @param mixed $id * @param \Closure|array $columns * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed + * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection|mixed */ public function findOr($id, $columns = ['*'], ?Closure $callback = null) { @@ -784,7 +772,7 @@ public function findOr($id, $columns = ['*'], ?Closure $callback = null) * @param mixed $operator * @param mixed $value * @param string $boolean - * @return \Illuminate\Database\Eloquent\Model|static|null + * @return TRelatedModel|null */ public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') { @@ -795,7 +783,7 @@ public function firstWhere($column, $operator = null, $value = null, $boolean = * Execute the query and get the first result. * * @param array $columns - * @return \Illuminate\Database\Eloquent\Model|static|null + * @return TRelatedModel|null */ public function first($columns = ['*']) { @@ -808,9 +796,9 @@ public function first($columns = ['*']) * Execute the query and get the first result or throw an exception. * * @param array $columns - * @return \Illuminate\Database\Eloquent\Model|static + * @return TRelatedModel * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function firstOrFail($columns = ['*']) { @@ -826,7 +814,7 @@ public function firstOrFail($columns = ['*']) * * @param \Closure|array $columns * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Model|static|mixed + * @return TRelatedModel|mixed */ public function firstOr($columns = ['*'], ?Closure $callback = null) { @@ -843,11 +831,7 @@ public function firstOr($columns = ['*'], ?Closure $callback = null) return $callback(); } - /** - * Get the results of the relationship. - * - * @return mixed - */ + /** @inheritDoc */ public function getResults() { return ! is_null($this->parent->{$this->parentKey}) @@ -855,12 +839,7 @@ public function getResults() : $this->related->newCollection(); } - /** - * Execute the query as a "select" statement. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ + /** @inheritDoc */ public function get($columns = ['*']) { // First we'll add the proper select columns onto the query so it is run with @@ -1084,7 +1063,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) { @@ -1101,7 +1080,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) { @@ -1124,7 +1103,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) { @@ -1144,7 +1123,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() { @@ -1158,7 +1137,7 @@ public function cursor() /** * Prepare the query builder for query execution. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ protected function prepareQueryBuilder() { @@ -1168,7 +1147,7 @@ protected function prepareQueryBuilder() /** * Hydrate the pivot table relationship on the models. * - * @param array $models + * @param array $models * @return void */ protected function hydratePivotRelation(array $models) @@ -1186,7 +1165,7 @@ protected function hydratePivotRelation(array $models) /** * Get the pivot attributes from a model. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param TRelatedModel $model * @return array */ protected function migratePivotAttributes(Model $model) @@ -1271,7 +1250,7 @@ public function touch() /** * Get all of the IDs for the related models. * - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function allRelatedIds() { @@ -1281,10 +1260,10 @@ public function allRelatedIds() /** * Save a new model and attach it to the parent model. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param TRelatedModel $model * @param array $pivotAttributes * @param bool $touch - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function save(Model $model, array $pivotAttributes = [], $touch = true) { @@ -1298,10 +1277,10 @@ public function save(Model $model, array $pivotAttributes = [], $touch = true) /** * Save a new model without raising any events and attach it to the parent model. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param TRelatedModel $model * @param array $pivotAttributes * @param bool $touch - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function saveQuietly(Model $model, array $pivotAttributes = [], $touch = true) { @@ -1313,9 +1292,10 @@ public function saveQuietly(Model $model, array $pivotAttributes = [], $touch = /** * Save an array of new models and attach them to the parent model. * - * @param \Illuminate\Support\Collection|array $models + * @param \Illuminate\Support\Collection|array $models * @param array $pivotAttributes - * @return array + * @return array< + * @return \Illuminate\Support\Collection|array */ public function saveMany($models, array $pivotAttributes = []) { @@ -1331,9 +1311,9 @@ public function saveMany($models, array $pivotAttributes = []) /** * Save an array of new models without raising any events and attach them to the parent model. * - * @param \Illuminate\Support\Collection|array $models + * @param \Illuminate\Support\Collection|array $models * @param array $pivotAttributes - * @return array + * @return \Illuminate\Support\Collection|array */ public function saveManyQuietly($models, array $pivotAttributes = []) { @@ -1348,7 +1328,7 @@ public function saveManyQuietly($models, array $pivotAttributes = []) * @param array $attributes * @param array $joining * @param bool $touch - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function create(array $attributes = [], array $joining = [], $touch = true) { @@ -1369,7 +1349,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 = []) { @@ -1384,14 +1364,7 @@ public function createMany(iterable $records, array $joinings = []) return $instances; } - /** - * Add the constraints for a relationship query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($parentQuery->getQuery()->from == $query->getQuery()->from) { @@ -1406,10 +1379,10 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, /** * Add the constraints for a relationship query on the same table. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfJoin(Builder $query, Builder $parentQuery, $columns = ['*']) { diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php index e6f9f23015e1..ac2411f806c6 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php @@ -86,8 +86,8 @@ public static function fromRawAttributes(Model $parent, $attributes, $table, $ex /** * Set the keys for a select query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSelectQuery($query) { @@ -107,8 +107,8 @@ protected function setKeysForSelectQuery($query) /** * Set the keys for a save update query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSaveQuery($query) { @@ -142,7 +142,7 @@ public function delete() /** * Get the query builder for a delete operation on the pivot. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ protected function getDeleteQuery() { @@ -271,7 +271,7 @@ public function getQueueableId() * Get a new query to restore one or more models by their queueable IDs. * * @param int[]|string[]|string $ids - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryForRestoration($ids) { @@ -294,7 +294,7 @@ public function newQueryForRestoration($ids) * Get a new query to restore multiple models by their queueable IDs. * * @param int[]|string[] $ids - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ protected function newQueryForCollectionRestoration(array $ids) { diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/CanBeOneOfMany.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/CanBeOneOfMany.php index 7e3babe3fc52..c9a6f7612db1 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/CanBeOneOfMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/CanBeOneOfMany.php @@ -27,14 +27,14 @@ trait CanBeOneOfMany /** * The one of many inner join subselect query builder instance. * - * @var \Illuminate\Database\Eloquent\Builder|null + * @var \Illuminate\Database\Eloquent\Builder<*>|null */ protected $oneOfManySubQuery; /** * Add constraints for inner join subselect for one of many relationships. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder<*> $query * @param string|null $column * @param string|null $aggregate * @return void @@ -188,7 +188,7 @@ protected function getDefaultOneOfManyJoinAlias($relation) * @param string|array $groupBy * @param array|null $columns * @param string|null $aggregate - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder<*> */ protected function newOneOfManySubQuery($groupBy, $columns = null, $aggregate = null) { @@ -222,8 +222,8 @@ protected function newOneOfManySubQuery($groupBy, $columns = null, $aggregate = /** * Add the join subquery to the given query on the given column and the relationship's foreign key. * - * @param \Illuminate\Database\Eloquent\Builder $parent - * @param \Illuminate\Database\Eloquent\Builder $subQuery + * @param \Illuminate\Database\Eloquent\Builder<*> $parent + * @param \Illuminate\Database\Eloquent\Builder<*> $subQuery * @param array $on * @return void */ @@ -245,7 +245,7 @@ protected function addOneOfManyJoinSubQuery(Builder $parent, Builder $subQuery, /** * Merge the relationship query joins to the given query builder. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder<*> $query * @return void */ protected function mergeOneOfManyJoinsTo(Builder $query) @@ -258,7 +258,7 @@ protected function mergeOneOfManyJoinsTo(Builder $query) /** * Get the query builder that will contain the relationship constraints. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder<*> */ protected function getRelationQuery() { @@ -270,7 +270,7 @@ protected function getRelationQuery() /** * Get the one of many inner join subselect builder instance. * - * @return \Illuminate\Database\Eloquent\Builder|void + * @return \Illuminate\Database\Eloquent\Builder<*>|void */ public function getOneOfManySubQuery() { diff --git a/src/Illuminate/Database/Eloquent/Relations/HasMany.php b/src/Illuminate/Database/Eloquent/Relations/HasMany.php index 27bcd73e39b2..2a2a3e6a0e5a 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasMany.php @@ -4,12 +4,18 @@ use Illuminate\Database\Eloquent\Collection; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasOneOrMany> + */ class HasMany extends HasOneOrMany { /** * Convert the relationship to a "has one" relationship. * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function one() { @@ -21,11 +27,7 @@ public function one() )); } - /** - * Get the results of the relationship. - * - * @return mixed - */ + /** @inheritDoc */ public function getResults() { return ! is_null($this->getParentKey()) @@ -33,13 +35,7 @@ public function getResults() : $this->related->newCollection(); } - /** - * Initialize the relation on a set of models. - * - * @param array $models - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function initRelation(array $models, $relation) { foreach ($models as $model) { @@ -49,14 +45,7 @@ public function initRelation(array $models, $relation) return $models; } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { return $this->matchMany($models, $results, $relation); diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 2f3dc31fef60..5249ac33147e 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -2,91 +2,24 @@ namespace Illuminate\Database\Eloquent\Relations; -use Closure; -use Illuminate\Contracts\Support\Arrayable; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; -use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Database\Query\Grammars\MySqlGrammar; -use Illuminate\Database\UniqueConstraintViolationException; -class HasManyThrough extends Relation +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasOneOrManyThrough> + */ +class HasManyThrough extends HasOneOrManyThrough { use InteractsWithDictionary; - /** - * The "through" parent model instance. - * - * @var \Illuminate\Database\Eloquent\Model - */ - protected $throughParent; - - /** - * The far parent model instance. - * - * @var \Illuminate\Database\Eloquent\Model - */ - protected $farParent; - - /** - * The near key on the relationship. - * - * @var string - */ - protected $firstKey; - - /** - * The far key on the relationship. - * - * @var string - */ - protected $secondKey; - - /** - * The local key on the relationship. - * - * @var string - */ - protected $localKey; - - /** - * The local key on the intermediary model. - * - * @var string - */ - protected $secondLocalKey; - - /** - * Create a new has many through relationship instance. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $farParent - * @param \Illuminate\Database\Eloquent\Model $throughParent - * @param string $firstKey - * @param string $secondKey - * @param string $localKey - * @param string $secondLocalKey - * @return void - */ - public function __construct(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) - { - $this->localKey = $localKey; - $this->firstKey = $firstKey; - $this->secondKey = $secondKey; - $this->farParent = $farParent; - $this->throughParent = $throughParent; - $this->secondLocalKey = $secondLocalKey; - - parent::__construct($query, $throughParent); - } - /** * Convert the relationship to a "has one through" relationship. * - * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough + * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough */ public function one() { @@ -101,99 +34,7 @@ public function one() )); } - /** - * Set the base constraints on the relation query. - * - * @return void - */ - public function addConstraints() - { - $localValue = $this->farParent[$this->localKey]; - - $this->performJoin(); - - if (static::$constraints) { - $this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue); - } - } - - /** - * Set the join clause on the query. - * - * @param \Illuminate\Database\Eloquent\Builder|null $query - * @return void - */ - protected function performJoin(?Builder $query = null) - { - $query = $query ?: $this->query; - - $farKey = $this->getQualifiedFarKeyName(); - - $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey); - - if ($this->throughParentSoftDeletes()) { - $query->withGlobalScope('SoftDeletableHasManyThrough', function ($query) { - $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); - }); - } - } - - /** - * Get the fully qualified parent key name. - * - * @return string - */ - public function getQualifiedParentKeyName() - { - return $this->parent->qualifyColumn($this->secondLocalKey); - } - - /** - * Determine whether "through" parent of the relation uses Soft Deletes. - * - * @return bool - */ - public function throughParentSoftDeletes() - { - return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent)); - } - - /** - * Indicate that trashed "through" parents should be included in the query. - * - * @return $this - */ - public function withTrashedParents() - { - $this->query->withoutGlobalScope('SoftDeletableHasManyThrough'); - - return $this; - } - - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ - public function addEagerConstraints(array $models) - { - $whereIn = $this->whereInMethod($this->farParent, $this->localKey); - - $this->whereInEager( - $whereIn, - $this->getQualifiedFirstKeyName(), - $this->getKeys($models, $this->localKey) - ); - } - - /** - * Initialize the relation on a set of models. - * - * @param array $models - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function initRelation(array $models, $relation) { foreach ($models as $model) { @@ -203,14 +44,7 @@ public function initRelation(array $models, $relation) return $models; } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { $dictionary = $this->buildDictionary($results); @@ -229,690 +63,11 @@ public function match(array $models, Collection $results, $relation) return $models; } - /** - * Build model dictionary keyed by the relation's foreign key. - * - * @param \Illuminate\Database\Eloquent\Collection $results - * @return array - */ - protected function buildDictionary(Collection $results) - { - $dictionary = []; - - // First we will create a dictionary of models keyed by the foreign key of the - // relationship as this will allow us to quickly access all of the related - // models without having to do nested looping which will be quite slow. - foreach ($results as $result) { - $dictionary[$result->laravel_through_key][] = $result; - } - - return $dictionary; - } - - /** - * Get the first related model record matching the attributes or instantiate it. - * - * @param array $attributes - * @param array $values - * @return \Illuminate\Database\Eloquent\Model - */ - public function firstOrNew(array $attributes = [], array $values = []) - { - if (! is_null($instance = $this->where($attributes)->first())) { - return $instance; - } - - return $this->related->newInstance(array_merge($attributes, $values)); - } - - /** - * Get the first record matching the attributes. If the record is not found, create it. - * - * @param array $attributes - * @param array $values - * @return \Illuminate\Database\Eloquent\Model - */ - public function firstOrCreate(array $attributes = [], array $values = []) - { - if (! is_null($instance = (clone $this)->where($attributes)->first())) { - return $instance; - } - - return $this->createOrFirst(array_merge($attributes, $values)); - } - - /** - * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record. - * - * @param array $attributes - * @param array $values - * @return \Illuminate\Database\Eloquent\Model - */ - public function createOrFirst(array $attributes = [], array $values = []) - { - try { - return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values))); - } catch (UniqueConstraintViolationException $exception) { - return $this->where($attributes)->first() ?? throw $exception; - } - } - - /** - * Create or update a related record matching the attributes, and fill it with values. - * - * @param array $attributes - * @param array $values - * @return \Illuminate\Database\Eloquent\Model - */ - public function updateOrCreate(array $attributes, array $values = []) - { - return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) { - if (! $instance->wasRecentlyCreated) { - $instance->fill($values)->save(); - } - }); - } - - /** - * Add a basic where clause to the query, and return the first result. - * - * @param \Closure|string|array $column - * @param mixed $operator - * @param mixed $value - * @param string $boolean - * @return \Illuminate\Database\Eloquent\Model|static|null - */ - public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') - { - return $this->where($column, $operator, $value, $boolean)->first(); - } - - /** - * Execute the query and get the first related model. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Model|static|null - */ - public function first($columns = ['*']) - { - $results = $this->take(1)->get($columns); - - return count($results) > 0 ? $results->first() : null; - } - - /** - * Execute the query and get the first result or throw an exception. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Model|static - * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> - */ - public function firstOrFail($columns = ['*']) - { - if (! is_null($model = $this->first($columns))) { - return $model; - } - - throw (new ModelNotFoundException)->setModel(get_class($this->related)); - } - - /** - * Execute the query and get the first result or call a callback. - * - * @param \Closure|array $columns - * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Model|static|mixed - */ - public function firstOr($columns = ['*'], ?Closure $callback = null) - { - if ($columns instanceof Closure) { - $callback = $columns; - - $columns = ['*']; - } - - if (! is_null($model = $this->first($columns))) { - return $model; - } - - return $callback(); - } - - /** - * Find a related model by its primary key. - * - * @param mixed $id - * @param array $columns - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null - */ - public function find($id, $columns = ['*']) - { - if (is_array($id) || $id instanceof Arrayable) { - return $this->findMany($id, $columns); - } - - return $this->where( - $this->getRelated()->getQualifiedKeyName(), '=', $id - )->first($columns); - } - - /** - * Find multiple related models by their primary keys. - * - * @param \Illuminate\Contracts\Support\Arrayable|array $ids - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function findMany($ids, $columns = ['*']) - { - $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids; - - if (empty($ids)) { - return $this->getRelated()->newCollection(); - } - - return $this->whereIn( - $this->getRelated()->getQualifiedKeyName(), $ids - )->get($columns); - } - - /** - * Find a related model by its primary key or throw an exception. - * - * @param mixed $id - * @param array $columns - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection - * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> - */ - public function findOrFail($id, $columns = ['*']) - { - $result = $this->find($id, $columns); - - $id = $id instanceof Arrayable ? $id->toArray() : $id; - - if (is_array($id)) { - if (count($result) === count(array_unique($id))) { - return $result; - } - } elseif (! is_null($result)) { - return $result; - } - - throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); - } - - /** - * Find a related model by its primary key or call a callback. - * - * @param mixed $id - * @param \Closure|array $columns - * @param \Closure|null $callback - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed - */ - public function findOr($id, $columns = ['*'], ?Closure $callback = null) - { - if ($columns instanceof Closure) { - $callback = $columns; - - $columns = ['*']; - } - - $result = $this->find($id, $columns); - - $id = $id instanceof Arrayable ? $id->toArray() : $id; - - if (is_array($id)) { - if (count($result) === count(array_unique($id))) { - return $result; - } - } elseif (! is_null($result)) { - return $result; - } - - return $callback(); - } - - /** - * Get the results of the relationship. - * - * @return mixed - */ + /** @inheritDoc */ public function getResults() { return ! is_null($this->farParent->{$this->localKey}) ? $this->get() : $this->related->newCollection(); } - - /** - * Execute the query as a "select" statement. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function get($columns = ['*']) - { - $builder = $this->prepareQueryBuilder($columns); - - $models = $builder->getModels(); - - // If we actually found models we will also eager load any relationships that - // have been specified as needing to be eager loaded. This will solve the - // n + 1 query problem for the developer and also increase performance. - if (count($models) > 0) { - $models = $builder->eagerLoadRelations($models); - } - - return $this->query->applyAfterQueryCallbacks( - $this->related->newCollection($models) - ); - } - - /** - * Get a paginator for the "select" statement. - * - * @param int|null $perPage - * @param array $columns - * @param string $pageName - * @param int $page - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) - { - $this->query->addSelect($this->shouldSelect($columns)); - - return $this->query->paginate($perPage, $columns, $pageName, $page); - } - - /** - * Paginate the given query into a simple paginator. - * - * @param int|null $perPage - * @param array $columns - * @param string $pageName - * @param int|null $page - * @return \Illuminate\Contracts\Pagination\Paginator - */ - public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) - { - $this->query->addSelect($this->shouldSelect($columns)); - - return $this->query->simplePaginate($perPage, $columns, $pageName, $page); - } - - /** - * Paginate the given query into a cursor paginator. - * - * @param int|null $perPage - * @param array $columns - * @param string $cursorName - * @param string|null $cursor - * @return \Illuminate\Contracts\Pagination\CursorPaginator - */ - public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) - { - $this->query->addSelect($this->shouldSelect($columns)); - - return $this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor); - } - - /** - * Set the select clause for the relation query. - * - * @param array $columns - * @return array - */ - protected function shouldSelect(array $columns = ['*']) - { - if ($columns == ['*']) { - $columns = [$this->related->getTable().'.*']; - } - - return array_merge($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']); - } - - /** - * Chunk the results of the query. - * - * @param int $count - * @param callable $callback - * @return bool - */ - public function chunk($count, callable $callback) - { - return $this->prepareQueryBuilder()->chunk($count, $callback); - } - - /** - * Chunk the results of a query by comparing numeric IDs. - * - * @param int $count - * @param callable $callback - * @param string|null $column - * @param string|null $alias - * @return bool - */ - public function chunkById($count, callable $callback, $column = null, $alias = null) - { - $column ??= $this->getRelated()->getQualifiedKeyName(); - - $alias ??= $this->getRelated()->getKeyName(); - - return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias); - } - - /** - * Chunk the results of a query by comparing IDs in descending order. - * - * @param int $count - * @param callable $callback - * @param string|null $column - * @param string|null $alias - * @return bool - */ - public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null) - { - $column ??= $this->getRelated()->getQualifiedKeyName(); - - $alias ??= $this->getRelated()->getKeyName(); - - return $this->prepareQueryBuilder()->chunkByIdDesc($count, $callback, $column, $alias); - } - - /** - * Execute a callback over each item while chunking by ID. - * - * @param callable $callback - * @param int $count - * @param string|null $column - * @param string|null $alias - * @return bool - */ - public function eachById(callable $callback, $count = 1000, $column = null, $alias = null) - { - $column = $column ?? $this->getRelated()->getQualifiedKeyName(); - - $alias = $alias ?? $this->getRelated()->getKeyName(); - - return $this->prepareQueryBuilder()->eachById($callback, $count, $column, $alias); - } - - /** - * Get a generator for the given query. - * - * @return \Illuminate\Support\LazyCollection - */ - public function cursor() - { - return $this->prepareQueryBuilder()->cursor(); - } - - /** - * Execute a callback over each item while chunking. - * - * @param callable $callback - * @param int $count - * @return bool - */ - public function each(callable $callback, $count = 1000) - { - return $this->chunk($count, function ($results) use ($callback) { - foreach ($results as $key => $value) { - if ($callback($value, $key) === false) { - return false; - } - } - }); - } - - /** - * Query lazily, by chunks of the given size. - * - * @param int $chunkSize - * @return \Illuminate\Support\LazyCollection - */ - public function lazy($chunkSize = 1000) - { - return $this->prepareQueryBuilder()->lazy($chunkSize); - } - - /** - * Query lazily, by chunking the results of a query by comparing IDs. - * - * @param int $chunkSize - * @param string|null $column - * @param string|null $alias - * @return \Illuminate\Support\LazyCollection - */ - public function lazyById($chunkSize = 1000, $column = null, $alias = null) - { - $column ??= $this->getRelated()->getQualifiedKeyName(); - - $alias ??= $this->getRelated()->getKeyName(); - - return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias); - } - - /** - * Query lazily, by chunking the results of a query by comparing IDs in descending order. - * - * @param int $chunkSize - * @param string|null $column - * @param string|null $alias - * @return \Illuminate\Support\LazyCollection - */ - public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) - { - $column ??= $this->getRelated()->getQualifiedKeyName(); - - $alias ??= $this->getRelated()->getKeyName(); - - return $this->prepareQueryBuilder()->lazyByIdDesc($chunkSize, $column, $alias); - } - - /** - * Prepare the query builder for query execution. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function prepareQueryBuilder($columns = ['*']) - { - $builder = $this->query->applyScopes(); - - return $builder->addSelect( - $this->shouldSelect($builder->getQuery()->columns ? [] : $columns) - ); - } - - /** - * Add the constraints for a relationship query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ - public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) - { - if ($parentQuery->getQuery()->from === $query->getQuery()->from) { - return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); - } - - if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) { - return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns); - } - - $this->performJoin($query); - - return $query->select($columns)->whereColumn( - $this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName() - ); - } - - /** - * Add the constraints for a relationship query on the same table. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ - public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) - { - $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); - - $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey); - - if ($this->throughParentSoftDeletes()) { - $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); - } - - $query->getModel()->setTable($hash); - - return $query->select($columns)->whereColumn( - $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $this->getQualifiedFirstKeyName() - ); - } - - /** - * Add the constraints for a relationship query on the same table as the through parent. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ - public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) - { - $table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash(); - - $query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName()); - - if ($this->throughParentSoftDeletes()) { - $query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn()); - } - - return $query->select($columns)->whereColumn( - $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey - ); - } - - /** - * Alias to set the "limit" value of the query. - * - * @param int $value - * @return $this - */ - public function take($value) - { - return $this->limit($value); - } - - /** - * Set the "limit" value of the query. - * - * @param int $value - * @return $this - */ - public function limit($value) - { - if ($this->farParent->exists) { - $this->query->limit($value); - } else { - $column = $this->getQualifiedFirstKeyName(); - - $grammar = $this->query->getQuery()->getGrammar(); - - if ($grammar instanceof MySqlGrammar && $grammar->useLegacyGroupLimit($this->query->getQuery())) { - $column = 'laravel_through_key'; - } - - $this->query->groupLimit($value, $column); - } - - return $this; - } - - /** - * Get the qualified foreign key on the related model. - * - * @return string - */ - public function getQualifiedFarKeyName() - { - return $this->getQualifiedForeignKeyName(); - } - - /** - * Get the foreign key on the "through" model. - * - * @return string - */ - public function getFirstKeyName() - { - return $this->firstKey; - } - - /** - * Get the qualified foreign key on the "through" model. - * - * @return string - */ - public function getQualifiedFirstKeyName() - { - return $this->throughParent->qualifyColumn($this->firstKey); - } - - /** - * Get the foreign key on the related model. - * - * @return string - */ - public function getForeignKeyName() - { - return $this->secondKey; - } - - /** - * Get the qualified foreign key on the related model. - * - * @return string - */ - public function getQualifiedForeignKeyName() - { - return $this->related->qualifyColumn($this->secondKey); - } - - /** - * Get the local key on the far parent model. - * - * @return string - */ - public function getLocalKeyName() - { - return $this->localKey; - } - - /** - * Get the qualified local key on the far parent model. - * - * @return string - */ - public function getQualifiedLocalKeyName() - { - return $this->farParent->qualifyColumn($this->localKey); - } - - /** - * Get the local key on the intermediary model. - * - * @return string - */ - public function getSecondLocalKeyName() - { - return $this->secondLocalKey; - } } diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOne.php b/src/Illuminate/Database/Eloquent/Relations/HasOne.php index ed85f1e910ee..20728b5a4d3e 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOne.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOne.php @@ -11,15 +11,17 @@ use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels; use Illuminate\Database\Query\JoinClause; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasOneOrMany + */ class HasOne extends HasOneOrMany implements SupportsPartialRelations { use ComparesRelatedModels, CanBeOneOfMany, SupportsDefaultModels; - /** - * Get the results of the relationship. - * - * @return mixed - */ + /** @inheritDoc */ public function getResults() { if (is_null($this->getParentKey())) { @@ -29,13 +31,7 @@ public function getResults() return $this->query->first() ?: $this->getDefaultFor($this->parent); } - /** - * Initialize the relation on a set of models. - * - * @param array $models - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function initRelation(array $models, $relation) { foreach ($models as $model) { @@ -45,29 +41,13 @@ public function initRelation(array $models, $relation) return $models; } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { return $this->matchOne($models, $results, $relation); } - /** - * Add the constraints for an internal relationship existence query. - * - * Essentially, these queries compare on column names like "whereColumn". - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($this->isOneOfMany()) { @@ -80,7 +60,7 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, /** * Add constraints for inner join subselect for one of many relationships. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @param string|null $column * @param string|null $aggregate * @return void @@ -114,8 +94,8 @@ public function addOneOfManyJoinSubQueryConstraints(JoinClause $join) /** * Make a new related instance for the given model. * - * @param \Illuminate\Database\Eloquent\Model $parent - * @return \Illuminate\Database\Eloquent\Model + * @param TDeclaringModel $parent + * @return TRelatedModel */ public function newRelatedInstanceFor(Model $parent) { @@ -127,8 +107,8 @@ public function newRelatedInstanceFor(Model $parent) /** * Get the value of the model's foreign key. * - * @param \Illuminate\Database\Eloquent\Model $model - * @return mixed + * @param TRelatedModel $model + * @return int|string */ protected function getRelatedKeyFrom(Model $model) { diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index e1d295d86be4..2a772b39822d 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -8,6 +8,13 @@ use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; use Illuminate\Database\UniqueConstraintViolationException; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * @template TResult + * + * @extends \Illuminate\Database\Eloquent\Relations\Relation + */ abstract class HasOneOrMany extends Relation { use InteractsWithDictionary; @@ -29,8 +36,8 @@ abstract class HasOneOrMany extends Relation /** * Create a new has one or many relationship instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $foreignKey * @param string $localKey * @return void @@ -47,7 +54,7 @@ public function __construct(Builder $query, Model $parent, $foreignKey, $localKe * Create and return an un-saved instance of the related model. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function make(array $attributes = []) { @@ -60,7 +67,7 @@ public function make(array $attributes = []) * Create and return an un-saved instance of the related models. * * @param iterable $records - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function makeMany($records) { @@ -89,12 +96,7 @@ public function addConstraints() } } - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ + /** @inheritDoc */ public function addEagerConstraints(array $models) { $whereIn = $this->whereInMethod($this->parent, $this->localKey); @@ -110,10 +112,10 @@ public function addEagerConstraints(array $models) /** * Match the eagerly loaded results to their single parents. * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation - * @return array + * @return array */ public function matchOne(array $models, Collection $results, $relation) { @@ -123,10 +125,10 @@ public function matchOne(array $models, Collection $results, $relation) /** * Match the eagerly loaded results to their many parents. * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation - * @return array + * @return array */ public function matchMany(array $models, Collection $results, $relation) { @@ -136,11 +138,11 @@ public function matchMany(array $models, Collection $results, $relation) /** * Match the eagerly loaded results to their many parents. * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @param string $type - * @return array + * @return array */ protected function matchOneOrMany(array $models, Collection $results, $relation, $type) { @@ -178,8 +180,8 @@ protected function getRelationValue(array $dictionary, $key, $type) /** * Build model dictionary keyed by the relation's foreign key. * - * @param \Illuminate\Database\Eloquent\Collection $results - * @return array + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array> */ protected function buildDictionary(Collection $results) { @@ -195,7 +197,7 @@ protected function buildDictionary(Collection $results) * * @param mixed $id * @param array $columns - * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model + * @return \Illuminate\Support\Collection|TRelatedModel */ public function findOrNew($id, $columns = ['*']) { @@ -213,7 +215,7 @@ public function findOrNew($id, $columns = ['*']) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function firstOrNew(array $attributes = [], array $values = []) { @@ -231,7 +233,7 @@ public function firstOrNew(array $attributes = [], array $values = []) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function firstOrCreate(array $attributes = [], array $values = []) { @@ -247,7 +249,7 @@ public function firstOrCreate(array $attributes = [], array $values = []) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function createOrFirst(array $attributes = [], array $values = []) { @@ -263,7 +265,7 @@ public function createOrFirst(array $attributes = [], array $values = []) * * @param array $attributes * @param array $values - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function updateOrCreate(array $attributes, array $values = []) { @@ -277,8 +279,8 @@ public function updateOrCreate(array $attributes, array $values = []) /** * Attach a model instance to the parent model. * - * @param \Illuminate\Database\Eloquent\Model $model - * @return \Illuminate\Database\Eloquent\Model|false + * @param TRelatedModel $model + * @return TRelatedModel|false */ public function save(Model $model) { @@ -290,8 +292,8 @@ public function save(Model $model) /** * Attach a model instance without raising any events to the parent model. * - * @param \Illuminate\Database\Eloquent\Model $model - * @return \Illuminate\Database\Eloquent\Model|false + * @param TRelatedModel $model + * @return TRelatedModel|false */ public function saveQuietly(Model $model) { @@ -303,8 +305,8 @@ public function saveQuietly(Model $model) /** * Attach a collection of models to the parent instance. * - * @param iterable $models - * @return iterable + * @param iterable $models + * @return iterable */ public function saveMany($models) { @@ -318,8 +320,8 @@ public function saveMany($models) /** * Attach a collection of models to the parent instance without raising any events to the parent model. * - * @param iterable $models - * @return iterable + * @param iterable $models + * @return iterable */ public function saveManyQuietly($models) { @@ -332,7 +334,7 @@ public function saveManyQuietly($models) * Create a new instance of the related model. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function create(array $attributes = []) { @@ -347,7 +349,7 @@ public function create(array $attributes = []) * Create a new instance of the related model without raising any events to the parent model. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function createQuietly(array $attributes = []) { @@ -358,7 +360,7 @@ public function createQuietly(array $attributes = []) * Create a new instance of the related model. Allow mass-assignment. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function forceCreate(array $attributes = []) { @@ -371,7 +373,7 @@ public function forceCreate(array $attributes = []) * Create a new instance of the related model with mass assignment without raising model events. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function forceCreateQuietly(array $attributes = []) { @@ -382,7 +384,7 @@ public function forceCreateQuietly(array $attributes = []) * Create a Collection of new instances of the related model. * * @param iterable $records - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function createMany(iterable $records) { @@ -399,7 +401,7 @@ public function createMany(iterable $records) * Create a Collection of new instances of the related model without raising any events to the parent model. * * @param iterable $records - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function createManyQuietly(iterable $records) { @@ -409,7 +411,7 @@ public function createManyQuietly(iterable $records) /** * Set the foreign ID for creating a related model. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param TRelatedModel $model * @return void */ protected function setForeignAttributesForCreate(Model $model) @@ -417,14 +419,7 @@ protected function setForeignAttributesForCreate(Model $model) $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); } - /** - * Add the constraints for a relationship query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($query->getQuery()->from == $parentQuery->getQuery()->from) { @@ -437,10 +432,10 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, /** * Add the constraints for a relationship query on the same table. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php new file mode 100644 index 000000000000..9f1adad9d131 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php @@ -0,0 +1,837 @@ + + */ +abstract class HasOneOrManyThrough extends Relation +{ + use InteractsWithDictionary; + + /** + * The "through" parent model instance. + * + * @var TIntermediateModel + */ + protected $throughParent; + + /** + * The far parent model instance. + * + * @var TDeclaringModel + */ + protected $farParent; + + /** + * The near key on the relationship. + * + * @var string + */ + protected $firstKey; + + /** + * The far key on the relationship. + * + * @var string + */ + protected $secondKey; + + /** + * The local key on the relationship. + * + * @var string + */ + protected $localKey; + + /** + * The local key on the intermediary model. + * + * @var string + */ + protected $secondLocalKey; + + /** + * Create a new has many through relationship instance. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $farParent + * @param TIntermediateModel $throughParent + * @param string $firstKey + * @param string $secondKey + * @param string $localKey + * @param string $secondLocalKey + * @return void + */ + public function __construct(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) + { + $this->localKey = $localKey; + $this->firstKey = $firstKey; + $this->secondKey = $secondKey; + $this->farParent = $farParent; + $this->throughParent = $throughParent; + $this->secondLocalKey = $secondLocalKey; + + parent::__construct($query, $throughParent); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + $localValue = $this->farParent[$this->localKey]; + + $this->performJoin(); + + if (static::$constraints) { + $this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue); + } + } + + /** + * Set the join clause on the query. + * + * @param \Illuminate\Database\Eloquent\Builder|null $query + * @return void + */ + protected function performJoin(?Builder $query = null) + { + $query = $query ?: $this->query; + + $farKey = $this->getQualifiedFarKeyName(); + + $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey); + + if ($this->throughParentSoftDeletes()) { + $query->withGlobalScope('SoftDeletableHasManyThrough', function ($query) { + $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); + }); + } + } + + /** + * Get the fully qualified parent key name. + * + * @return string + */ + public function getQualifiedParentKeyName() + { + return $this->parent->qualifyColumn($this->secondLocalKey); + } + + /** + * Determine whether "through" parent of the relation uses Soft Deletes. + * + * @return bool + */ + public function throughParentSoftDeletes() + { + return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent)); + } + + /** + * Indicate that trashed "through" parents should be included in the query. + * + * @return $this + */ + public function withTrashedParents() + { + $this->query->withoutGlobalScope('SoftDeletableHasManyThrough'); + + return $this; + } + + /** @inheritDoc */ + public function addEagerConstraints(array $models) + { + $whereIn = $this->whereInMethod($this->farParent, $this->localKey); + + $this->whereInEager( + $whereIn, + $this->getQualifiedFirstKeyName(), + $this->getKeys($models, $this->localKey) + ); + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array> + */ + protected function buildDictionary(Collection $results) + { + $dictionary = []; + + // First we will create a dictionary of models keyed by the foreign key of the + // relationship as this will allow us to quickly access all of the related + // models without having to do nested looping which will be quite slow. + foreach ($results as $result) { + $dictionary[$result->laravel_through_key][] = $result; + } + + return $dictionary; + } + + /** + * Get the first related model record matching the attributes or instantiate it. + * + * @param array $attributes + * @param array $values + * @return TRelatedModel + */ + public function firstOrNew(array $attributes = [], array $values = []) + { + if (! is_null($instance = $this->where($attributes)->first())) { + return $instance; + } + + return $this->related->newInstance(array_merge($attributes, $values)); + } + + /** + * Get the first record matching the attributes. If the record is not found, create it. + * + * @param array $attributes + * @param array $values + * @return TRelatedModel + */ + public function firstOrCreate(array $attributes = [], array $values = []) + { + if (! is_null($instance = (clone $this)->where($attributes)->first())) { + return $instance; + } + + return $this->createOrFirst(array_merge($attributes, $values)); + } + + /** + * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record. + * + * @param array $attributes + * @param array $values + * @return TRelatedModel + */ + public function createOrFirst(array $attributes = [], array $values = []) + { + try { + return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values))); + } catch (UniqueConstraintViolationException $exception) { + return $this->where($attributes)->first() ?? throw $exception; + } + } + + /** + * Create or update a related record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @return TRelatedModel + */ + public function updateOrCreate(array $attributes, array $values = []) + { + return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) { + if (! $instance->wasRecentlyCreated) { + $instance->fill($values)->save(); + } + }); + } + + /** + * Add a basic where clause to the query, and return the first result. + * + * @param \Closure|string|array $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return TRelatedModel|null + */ + public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') + { + return $this->where($column, $operator, $value, $boolean)->first(); + } + + /** + * Execute the query and get the first related model. + * + * @param array $columns + * @return TRelatedModel|null + */ + public function first($columns = ['*']) + { + $results = $this->take(1)->get($columns); + + return count($results) > 0 ? $results->first() : null; + } + + /** + * Execute the query and get the first result or throw an exception. + * + * @param array $columns + * @return TRelatedModel + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function firstOrFail($columns = ['*']) + { + if (! is_null($model = $this->first($columns))) { + return $model; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->related)); + } + + /** + * Execute the query and get the first result or call a callback. + * + * @param \Closure|array $columns + * @param \Closure|null $callback + * @return TRelatedModel|mixed + */ + public function firstOr($columns = ['*'], ?Closure $callback = null) + { + if ($columns instanceof Closure) { + $callback = $columns; + + $columns = ['*']; + } + + if (! is_null($model = $this->first($columns))) { + return $model; + } + + return $callback(); + } + + /** + * Find a related model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection|null + */ + public function find($id, $columns = ['*']) + { + if (is_array($id) || $id instanceof Arrayable) { + return $this->findMany($id, $columns); + } + + return $this->where( + $this->getRelated()->getQualifiedKeyName(), '=', $id + )->first($columns); + } + + /** + * Find multiple related models by their primary keys. + * + * @param \Illuminate\Contracts\Support\Arrayable|array $ids + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function findMany($ids, $columns = ['*']) + { + $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids; + + if (empty($ids)) { + return $this->getRelated()->newCollection(); + } + + return $this->whereIn( + $this->getRelated()->getQualifiedKeyName(), $ids + )->get($columns); + } + + /** + * Find a related model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function findOrFail($id, $columns = ['*']) + { + $result = $this->find($id, $columns); + + $id = $id instanceof Arrayable ? $id->toArray() : $id; + + if (is_array($id)) { + if (count($result) === count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); + } + + /** + * Find a related model by its primary key or call a callback. + * + * @param mixed $id + * @param \Closure|array $columns + * @param \Closure|null $callback + * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection|mixed + */ + public function findOr($id, $columns = ['*'], ?Closure $callback = null) + { + if ($columns instanceof Closure) { + $callback = $columns; + + $columns = ['*']; + } + + $result = $this->find($id, $columns); + + $id = $id instanceof Arrayable ? $id->toArray() : $id; + + if (is_array($id)) { + if (count($result) === count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + return $callback(); + } + + /** @inheritDoc */ + public function get($columns = ['*']) + { + $builder = $this->prepareQueryBuilder($columns); + + $models = $builder->getModels(); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded. This will solve the + // n + 1 query problem for the developer and also increase performance. + if (count($models) > 0) { + $models = $builder->eagerLoadRelations($models); + } + + return $this->query->applyAfterQueryCallbacks( + $this->related->newCollection($models) + ); + } + + /** + * Get a paginator for the "select" statement. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return $this->query->paginate($perPage, $columns, $pageName, $page); + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return $this->query->simplePaginate($perPage, $columns, $pageName, $page); + } + + /** + * Paginate the given query into a cursor paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $cursorName + * @param string|null $cursor + * @return \Illuminate\Contracts\Pagination\CursorPaginator + */ + public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return $this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor); + } + + /** + * Set the select clause for the relation query. + * + * @param array $columns + * @return array + */ + protected function shouldSelect(array $columns = ['*']) + { + if ($columns == ['*']) { + $columns = [$this->related->getTable().'.*']; + } + + return array_merge($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']); + } + + /** + * Chunk the results of the query. + * + * @param int $count + * @param callable $callback + * @return bool + */ + public function chunk($count, callable $callback) + { + return $this->prepareQueryBuilder()->chunk($count, $callback); + } + + /** + * Chunk the results of a query by comparing numeric IDs. + * + * @param int $count + * @param callable $callback + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function chunkById($count, callable $callback, $column = null, $alias = null) + { + $column ??= $this->getRelated()->getQualifiedKeyName(); + + $alias ??= $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias); + } + + /** + * Chunk the results of a query by comparing IDs in descending order. + * + * @param int $count + * @param callable $callback + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null) + { + $column ??= $this->getRelated()->getQualifiedKeyName(); + + $alias ??= $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->chunkByIdDesc($count, $callback, $column, $alias); + } + + /** + * Execute a callback over each item while chunking by ID. + * + * @param callable $callback + * @param int $count + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function eachById(callable $callback, $count = 1000, $column = null, $alias = null) + { + $column = $column ?? $this->getRelated()->getQualifiedKeyName(); + + $alias = $alias ?? $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->eachById($callback, $count, $column, $alias); + } + + /** + * Get a generator for the given query. + * + * @return \Illuminate\Support\LazyCollection + */ + public function cursor() + { + return $this->prepareQueryBuilder()->cursor(); + } + + /** + * Execute a callback over each item while chunking. + * + * @param callable $callback + * @param int $count + * @return bool + */ + public function each(callable $callback, $count = 1000) + { + return $this->chunk($count, function ($results) use ($callback) { + foreach ($results as $key => $value) { + if ($callback($value, $key) === false) { + return false; + } + } + }); + } + + /** + * Query lazily, by chunks of the given size. + * + * @param int $chunkSize + * @return \Illuminate\Support\LazyCollection + */ + public function lazy($chunkSize = 1000) + { + return $this->prepareQueryBuilder()->lazy($chunkSize); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + */ + public function lazyById($chunkSize = 1000, $column = null, $alias = null) + { + $column ??= $this->getRelated()->getQualifiedKeyName(); + + $alias ??= $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs in descending order. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + */ + public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) + { + $column ??= $this->getRelated()->getQualifiedKeyName(); + + $alias ??= $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->lazyByIdDesc($chunkSize, $column, $alias); + } + + /** + * Prepare the query builder for query execution. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function prepareQueryBuilder($columns = ['*']) + { + $builder = $this->query->applyScopes(); + + return $builder->addSelect( + $this->shouldSelect($builder->getQuery()->columns ? [] : $columns) + ); + } + + /** @inheritDoc */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($parentQuery->getQuery()->from === $query->getQuery()->from) { + return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); + } + + if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) { + return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns); + } + + $this->performJoin($query); + + return $query->select($columns)->whereColumn( + $this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName() + ); + } + + /** + * Add the constraints for a relationship query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); + + $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey); + + if ($this->throughParentSoftDeletes()) { + $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); + } + + $query->getModel()->setTable($hash); + + return $query->select($columns)->whereColumn( + $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $this->getQualifiedFirstKeyName() + ); + } + + /** + * Add the constraints for a relationship query on the same table as the through parent. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash(); + + $query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName()); + + if ($this->throughParentSoftDeletes()) { + $query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn()); + } + + return $query->select($columns)->whereColumn( + $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey + ); + } + + /** + * Alias to set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function take($value) + { + return $this->limit($value); + } + + /** + * Set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function limit($value) + { + if ($this->farParent->exists) { + $this->query->limit($value); + } else { + $column = $this->getQualifiedFirstKeyName(); + + $grammar = $this->query->getQuery()->getGrammar(); + + if ($grammar instanceof MySqlGrammar && $grammar->useLegacyGroupLimit($this->query->getQuery())) { + $column = 'laravel_through_key'; + } + + $this->query->groupLimit($value, $column); + } + + return $this; + } + + /** + * Get the qualified foreign key on the related model. + * + * @return string + */ + public function getQualifiedFarKeyName() + { + return $this->getQualifiedForeignKeyName(); + } + + /** + * Get the foreign key on the "through" model. + * + * @return string + */ + public function getFirstKeyName() + { + return $this->firstKey; + } + + /** + * Get the qualified foreign key on the "through" model. + * + * @return string + */ + public function getQualifiedFirstKeyName() + { + return $this->throughParent->qualifyColumn($this->firstKey); + } + + /** + * Get the foreign key on the related model. + * + * @return string + */ + public function getForeignKeyName() + { + return $this->secondKey; + } + + /** + * Get the qualified foreign key on the related model. + * + * @return string + */ + public function getQualifiedForeignKeyName() + { + return $this->related->qualifyColumn($this->secondKey); + } + + /** + * Get the local key on the far parent model. + * + * @return string + */ + public function getLocalKeyName() + { + return $this->localKey; + } + + /** + * Get the qualified local key on the far parent model. + * + * @return string + */ + public function getQualifiedLocalKeyName() + { + return $this->farParent->qualifyColumn($this->localKey); + } + + /** + * Get the local key on the intermediary model. + * + * @return string + */ + public function getSecondLocalKeyName() + { + return $this->secondLocalKey; + } +} diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php index ed9c7baa4dc3..7cf7850d114d 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php @@ -7,27 +7,24 @@ use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels; -class HasOneThrough extends HasManyThrough +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasOneOrManyThrough + */ +class HasOneThrough extends HasOneOrManyThrough { use InteractsWithDictionary, SupportsDefaultModels; - /** - * Get the results of the relationship. - * - * @return mixed - */ + /** @inheritDoc */ public function getResults() { return $this->first() ?: $this->getDefaultFor($this->farParent); } - /** - * Initialize the relation on a set of models. - * - * @param array $models - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function initRelation(array $models, $relation) { foreach ($models as $model) { @@ -37,14 +34,7 @@ public function initRelation(array $models, $relation) return $models; } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { $dictionary = $this->buildDictionary($results); @@ -67,8 +57,8 @@ public function match(array $models, Collection $results, $relation) /** * Make a new related instance for the given model. * - * @param \Illuminate\Database\Eloquent\Model $parent - * @return \Illuminate\Database\Eloquent\Model + * @param TDeclaringModel $parent + * @return TRelatedModel */ public function newRelatedInstanceFor(Model $parent) { diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphMany.php index 3636f25d06c2..cd2a51d33ed6 100755 --- a/src/Illuminate/Database/Eloquent/Relations/MorphMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphMany.php @@ -3,14 +3,19 @@ namespace Illuminate\Database\Eloquent\Relations; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Database\Eloquent\Model; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\MorphOneOrMany> + */ class MorphMany extends MorphOneOrMany { /** * Convert the relationship to a "morph one" relationship. * - * @return \Illuminate\Database\Eloquent\Relations\MorphOne + * @return \Illuminate\Database\Eloquent\Relations\MorphOne */ public function one() { @@ -23,11 +28,7 @@ public function one() )); } - /** - * Get the results of the relationship. - * - * @return mixed - */ + /** @inheritDoc */ public function getResults() { return ! is_null($this->getParentKey()) @@ -35,13 +36,7 @@ public function getResults() : $this->related->newCollection(); } - /** - * Initialize the relation on a set of models. - * - * @param array $models - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function initRelation(array $models, $relation) { foreach ($models as $model) { @@ -51,40 +46,17 @@ public function initRelation(array $models, $relation) return $models; } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { return $this->matchMany($models, $results, $relation); } - /** - * Create a new instance of the related model. Allow mass-assignment. - * - * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model - */ + /** @inheritDoc */ public function forceCreate(array $attributes = []) { $attributes[$this->getMorphType()] = $this->morphClass; return parent::forceCreate($attributes); } - - /** - * Create a new instance of the related model with mass assignment without raising model events. - * - * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model - */ - public function forceCreateQuietly(array $attributes = []) - { - return Model::withoutEvents(fn () => $this->forceCreate($attributes)); - } } diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphOne.php b/src/Illuminate/Database/Eloquent/Relations/MorphOne.php index fc8f4dc8ca48..34429fdcd112 100755 --- a/src/Illuminate/Database/Eloquent/Relations/MorphOne.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphOne.php @@ -11,15 +11,17 @@ use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels; use Illuminate\Database\Query\JoinClause; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\MorphOneOrMany + */ class MorphOne extends MorphOneOrMany implements SupportsPartialRelations { use CanBeOneOfMany, ComparesRelatedModels, SupportsDefaultModels; - /** - * Get the results of the relationship. - * - * @return mixed - */ + /** @inheritDoc */ public function getResults() { if (is_null($this->getParentKey())) { @@ -29,13 +31,7 @@ public function getResults() return $this->query->first() ?: $this->getDefaultFor($this->parent); } - /** - * Initialize the relation on a set of models. - * - * @param array $models - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function initRelation(array $models, $relation) { foreach ($models as $model) { @@ -45,27 +41,13 @@ public function initRelation(array $models, $relation) return $models; } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { return $this->matchOne($models, $results, $relation); } - /** - * Get the relationship query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($this->isOneOfMany()) { @@ -78,7 +60,7 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, /** * Add constraints for inner join subselect for one of many relationships. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @param string|null $column * @param string|null $aggregate * @return void @@ -114,8 +96,8 @@ public function addOneOfManyJoinSubQueryConstraints(JoinClause $join) /** * Make a new related instance for the given model. * - * @param \Illuminate\Database\Eloquent\Model $parent - * @return \Illuminate\Database\Eloquent\Model + * @param TDeclaringModel $parent + * @return TRelatedModel */ public function newRelatedInstanceFor(Model $parent) { @@ -127,8 +109,8 @@ public function newRelatedInstanceFor(Model $parent) /** * Get the value of the model's foreign key. * - * @param \Illuminate\Database\Eloquent\Model $model - * @return mixed + * @param TRelatedModel $model + * @return int|string */ protected function getRelatedKeyFrom(Model $model) { diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php index 3cfec895548d..d41757df711f 100755 --- a/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php @@ -5,6 +5,13 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * @template TResult + * + * @extends \Illuminate\Database\Eloquent\Relations\HasOneOrMany + */ abstract class MorphOneOrMany extends HasOneOrMany { /** @@ -24,8 +31,8 @@ abstract class MorphOneOrMany extends HasOneOrMany /** * Create a new morph one or many relationship instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $type * @param string $id * @param string $localKey @@ -54,12 +61,7 @@ public function addConstraints() } } - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ + /** @inheritDoc */ public function addEagerConstraints(array $models) { parent::addEagerConstraints($models); @@ -71,7 +73,7 @@ public function addEagerConstraints(array $models) * Create a new instance of the related model. Allow mass-assignment. * * @param array $attributes - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function forceCreate(array $attributes = []) { @@ -84,7 +86,7 @@ public function forceCreate(array $attributes = []) /** * Set the foreign ID and type for creating a related model. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param TRelatedModel $model * @return void */ protected function setForeignAttributesForCreate(Model $model) @@ -94,14 +96,7 @@ protected function setForeignAttributesForCreate(Model $model) $model->{$this->getMorphType()} = $this->morphClass; } - /** - * Get the relationship query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where( diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php index 39c7852f2888..6a3f395e73a6 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php @@ -25,8 +25,8 @@ class MorphPivot extends Pivot /** * Set the keys for a save update query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSaveQuery($query) { @@ -38,8 +38,8 @@ protected function setKeysForSaveQuery($query) /** * Set the keys for a select query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSelectQuery($query) { @@ -133,7 +133,7 @@ public function getQueueableId() * Get a new query to restore one or more models by their queueable IDs. * * @param array|int $ids - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryForRestoration($ids) { @@ -157,7 +157,7 @@ public function newQueryForRestoration($ids) * Get a new query to restore multiple models by their queueable IDs. * * @param array $ids - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ protected function newQueryForCollectionRestoration(array $ids) { diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php index 570287ac0b48..a1bfed37572c 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php @@ -8,6 +8,12 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\BelongsTo + */ class MorphTo extends BelongsTo { use InteractsWithDictionary; @@ -22,7 +28,7 @@ class MorphTo extends BelongsTo /** * The models whose relations are being eager loaded. * - * @var \Illuminate\Database\Eloquent\Collection + * @var \Illuminate\Database\Eloquent\Collection */ protected $models; @@ -64,8 +70,8 @@ class MorphTo extends BelongsTo /** * Create a new morph to relationship instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $foreignKey * @param string $ownerKey * @param string $type @@ -79,12 +85,7 @@ public function __construct(Builder $query, Model $parent, $foreignKey, $ownerKe parent::__construct($query, $parent, $foreignKey, $ownerKey, $relation); } - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ + /** @inheritDoc */ public function addEagerConstraints(array $models) { $this->buildDictionary($this->models = Collection::make($models)); @@ -93,7 +94,7 @@ public function addEagerConstraints(array $models) /** * Build a dictionary with the models. * - * @param \Illuminate\Database\Eloquent\Collection $models + * @param \Illuminate\Database\Eloquent\Collection $models * @return void */ protected function buildDictionary(Collection $models) @@ -113,7 +114,7 @@ protected function buildDictionary(Collection $models) * * Called via eager load method of Eloquent query builder. * - * @return mixed + * @return \Illuminate\Database\Eloquent\Collection */ public function getEager() { @@ -128,7 +129,7 @@ public function getEager() * Get all of the relation results for a type. * * @param string $type - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ protected function getResultsByType($type) { @@ -177,7 +178,7 @@ protected function gatherKeysByType($type, $keyType) * Create a new model instance by type. * * @param string $type - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function createModelByType($type) { @@ -190,14 +191,7 @@ public function createModelByType($type) }); } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { return $models; @@ -207,7 +201,7 @@ public function match(array $models, Collection $results, $relation) * Match the results for a given type to their parents. * * @param string $type - * @param \Illuminate\Database\Eloquent\Collection $results + * @param \Illuminate\Database\Eloquent\Collection $results * @return void */ protected function matchToMorphParents($type, Collection $results) @@ -226,8 +220,8 @@ protected function matchToMorphParents($type, Collection $results) /** * Associate the model instance to the given parent. * - * @param \Illuminate\Database\Eloquent\Model|null $model - * @return \Illuminate\Database\Eloquent\Model + * @param TRelatedModel|null $model + * @return TDeclaringModel */ public function associate($model) { @@ -251,7 +245,7 @@ public function associate($model) /** * Dissociate previously associated model from the given parent. * - * @return \Illuminate\Database\Eloquent\Model + * @return TDDeclaringModel */ public function dissociate() { @@ -274,12 +268,7 @@ public function touch() } } - /** - * Make a new related instance for the given model. - * - * @param \Illuminate\Database\Eloquent\Model $parent - * @return \Illuminate\Database\Eloquent\Model - */ + /** @inheritDoc */ protected function newRelatedInstanceFor(Model $parent) { return $parent->{$this->getRelationName()}()->getRelated()->newInstance(); @@ -309,7 +298,7 @@ public function getDictionary() * Specify which relations to load for a given morph type. * * @param array $with - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return $this */ public function morphWith(array $with) { @@ -324,7 +313,7 @@ public function morphWith(array $with) * Specify which relationship counts to load for a given morph type. * * @param array $withCount - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return $this */ public function morphWithCount(array $withCount) { @@ -339,7 +328,7 @@ public function morphWithCount(array $withCount) * Specify constraints on the query for a given morph type. * * @param array $callbacks - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return $this */ public function constrain(array $callbacks) { @@ -404,8 +393,8 @@ public function onlyTrashed() /** * Replay stored macro calls on the actual related instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ protected function replayMacros(Builder $query) { diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php index 8cf113bd0f34..1f3c3e57ddb4 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php @@ -6,6 +6,12 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ class MorphToMany extends BelongsToMany { /** @@ -34,8 +40,8 @@ class MorphToMany extends BelongsToMany /** * Create a new morph to many relationship instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @param string $name * @param string $table * @param string $foreignPivotKey @@ -73,12 +79,7 @@ protected function addWhereConstraints() return $this; } - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ + /** @inheritDoc */ public function addEagerConstraints(array $models) { parent::addEagerConstraints($models); @@ -100,14 +101,7 @@ protected function baseAttachRecord($id, $timed) ); } - /** - * Add the constraints for a relationship count query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where( @@ -118,7 +112,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() { diff --git a/src/Illuminate/Database/Eloquent/Relations/Relation.php b/src/Illuminate/Database/Eloquent/Relations/Relation.php index d94671d4f777..21c616d887a7 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Relation.php +++ b/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -13,6 +13,13 @@ use Illuminate\Support\Traits\ForwardsCalls; use Illuminate\Support\Traits\Macroable; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * @template TResult + * + * @mixin \Illuminate\Database\Eloquent\Builder + */ abstract class Relation implements BuilderContract { use ForwardsCalls, Macroable { @@ -22,21 +29,21 @@ abstract class Relation implements BuilderContract /** * The Eloquent query builder instance. * - * @var \Illuminate\Database\Eloquent\Builder + * @var \Illuminate\Database\Eloquent\Builder */ protected $query; /** * The parent model instance. * - * @var \Illuminate\Database\Eloquent\Model + * @var TDeclaringModel */ protected $parent; /** * The related model instance. * - * @var \Illuminate\Database\Eloquent\Model + * @var TRelatedModel */ protected $related; @@ -78,8 +85,8 @@ abstract class Relation implements BuilderContract /** * Create a new relation instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent * @return void */ public function __construct(Builder $query, Model $parent) @@ -123,7 +130,7 @@ abstract public function addConstraints(); /** * Set the constraints for an eager load of the relation. * - * @param array $models + * @param array $models * @return void */ abstract public function addEagerConstraints(array $models); @@ -131,33 +138,33 @@ abstract public function addEagerConstraints(array $models); /** * Initialize the relation on a set of models. * - * @param array $models + * @param array $models * @param string $relation - * @return array + * @return array */ abstract public function initRelation(array $models, $relation); /** * Match the eagerly loaded results to their parents. * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation - * @return array + * @return array */ abstract public function match(array $models, Collection $results, $relation); /** * Get the results of the relationship. * - * @return mixed + * @return TResult */ abstract public function getResults(); /** * Get the relationship for eager loading. * - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function getEager() { @@ -170,9 +177,9 @@ public function getEager() * Execute the query and get the first result if it's the sole matching record. * * @param array|string $columns - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function sole($columns = ['*']) @@ -196,7 +203,7 @@ public function sole($columns = ['*']) * Execute the query as a "select" statement. * * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function get($columns = ['*']) { @@ -233,9 +240,9 @@ public function rawUpdate(array $attributes = []) /** * Add the constraints for a relationship count query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceCountQuery(Builder $query, Builder $parentQuery) { @@ -249,10 +256,10 @@ public function getRelationExistenceCountQuery(Builder $query, Builder $parentQu * * Essentially, these queries compare on column names like whereColumn. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -275,9 +282,9 @@ public function getRelationCountHash($incrementJoinCount = true) /** * Get all of the primary keys for an array of models. * - * @param array $models + * @param array $models * @param string|null $key - * @return array + * @return array */ protected function getKeys(array $models, $key = null) { @@ -289,7 +296,7 @@ protected function getKeys(array $models, $key = null) /** * Get the query builder that will contain the relationship constraints. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ protected function getRelationQuery() { @@ -299,7 +306,7 @@ protected function getRelationQuery() /** * Get the underlying query for the relation. * - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ public function getQuery() { @@ -329,7 +336,7 @@ public function toBase() /** * Get the parent model of the relation. * - * @return \Illuminate\Database\Eloquent\Model + * @return TDeclaringModel */ public function getParent() { @@ -349,7 +356,7 @@ public function getQualifiedParentKeyName() /** * Get the related model of the relation. * - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ public function getRelated() { @@ -392,10 +399,10 @@ public function relatedUpdatedAt() * @param string $whereIn * @param string $key * @param array $modelKeys - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder|null $query * @return void */ - protected function whereInEager(string $whereIn, string $key, array $modelKeys, $query = null) + protected function whereInEager(string $whereIn, string $key, array $modelKeys, ?Builder $query = null) { ($query ?? $this->query)->{$whereIn}($key, $modelKeys); diff --git a/src/Illuminate/Database/Eloquent/SoftDeletes.php b/src/Illuminate/Database/Eloquent/SoftDeletes.php index da7a4a371479..88c658a80b5c 100644 --- a/src/Illuminate/Database/Eloquent/SoftDeletes.php +++ b/src/Illuminate/Database/Eloquent/SoftDeletes.php @@ -3,9 +3,11 @@ namespace Illuminate\Database\Eloquent; /** - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withTrashed(bool $withTrashed = true) - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder onlyTrashed() - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutTrashed() + * @method static \Illuminate\Database\Eloquent\Builder withTrashed(bool $withTrashed = true) + * @method static \Illuminate\Database\Eloquent\Builder onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder withoutTrashed() + * + * @mixin \Illuminate\Database\Eloquent\Model */ trait SoftDeletes { diff --git a/src/Illuminate/Database/Eloquent/SoftDeletingScope.php b/src/Illuminate/Database/Eloquent/SoftDeletingScope.php index f0b0bd417925..d1ef0d22b9b9 100644 --- a/src/Illuminate/Database/Eloquent/SoftDeletingScope.php +++ b/src/Illuminate/Database/Eloquent/SoftDeletingScope.php @@ -14,8 +14,10 @@ class SoftDeletingScope implements Scope /** * Apply the scope to a given Eloquent query builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder - * @param \Illuminate\Database\Eloquent\Model $model + * @template TModel of \Illuminate\Database\Eloquent\Model + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @param TModel $model * @return void */ public function apply(Builder $builder, Model $model) @@ -26,7 +28,7 @@ public function apply(Builder $builder, Model $model) /** * Extend the query builder with the needed functions. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @return void */ public function extend(Builder $builder) @@ -47,7 +49,7 @@ public function extend(Builder $builder) /** * Get the "deleted at" column for the builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @return string */ protected function getDeletedAtColumn(Builder $builder) @@ -62,7 +64,7 @@ protected function getDeletedAtColumn(Builder $builder) /** * Add the restore extension to the builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @return void */ protected function addRestore(Builder $builder) @@ -77,7 +79,7 @@ protected function addRestore(Builder $builder) /** * Add the restore-or-create extension to the builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @return void */ protected function addRestoreOrCreate(Builder $builder) @@ -94,7 +96,7 @@ protected function addRestoreOrCreate(Builder $builder) /** * Add the create-or-restore extension to the builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @return void */ protected function addCreateOrRestore(Builder $builder) @@ -111,7 +113,7 @@ protected function addCreateOrRestore(Builder $builder) /** * Add the with-trashed extension to the builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @return void */ protected function addWithTrashed(Builder $builder) @@ -128,7 +130,7 @@ protected function addWithTrashed(Builder $builder) /** * Add the without-trashed extension to the builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @return void */ protected function addWithoutTrashed(Builder $builder) @@ -147,7 +149,7 @@ protected function addWithoutTrashed(Builder $builder) /** * Add the only-trashed extension to the builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @return void */ protected function addOnlyTrashed(Builder $builder) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 1448109d505f..c2bd73c63398 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -31,6 +31,7 @@ class Builder implements BuilderContract { + /** @use \Illuminate\Database\Concerns\BuildsQueries */ use BuildsQueries, ExplainsQueries, ForwardsCalls, Macroable { __call as macroCall; } @@ -289,7 +290,7 @@ public function select($columns = ['*']) /** * Add a subselect expression to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @param string $as * @return $this * @@ -325,7 +326,7 @@ public function selectRaw($expression, array $bindings = []) /** * Makes "from" fetch from a subquery. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @param string $as * @return $this * @@ -357,7 +358,7 @@ public function fromRaw($expression, $bindings = []) /** * Creates a subquery and parse it. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @return array */ protected function createSub($query) @@ -467,7 +468,7 @@ public function distinct() /** * Set the table which the query is targeting. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Contracts\Database\Query\Expression|string $table + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|\Illuminate\Contracts\Database\Query\Expression|string $table * @param string|null $as * @return $this */ @@ -579,7 +580,7 @@ public function joinWhere($table, $first, $operator, $second, $type = 'inner') /** * Add a subquery join clause to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @param string $as * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator @@ -604,7 +605,7 @@ public function joinSub($query, $as, $first, $operator = null, $second = null, $ /** * Add a lateral join clause to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @param string $as * @param string $type * @return $this @@ -625,7 +626,7 @@ public function joinLateral($query, string $as, string $type = 'inner') /** * Add a lateral left join to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @param string $as * @return $this */ @@ -665,7 +666,7 @@ public function leftJoinWhere($table, $first, $operator, $second) /** * Add a subquery left join to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @param string $as * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator @@ -708,7 +709,7 @@ public function rightJoinWhere($table, $first, $operator, $second) /** * Add a subquery right join to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @param string $as * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator @@ -743,7 +744,7 @@ public function crossJoin($table, $first = null, $operator = null, $second = nul /** * Add a subquery cross join to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @param string $as * @return $this */ @@ -1719,7 +1720,7 @@ public function addNestedWhereQuery($query, $boolean = 'and') * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*> $callback * @param string $boolean * @return $this */ @@ -1748,7 +1749,7 @@ protected function whereSub($column, $operator, $callback, $boolean) /** * Add an exists clause to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*> $callback * @param string $boolean * @param bool $not * @return $this @@ -1772,7 +1773,7 @@ public function whereExists($callback, $boolean = 'and', $not = false) /** * Add an or exists clause to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*> $callback * @param bool $not * @return $this */ @@ -1784,7 +1785,7 @@ public function orWhereExists($callback, $not = false) /** * Add a where not exists clause to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*> $callback * @param string $boolean * @return $this */ @@ -1796,7 +1797,7 @@ public function whereNotExists($callback, $boolean = 'and') /** * Add a where not exists clause to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*> $callback * @return $this */ public function orWhereNotExists($callback) @@ -2491,7 +2492,7 @@ public function orHavingRaw($sql, array $bindings = []) /** * Add an "order by" clause to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Contracts\Database\Query\Expression|string $column + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|\Illuminate\Contracts\Database\Query\Expression|string $column * @param string $direction * @return $this * @@ -2524,7 +2525,7 @@ public function orderBy($column, $direction = 'asc') /** * Add a descending "order by" clause to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Contracts\Database\Query\Expression|string $column + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|\Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function orderByDesc($column) @@ -2744,7 +2745,7 @@ protected function removeExistingOrdersFor($column) /** * Add a union statement to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*> $query * @param bool $all * @return $this */ @@ -2764,7 +2765,7 @@ public function union($query, $all = false) /** * Add a union all statement to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*> $query * @return $this */ public function unionAll($query) @@ -3663,7 +3664,7 @@ public function insertGetId(array $values, $sequence = null) * Insert new records into the table using a subquery. * * @param array $columns - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @return int */ public function insertUsing(array $columns, $query) @@ -3682,7 +3683,7 @@ public function insertUsing(array $columns, $query) * Insert new records into the table using a subquery while ignoring errors. * * @param array $columns - * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder<*>|string $query * @return int */ public function insertOrIgnoreUsing(array $columns, $query) diff --git a/src/Illuminate/Notifications/DatabaseNotification.php b/src/Illuminate/Notifications/DatabaseNotification.php index 14bc9d659f97..fa4e36bc2b25 100644 --- a/src/Illuminate/Notifications/DatabaseNotification.php +++ b/src/Illuminate/Notifications/DatabaseNotification.php @@ -48,7 +48,7 @@ class DatabaseNotification extends Model /** * Get the notifiable entity that the notification belongs to. * - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this> */ public function notifiable() { @@ -102,8 +102,8 @@ public function unread() /** * Scope a query to only include read notifications. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ public function scopeRead(Builder $query) { @@ -113,8 +113,8 @@ public function scopeRead(Builder $query) /** * Scope a query to only include unread notifications. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder */ public function scopeUnread(Builder $query) { diff --git a/src/Illuminate/Notifications/HasDatabaseNotifications.php b/src/Illuminate/Notifications/HasDatabaseNotifications.php index 162d16cb9101..10e2386f55ba 100644 --- a/src/Illuminate/Notifications/HasDatabaseNotifications.php +++ b/src/Illuminate/Notifications/HasDatabaseNotifications.php @@ -7,7 +7,7 @@ trait HasDatabaseNotifications /** * Get the entity's notifications. * - * @return \Illuminate\Database\Eloquent\Relations\MorphMany + * @return \Illuminate\Database\Eloquent\Relations\MorphMany */ public function notifications() { diff --git a/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php b/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php index 1ca1d8310677..3b024c623394 100644 --- a/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php +++ b/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php @@ -112,9 +112,11 @@ public function restoreModel($value) /** * Get the query for model restoration. * - * @param \Illuminate\Database\Eloquent\Model $model + * @template TModel of \Illuminate\Database\Eloquent\Model + * + * @param TModel $model * @param array|int $ids - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder */ protected function getQueryForModelRestoration($model, $ids) { diff --git a/types/Database/Eloquent/Builder.php b/types/Database/Eloquent/Builder.php new file mode 100644 index 000000000000..a9849c17ffe8 --- /dev/null +++ b/types/Database/Eloquent/Builder.php @@ -0,0 +1,34 @@ + $query */ +function test(Builder $query): void +{ + assertType('Illuminate\Database\Eloquent\Builder', $query->where('id', 1)); + assertType('Illuminate\Database\Eloquent\Builder', $query->orWhere('name', 'John')); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereNot('status', 'active')); + assertType('Illuminate\Database\Eloquent\Builder', $query->with('relation')); + assertType('Illuminate\Database\Eloquent\Builder', $query->without('relation')); + assertType('Illuminate\Database\Eloquent\Builder', $query->withOnly(['relation'])); + + assertType('User|null', $query->first()); + assertType('Illuminate\Database\Eloquent\Collection', $query->get()); + assertType('Illuminate\Database\Eloquent\Collection|User|null', $query->find(1)); + assertType('Illuminate\Database\Eloquent\Collection', $query->findMany([1, 2, 3])); + assertType('Illuminate\Database\Eloquent\Collection|User', $query->findOrFail(1)); + assertType('User', $query->firstOrNew(['id' => 1])); + assertType('User', $query->firstOrCreate(['id' => 1])); + assertType('User', $query->create(['name' => 'John'])); + assertType('User', $query->forceCreate(['name' => 'John'])); + assertType('User', $query->updateOrCreate(['id' => 1], ['name' => 'John'])); + assertType('User', $query->firstOrFail()); + assertType('User', $query->sole()); + + assertType('Illuminate\Database\Query\Builder', $query->toBase()); + assertType('object|null', $query->toBase()->first()); +} diff --git a/types/Database/Eloquent/Model.php b/types/Database/Eloquent/Model.php index 3ca5dc05fd7e..bac0bd17135c 100644 --- a/types/Database/Eloquent/Model.php +++ b/types/Database/Eloquent/Model.php @@ -2,5 +2,6 @@ use function PHPStan\Testing\assertType; -$factory = User::factory(); -assertType('Illuminate\Database\Eloquent\Factories\Factory', $factory); +assertType('Illuminate\Database\Eloquent\Factories\Factory', User::factory()); + +assertType('Illuminate\Database\Eloquent\Builder', User::query()); diff --git a/types/Database/Eloquent/Relations.php b/types/Database/Eloquent/Relations.php new file mode 100644 index 000000000000..67eb186222e1 --- /dev/null +++ b/types/Database/Eloquent/Relations.php @@ -0,0 +1,264 @@ +', $user->address()); + assertType('Illuminate\Database\Eloquent\Relations\HasOne', $child->address()); + assertType('Illuminate\Types\Relations\Address|null', $user->address()->getResults()); + assertType('Illuminate\Database\Eloquent\Collection', $user->address()->get()); + + assertType('Illuminate\Database\Eloquent\Relations\HasMany', $user->posts()); + assertType('Illuminate\Database\Eloquent\Collection', $user->posts()->getResults()); + assertType('Illuminate\Database\Eloquent\Relations\HasOne', $user->latestPost()); + + assertType('Illuminate\Database\Eloquent\Relations\BelongsToMany', $user->roles()); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->getResults()); + + assertType('Illuminate\Database\Eloquent\Relations\HasOneThrough', $user->car()); + assertType('Illuminate\Types\Relations\Car|null', $user->car()->getResults()); + + assertType('Illuminate\Database\Eloquent\Relations\HasManyThrough', $user->parts()); + assertType('Illuminate\Database\Eloquent\Collection', $user->parts()->getResults()); + assertType('Illuminate\Database\Eloquent\Relations\HasOneThrough', $user->firstPart()); + + assertType('Illuminate\Database\Eloquent\Relations\BelongsTo', $post->user()); + assertType('Illuminate\Types\Relations\User|null', $post->user()->getResults()); + + assertType('Illuminate\Database\Eloquent\Relations\MorphOne', $post->image()); + assertType('Illuminate\Types\Relations\Image|null', $post->image()->getResults()); + + assertType('Illuminate\Database\Eloquent\Relations\MorphMany', $post->comments()); + assertType('Illuminate\Database\Eloquent\Collection', $post->comments()->getResults()); + assertType('Illuminate\Database\Eloquent\Relations\MorphOne', $post->latestComment()); + + assertType('Illuminate\Database\Eloquent\Relations\MorphTo', $comment->commentable()); + assertType('Illuminate\Database\Eloquent\Model|null', $comment->commentable()->getResults()); + + assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $post->tags()); + assertType('Illuminate\Database\Eloquent\Collection', $post->tags()->getResults()); +} + +class User extends Model +{ + /** @return HasOne */ + public function address(): HasOne + { + $hasOne = $this->hasOne(Address::class); + assertType('Illuminate\Database\Eloquent\Relations\HasOne', $hasOne); + + return $hasOne; + } + + /** @return HasMany */ + public function posts(): HasMany + { + $hasMany = $this->hasMany(Post::class); + assertType('Illuminate\Database\Eloquent\Relations\HasMany', $hasMany); + + return $hasMany; + } + + /** @return HasOne */ + public function latestPost(): HasOne + { + $post = $this->posts()->one(); + assertType('Illuminate\Database\Eloquent\Relations\HasOne', $post); + + return $post; + } + + /** @return BelongsToMany */ + public function roles(): BelongsToMany + { + $belongsToMany = $this->belongsToMany(Role::class); + assertType('Illuminate\Database\Eloquent\Relations\BelongsToMany', $belongsToMany); + + return $belongsToMany; + } + + /** @return HasOne */ + public function mechanic(): HasOne + { + return $this->hasOne(Mechanic::class); + } + + /** @return HasOneThrough */ + public function car(): HasOneThrough + { + $hasOneThrough = $this->hasOneThrough(Car::class, Mechanic::class); + assertType('Illuminate\Database\Eloquent\Relations\HasOneThrough', $hasOneThrough); + + /** @phpstan-ignore argument.templateType (unable to resolve template type from string) */ + $through = $this->through('mechanic'); + assertType( + 'Illuminate\Database\Eloquent\PendingHasThroughRelationship', + $through, + ); + assertType( + 'Illuminate\Database\Eloquent\Relations\HasManyThrough|Illuminate\Database\Eloquent\Relations\HasOneThrough', + /** @phpstan-ignore argument.templateType (unable to resolve template type from string) */ + $through->has('car'), + ); + + $through = $this->through($this->mechanic()); + assertType( + 'Illuminate\Database\Eloquent\PendingHasThroughRelationship', + $through, + ); + assertType( + 'Illuminate\Database\Eloquent\Relations\HasOneThrough', + $through->has(function ($mechanic) { + assertType('Illuminate\Types\Relations\Mechanic', $mechanic); + + return $mechanic->car(); + }), + ); + + return $hasOneThrough; + } + + /** @return HasManyThrough */ + public function parts(): HasManyThrough + { + $hasManyThrough = $this->hasManyThrough(Part::class, Mechanic::class); + assertType('Illuminate\Database\Eloquent\Relations\HasManyThrough', $hasManyThrough); + + assertType( + 'Illuminate\Database\Eloquent\Relations\HasManyThrough', + $this->through($this->mechanic())->has(fn ($mechanic) => $mechanic->parts()), + ); + + return $hasManyThrough; + } + + /** @return HasOneThrough */ + public function firstPart(): HasOneThrough + { + $part = $this->parts()->one(); + assertType('Illuminate\Database\Eloquent\Relations\HasOneThrough', $part); + + return $part; + } +} + +class Post extends Model +{ + /** @return BelongsTo */ + public function user(): BelongsTo + { + $belongsTo = $this->belongsTo(User::class); + assertType('Illuminate\Database\Eloquent\Relations\BelongsTo', $belongsTo); + + return $belongsTo; + } + + /** @return MorphOne */ + public function image(): MorphOne + { + $morphOne = $this->morphOne(Image::class, 'imageable'); + assertType('Illuminate\Database\Eloquent\Relations\MorphOne', $morphOne); + + return $morphOne; + } + + /** @return MorphMany */ + public function comments(): MorphMany + { + $morphMany = $this->morphMany(Comment::class, 'commentable'); + assertType('Illuminate\Database\Eloquent\Relations\MorphMany', $morphMany); + + return $morphMany; + } + + /** @return MorphOne */ + public function latestComment(): MorphOne + { + $comment = $this->comments()->one(); + assertType('Illuminate\Database\Eloquent\Relations\MorphOne', $comment); + + return $comment; + } + + /** @return MorphToMany */ + public function tags(): MorphToMany + { + $morphToMany = $this->morphedByMany(Tag::class, 'taggable'); + assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); + + return $morphToMany; + } +} + +class Comment extends Model +{ + /** @return MorphTo<\Illuminate\Database\Eloquent\Model, $this> */ + public function commentable(): MorphTo + { + $morphTo = $this->morphTo(); + assertType('Illuminate\Database\Eloquent\Relations\MorphTo', $morphTo); + + return $morphTo; + } +} + +class Tag extends Model +{ + /** @return MorphToMany */ + public function posts(): MorphToMany + { + $morphToMany = $this->morphToMany(Post::class, 'taggable'); + assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); + + return $morphToMany; + } +} + +class Mechanic extends Model +{ + /** @return HasOne */ + public function car(): HasOne + { + return $this->hasOne(Car::class); + } + + /** @return HasMany */ + public function parts(): HasMany + { + return $this->hasMany(Part::class); + } +} + +class ChildUser extends User +{ +} +class Address extends Model +{ +} +class Role extends Model +{ +} +class Car extends Model +{ +} +class Part extends Model +{ +} +class Image extends Model +{ +} From 4963b65d8e05df6bd9212013015cd75a9e3369b6 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 20 Jun 2024 10:47:58 +0200 Subject: [PATCH 2/4] Update src/Illuminate/Auth/EloquentUserProvider.php Co-authored-by: Jonas Staudenmeir --- src/Illuminate/Auth/EloquentUserProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Auth/EloquentUserProvider.php b/src/Illuminate/Auth/EloquentUserProvider.php index a1455abb64da..646a0c75ebf7 100755 --- a/src/Illuminate/Auth/EloquentUserProvider.php +++ b/src/Illuminate/Auth/EloquentUserProvider.php @@ -254,7 +254,7 @@ public function setModel($model) /** * Get the callback that modifies the query before retrieving users. * - * @param (\Closure(\Illuminate\Database\Eloquent\Builder<*>):mixed)|null + * @return (\Closure(\Illuminate\Database\Eloquent\Builder<*>):mixed)|null */ public function getQueryCallback() { From 7786955dd06018c456797fd5e6b5733b05ea6e2d Mon Sep 17 00:00:00 2001 From: Caleb White Date: Thu, 20 Jun 2024 14:11:38 -0500 Subject: [PATCH 3/4] test: add additional type tests --- .../Database/Concerns/BuildsQueries.php | 18 +- src/Illuminate/Database/Eloquent/Builder.php | 32 ++-- .../Eloquent/Concerns/HasRelationships.php | 6 +- .../Concerns/QueriesRelationships.php | 20 +-- src/Illuminate/Database/Eloquent/Model.php | 2 +- .../PendingHasThroughRelationship.php | 2 +- .../Eloquent/Relations/BelongsToMany.php | 37 ++-- .../Eloquent/Relations/HasOneOrMany.php | 2 +- .../Relations/HasOneOrManyThrough.php | 20 ++- .../Database/Eloquent/Relations/MorphTo.php | 2 +- types/Autoload.php | 6 + types/Database/Eloquent/Builder.php | 167 +++++++++++++++++- types/Database/Eloquent/Collection.php | 24 +-- types/Database/Eloquent/Model.php | 24 ++- types/Database/Eloquent/Relations.php | 70 +++++++- types/Database/Query/Builder.php | 57 ++++++ 16 files changed, 412 insertions(+), 77 deletions(-) create mode 100644 types/Database/Query/Builder.php diff --git a/src/Illuminate/Database/Concerns/BuildsQueries.php b/src/Illuminate/Database/Concerns/BuildsQueries.php index b37eb9c17004..5a6cf27b5a6e 100644 --- a/src/Illuminate/Database/Concerns/BuildsQueries.php +++ b/src/Illuminate/Database/Concerns/BuildsQueries.php @@ -32,7 +32,7 @@ trait BuildsQueries * Chunk the results of the query. * * @param int $count - * @param callable $callback + * @param callable(\Illuminate\Support\Collection, int): mixed $callback * @return bool */ public function chunk($count, callable $callback) @@ -71,9 +71,11 @@ public function chunk($count, callable $callback) /** * Run a map over each item while chunking. * - * @param callable $callback + * @template TReturn + * + * @param callable(TValue): TReturn $callback * @param int $count - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function chunkMap(callable $callback, $count = 1000) { @@ -91,7 +93,7 @@ public function chunkMap(callable $callback, $count = 1000) /** * Execute a callback over each item while chunking. * - * @param callable $callback + * @param callable(TValue, int): mixed $callback * @param int $count * @return bool * @@ -112,7 +114,7 @@ public function each(callable $callback, $count = 1000) * Chunk the results of a query by comparing IDs. * * @param int $count - * @param callable $callback + * @param callable(\Illuminate\Support\Collection, int): mixed $callback * @param string|null $column * @param string|null $alias * @return bool @@ -126,7 +128,7 @@ public function chunkById($count, callable $callback, $column = null, $alias = n * Chunk the results of a query by comparing IDs in descending order. * * @param int $count - * @param callable $callback + * @param callable(\Illuminate\Support\Collection, int): mixed $callback * @param string|null $column * @param string|null $alias * @return bool @@ -140,7 +142,7 @@ public function chunkByIdDesc($count, callable $callback, $column = null, $alias * Chunk the results of a query by comparing IDs in a given order. * * @param int $count - * @param callable $callback + * @param callable(\Illuminate\Support\Collection, int): mixed $callback * @param string|null $column * @param string|null $alias * @param bool $descending @@ -200,7 +202,7 @@ public function orderedChunkById($count, callable $callback, $column = null, $al /** * Execute a callback over each item while chunking by ID. * - * @param callable $callback + * @param callable(TValue, int): mixed $callback * @param int $count * @param string|null $column * @param string|null $alias diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 4f29ab1d4e2c..89ee41e19dc5 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -297,7 +297,7 @@ public function whereKeyNot($id) /** * Add a basic where clause to the query. * - * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column + * @param (\Closure(static): mixed)|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean @@ -319,7 +319,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' /** * Add a basic where clause to the query, and return the first result. * - * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column + * @param (\Closure(static): mixed)|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean @@ -333,7 +333,7 @@ public function firstWhere($column, $operator = null, $value = null, $boolean = /** * Add an "or where" clause to the query. * - * @param \Closure|array|string|\Illuminate\Contracts\Database\Query\Expression $column + * @param (\Closure(static): mixed)|array|string|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return $this @@ -350,7 +350,7 @@ public function orWhere($column, $operator = null, $value = null) /** * Add a basic "where not" clause to the query. * - * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column + * @param (\Closure(static): mixed)|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean @@ -364,7 +364,7 @@ public function whereNot($column, $operator = null, $value = null, $boolean = 'a /** * Add an "or where not" clause to the query. * - * @param \Closure|array|string|\Illuminate\Contracts\Database\Query\Expression $column + * @param (\Closure(static): mixed)|array|string|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return $this @@ -448,7 +448,7 @@ public function fromQuery($query, $bindings = []) * * @param mixed $id * @param array|string $columns - * @return TModel|\Illuminate\Database\Eloquent\Collection|null + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TModel|null) */ public function find($id, $columns = ['*']) { @@ -482,7 +482,7 @@ public function findMany($ids, $columns = ['*']) * * @param mixed $id * @param array|string $columns - * @return TModel|\Illuminate\Database\Eloquent\Collection + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TModel) * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ @@ -516,7 +516,7 @@ public function findOrFail($id, $columns = ['*']) * * @param mixed $id * @param array|string $columns - * @return TModel + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TModel) */ public function findOrNew($id, $columns = ['*']) { @@ -530,10 +530,16 @@ public function findOrNew($id, $columns = ['*']) /** * Find a model by its primary key or call a callback. * + * @template TFindOrValue + * * @param mixed $id * @param \Closure|array|string $columns - * @param \Closure|null $callback - * @return TModel|\Illuminate\Database\Eloquent\Collection|mixed + * @param (\Closure(): TFindOrValue)|null $callback + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : ($callback is null ? TModel|null : TModel|TFindOrValue) + * ) */ public function findOr($id, $columns = ['*'], ?Closure $callback = null) { @@ -634,9 +640,11 @@ public function firstOrFail($columns = ['*']) /** * Execute the query and get the first result or call a callback. * + * @template TFirstOrValue + * * @param \Closure|array|string $columns - * @param \Closure|null $callback - * @return TModel|mixed + * @param (\Closure(): TFirstOrValue)|null $callback + * @return ($callback is null ? TModel|null : TModel|TFirstOrValue) */ public function firstOr($columns = ['*'], ?Closure $callback = null) { diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php index bab460f50054..6daf7e0663f3 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php @@ -391,7 +391,11 @@ protected function guessBelongsToRelation() * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model * * @param string|\Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne $relationship - * @return \Illuminate\Database\Eloquent\PendingHasThroughRelationship + * @return ( + * $relationship is string + * ? \Illuminate\Database\Eloquent\PendingHasThroughRelationship<\Illuminate\Database\Eloquent\Model, $this> + * : \Illuminate\Database\Eloquent\PendingHasThroughRelationship + * ) */ public function through($relationship) { diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index 49091f2b601c..eaccfe9802bd 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -105,7 +105,7 @@ protected function hasNested($relations, $operator = '>=', $count = 1, $boolean /** * Add a relationship count / exists condition to the query with an "or". * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param string $operator * @param int $count * @return $this @@ -118,7 +118,7 @@ public function orHas($relation, $operator = '>=', $count = 1) /** * Add a relationship count / exists condition to the query. * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param string $boolean * @param \Closure|null $callback * @return $this @@ -131,7 +131,7 @@ public function doesntHave($relation, $boolean = 'and', ?Closure $callback = nul /** * Add a relationship count / exists condition to the query with an "or". * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @return $this */ public function orDoesntHave($relation) @@ -142,7 +142,7 @@ public function orDoesntHave($relation) /** * Add a relationship count / exists condition to the query with where clauses. * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param \Closure|null $callback * @param string $operator * @param int $count @@ -158,7 +158,7 @@ public function whereHas($relation, ?Closure $callback = null, $operator = '>=', * * Also load the relationship with same condition. * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param \Closure|null $callback * @param string $operator * @param int $count @@ -173,7 +173,7 @@ public function withWhereHas($relation, ?Closure $callback = null, $operator = ' /** * Add a relationship count / exists condition to the query with where clauses and an "or". * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param \Closure|null $callback * @param string $operator * @param int $count @@ -187,7 +187,7 @@ public function orWhereHas($relation, ?Closure $callback = null, $operator = '>= /** * Add a relationship count / exists condition to the query with where clauses. * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param \Closure|null $callback * @return $this */ @@ -199,7 +199,7 @@ public function whereDoesntHave($relation, ?Closure $callback = null) /** * Add a relationship count / exists condition to the query with where clauses and an "or". * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param \Closure|null $callback * @return $this */ @@ -381,7 +381,7 @@ public function orWhereDoesntHaveMorph($relation, $types, ?Closure $callback = n /** * Add a basic where clause to a relationship query. * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value @@ -401,7 +401,7 @@ public function whereRelation($relation, $column, $operator = null, $value = nul /** * Add an "or where" clause to a relationship query. * - * @param string $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 51c7f347422f..3b879cea897c 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -1564,7 +1564,7 @@ public function newQueryForRestoration($ids) * Create a new Eloquent query builder for the model. * * @param \Illuminate\Database\Query\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder<*> */ public function newEloquentBuilder($query) { diff --git a/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php b/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php index a768afa44c1d..478d3d3a8de5 100644 --- a/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php +++ b/src/Illuminate/Database/Eloquent/PendingHasThroughRelationship.php @@ -47,7 +47,7 @@ public function __construct($rootModel, $localRelationship) * @param string|(callable(TIntermediateModel): (\Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Eloquent\Relations\HasMany)) $callback * @return ( * $callback is string - * ? \Illuminate\Database\Eloquent\Relations\HasManyThrough|\Illuminate\Database\Eloquent\Relations\HasOneThrough + * ? \Illuminate\Database\Eloquent\Relations\HasManyThrough<\Illuminate\Database\Eloquent\Model, TIntermediateModel, TDeclaringModel>|\Illuminate\Database\Eloquent\Relations\HasOneThrough<\Illuminate\Database\Eloquent\Model, TIntermediateModel, TDeclaringModel> * : ( * $callback is callable(TIntermediateModel): \Illuminate\Database\Eloquent\Relations\HasOne * ? \Illuminate\Database\Eloquent\Relations\HasOneThrough diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 1593d84c0dc0..e2067b4cd864 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -287,7 +287,7 @@ public function match(array $models, Collection $results, $relation) * Build model dictionary keyed by the relation's foreign key. * * @param \Illuminate\Database\Eloquent\Collection $results - * @return array + * @return array> */ protected function buildDictionary(Collection $results) { @@ -571,7 +571,7 @@ public function orderByPivot($column, $direction = 'asc') * * @param mixed $id * @param array $columns - * @return \Illuminate\Support\Collection|TRelatedModel + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel) */ public function findOrNew($id, $columns = ['*']) { @@ -675,7 +675,7 @@ public function updateOrCreate(array $attributes, array $values = [], array $joi * * @param mixed $id * @param array $columns - * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection|null + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel|null) */ public function find($id, $columns = ['*']) { @@ -713,7 +713,7 @@ public function findMany($ids, $columns = ['*']) * * @param mixed $id * @param array $columns - * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel) * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ @@ -737,10 +737,16 @@ public function findOrFail($id, $columns = ['*']) /** * Find a related model by its primary key or call a callback. * + * @template TFindOrValue + * * @param mixed $id * @param \Closure|array $columns - * @param \Closure|null $callback - * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection|mixed + * @param (\Closure(): TFindOrValue)|null $callback + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : ($callback is null ? TRelatedModel|null : TRelatedModel|TFindOrValue) + * ) */ public function findOr($id, $columns = ['*'], ?Closure $callback = null) { @@ -812,9 +818,11 @@ public function firstOrFail($columns = ['*']) /** * Execute the query and get the first result or call a callback. * + * @template TFirstOrValue + * * @param \Closure|array $columns - * @param \Closure|null $callback - * @return TRelatedModel|mixed + * @param (\Closure(): TFirstOrValue)|null $callback + * @return ($callback is null ? TRelatedModel|null : TRelatedModel|TFirstOrValue) */ public function firstOr($columns = ['*'], ?Closure $callback = null) { @@ -1292,10 +1300,11 @@ public function saveQuietly(Model $model, array $pivotAttributes = [], $touch = /** * Save an array of new models and attach them to the parent model. * - * @param \Illuminate\Support\Collection|array $models + * @template TContainer of \Illuminate\Support\Collection|array + * + * @param TContainer $models * @param array $pivotAttributes - * @return array< - * @return \Illuminate\Support\Collection|array + * @return TContainer */ public function saveMany($models, array $pivotAttributes = []) { @@ -1311,9 +1320,11 @@ public function saveMany($models, array $pivotAttributes = []) /** * Save an array of new models without raising any events and attach them to the parent model. * - * @param \Illuminate\Support\Collection|array $models + * @template TContainer of \Illuminate\Support\Collection|array + * + * @param TContainer $models * @param array $pivotAttributes - * @return \Illuminate\Support\Collection|array + * @return TContainer */ public function saveManyQuietly($models, array $pivotAttributes = []) { diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index 2a772b39822d..d589f942739c 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -197,7 +197,7 @@ protected function buildDictionary(Collection $results) * * @param mixed $id * @param array $columns - * @return \Illuminate\Support\Collection|TRelatedModel + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel) */ public function findOrNew($id, $columns = ['*']) { diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php index 9f1adad9d131..c0d87dec4a3e 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php @@ -303,9 +303,11 @@ public function firstOrFail($columns = ['*']) /** * Execute the query and get the first result or call a callback. * + * @template TFirstOrValue + * * @param \Closure|array $columns - * @param \Closure|null $callback - * @return TRelatedModel|mixed + * @param (\Closure(): TFirstOrValue)|null $callback + * @return ($callback is null ? TRelatedModel|null : TRelatedModel|TFirstOrValue) */ public function firstOr($columns = ['*'], ?Closure $callback = null) { @@ -327,7 +329,7 @@ public function firstOr($columns = ['*'], ?Closure $callback = null) * * @param mixed $id * @param array $columns - * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection|null + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel|null) */ public function find($id, $columns = ['*']) { @@ -365,7 +367,7 @@ public function findMany($ids, $columns = ['*']) * * @param mixed $id * @param array $columns - * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection + * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel) * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ @@ -389,10 +391,16 @@ public function findOrFail($id, $columns = ['*']) /** * Find a related model by its primary key or call a callback. * + * @template TFindOrValue + * * @param mixed $id * @param \Closure|array $columns - * @param \Closure|null $callback - * @return TRelatedModel|\Illuminate\Database\Eloquent\Collection|mixed + * @param (\Closure(): TFindOrValue)|null $callback + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : ($callback is null ? TRelatedModel|null : TRelatedModel|TFindOrValue) + * ) */ public function findOr($id, $columns = ['*'], ?Closure $callback = null) { diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php index a1bfed37572c..d9c60aa6affa 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php @@ -245,7 +245,7 @@ public function associate($model) /** * Dissociate previously associated model from the given parent. * - * @return TDDeclaringModel + * @return TDeclaringModel */ public function dissociate() { diff --git a/types/Autoload.php b/types/Autoload.php index 98a3c26a9eaa..23e08d6064f4 100644 --- a/types/Autoload.php +++ b/types/Autoload.php @@ -1,11 +1,17 @@ $query */ -function test(Builder $query): void +function test(Builder $query, Post $post, ChildPost $childPost, Comment $comment): void { assertType('Illuminate\Database\Eloquent\Builder', $query->where('id', 1)); assertType('Illuminate\Database\Eloquent\Builder', $query->orWhere('name', 'John')); @@ -15,20 +19,169 @@ function test(Builder $query): void assertType('Illuminate\Database\Eloquent\Builder', $query->with('relation')); assertType('Illuminate\Database\Eloquent\Builder', $query->without('relation')); assertType('Illuminate\Database\Eloquent\Builder', $query->withOnly(['relation'])); - - assertType('User|null', $query->first()); + assertType('array', $query->getModels()); + assertType('array', $query->eagerLoadRelations([])); assertType('Illuminate\Database\Eloquent\Collection', $query->get()); - assertType('Illuminate\Database\Eloquent\Collection|User|null', $query->find(1)); + assertType('Illuminate\Database\Eloquent\Collection', $query->hydrate([])); + assertType('Illuminate\Database\Eloquent\Collection', $query->fromQuery('foo', [])); assertType('Illuminate\Database\Eloquent\Collection', $query->findMany([1, 2, 3])); - assertType('Illuminate\Database\Eloquent\Collection|User', $query->findOrFail(1)); + assertType('Illuminate\Database\Eloquent\Collection', $query->findOrFail([1])); + assertType('Illuminate\Database\Eloquent\Collection', $query->findOrNew([1])); + assertType('Illuminate\Database\Eloquent\Collection', $query->find([1])); + assertType('Illuminate\Database\Eloquent\Collection', $query->findOr([1], callback: fn () => 42)); + assertType('User', $query->findOrFail(1)); + assertType('User|null', $query->find(1)); + assertType('User|null', $query->findOr(1)); + assertType('int|User', $query->findOr(1, callback: fn () => 42)); + assertType('User|null', $query->first()); + assertType('User|null', $query->firstOr()); + assertType('int|User', $query->firstOr(callback: fn () => 42)); assertType('User', $query->firstOrNew(['id' => 1])); + assertType('User', $query->findOrNew(1)); assertType('User', $query->firstOrCreate(['id' => 1])); assertType('User', $query->create(['name' => 'John'])); assertType('User', $query->forceCreate(['name' => 'John'])); + assertType('User', $query->forceCreateQuietly(['name' => 'John'])); + assertType('User', $query->getModel()); + assertType('User', $query->make(['name' => 'John'])); + assertType('User', $query->forceCreate(['name' => 'John'])); assertType('User', $query->updateOrCreate(['id' => 1], ['name' => 'John'])); assertType('User', $query->firstOrFail()); assertType('User', $query->sole()); + assertType('Illuminate\Support\LazyCollection', $query->cursor()); + assertType('Illuminate\Support\Collection', $query->pluck('foo')); + assertType('Illuminate\Database\Eloquent\Relations\Relation', $query->getRelation('foo')); + assertType('Illuminate\Database\Eloquent\Builder', $query->setModel(new Post())); - assertType('Illuminate\Database\Query\Builder', $query->toBase()); - assertType('object|null', $query->toBase()->first()); + assertType('Illuminate\Database\Eloquent\Builder', $query->has('foo')); + assertType('Illuminate\Database\Eloquent\Builder', $query->has($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->orHas($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->doesntHave($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->orDoesntHave($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereHas($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->withWhereHas($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->orWhereHas($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereDoesntHave($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->orWhereDoesntHave($post->users())); + assertType('Illuminate\Database\Eloquent\Builder', $query->hasMorph($post->taggable(), 'taggable')); + assertType('Illuminate\Database\Eloquent\Builder', $query->orHasMorph($post->taggable(), 'taggable')); + assertType('Illuminate\Database\Eloquent\Builder', $query->doesntHaveMorph($post->taggable(), 'taggable')); + assertType('Illuminate\Database\Eloquent\Builder', $query->orDoesntHaveMorph($post->taggable(), 'taggable')); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereHasMorph($post->taggable(), 'taggable')); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereDoesntHaveMorph($post->taggable(), 'taggable')); + assertType('Illuminate\Database\Eloquent\Builder', $query->orWhereDoesntHaveMorph($post->taggable(), 'taggable')); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereRelation($post->users(), 'foo')); + assertType('Illuminate\Database\Eloquent\Builder', $query->orWhereRelation($post->users(), 'foo')); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereMorphRelation($post->taggable(), 'taggable', 'foo')); + assertType('Illuminate\Database\Eloquent\Builder', $query->orWhereMorphRelation($post->taggable(), 'taggable', 'foo')); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereMorphedTo($post->taggable(), new Post())); + assertType('Illuminate\Database\Eloquent\Builder', $query->whereNotMorphedTo($post->taggable(), new Post())); + assertType('Illuminate\Database\Eloquent\Builder', $query->orWhereMorphedTo($post->taggable(), new Post())); + assertType('Illuminate\Database\Eloquent\Builder', $query->orWhereNotMorphedTo($post->taggable(), new Post())); + + $query->chunk(1, function ($users, $page) { + assertType('Illuminate\Support\Collection', $users); + assertType('int', $page); + }); + $query->chunkById(1, function ($users, $page) { + assertType('Illuminate\Support\Collection', $users); + assertType('int', $page); + }); + $query->chunkMap(function ($users) { + assertType('User', $users); + }); + $query->chunkByIdDesc(1, function ($users, $page) { + assertType('Illuminate\Support\Collection', $users); + assertType('int', $page); + }); + $query->each(function ($users, $page) { + assertType('User', $users); + assertType('int', $page); + }); + $query->eachById(function ($users, $page) { + assertType('User', $users); + assertType('int', $page); + }); + + assertType('Illuminate\Types\Builder\CommonBuilder', $post->query()); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->query()->where('foo', 'bar')); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->query()->foo()); + assertType('Illuminate\Types\Builder\Post', $post->query()->create(['name' => 'John'])); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->query()); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->query()->where('foo', 'bar')); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->query()->foo()); + assertType('Illuminate\Types\Builder\ChildPost', $childPost->query()->create(['name' => 'John'])); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query()); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query()->where('foo', 'bar')); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query()->foo()); + assertType('Illuminate\Types\Builder\Comment', $comment->query()->create(['name' => 'John'])); +} + +class Post extends Model +{ + /** @return HasMany */ + public function users(): HasMany + { + return $this->hasMany(User::class); + } + + /** @return MorphTo<\Illuminate\Database\Eloquent\Model, $this> */ + public function taggable(): MorphTo + { + return $this->morphTo(); + } + + /** @return CommonBuilder */ + public static function query(): CommonBuilder + { + /** @var CommonBuilder */ + return parent::query(); + } + + /** + * @param \Illuminate\Database\Query\Builder $query + * @return CommonBuilder<*> + */ + public function newEloquentBuilder($query): CommonBuilder + { + return new CommonBuilder($query); + } +} + +class ChildPost extends Post +{ +} + +class Comment extends Model +{ + public static function query(): CommentBuilder + { + /** @var CommentBuilder */ + return parent::query(); + } + + /** @param \Illuminate\Database\Query\Builder $query */ + public function newEloquentBuilder($query): CommentBuilder + { + return new CommentBuilder($query); + } +} + +/** + * @template TModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Builder + */ +class CommonBuilder extends Builder +{ + /** @return $this */ + public function foo(): static + { + return $this->where('foo', 'bar'); + } +} + +/** @extends CommonBuilder */ +class CommentBuilder extends CommonBuilder +{ } diff --git a/types/Database/Eloquent/Collection.php b/types/Database/Eloquent/Collection.php index 1064beb7abf9..4edcce5db50f 100644 --- a/types/Database/Eloquent/Collection.php +++ b/types/Database/Eloquent/Collection.php @@ -11,66 +11,66 @@ assertType('Illuminate\Database\Eloquent\Collection', $collection->load('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->load(['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->load(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string'], 'string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }], 'string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount(['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists(['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing(['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing(['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorph('string', ['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorph('string', ['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorphCount('string', ['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorphCount('string', ['string' => function ($query) { - // assertType('\Illuminate\Database\Query\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Builder', $query); }])); assertType('bool', $collection->contains(function ($user) { @@ -179,3 +179,5 @@ assertType('array', $collection->getQueueableIds()); assertType('array', $collection->getQueueableRelations()); + +assertType('Illuminate\Database\Eloquent\Builder', $collection->toQuery()); diff --git a/types/Database/Eloquent/Model.php b/types/Database/Eloquent/Model.php index bac0bd17135c..7f8ec011b479 100644 --- a/types/Database/Eloquent/Model.php +++ b/types/Database/Eloquent/Model.php @@ -1,7 +1,27 @@ ', User::factory()); +function test(User $user): void +{ + assertType('Illuminate\Database\Eloquent\Factories\Factory', User::factory()); + assertType('Illuminate\Database\Eloquent\Builder', User::query()); + assertType('Illuminate\Database\Eloquent\Builder', $user->newQuery()); + assertType('Illuminate\Database\Eloquent\Builder', $user->withTrashed()); + assertType('Illuminate\Database\Eloquent\Builder', $user->onlyTrashed()); + assertType('Illuminate\Database\Eloquent\Builder', $user->withoutTrashed()); + assertType('Illuminate\Database\Eloquent\Builder', $user->prunable()); + assertType('Illuminate\Database\Eloquent\Relations\MorphMany', $user->notifications()); + + assertType('Illuminate\Database\Eloquent\Collection', $user->newCollection([new User()])); + assertType('Illuminate\Database\Eloquent\Collection', $user->newCollection(['foo' => new Post()])); +} -assertType('Illuminate\Database\Eloquent\Builder', User::query()); +class Post extends Model +{ +} diff --git a/types/Database/Eloquent/Relations.php b/types/Database/Eloquent/Relations.php index 67eb186222e1..9c109a129bf0 100644 --- a/types/Database/Eloquent/Relations.php +++ b/types/Database/Eloquent/Relations.php @@ -19,19 +19,74 @@ function test(User $user, Post $post, Comment $comment, ChildUser $child): void { assertType('Illuminate\Database\Eloquent\Relations\HasOne', $user->address()); - assertType('Illuminate\Database\Eloquent\Relations\HasOne', $child->address()); assertType('Illuminate\Types\Relations\Address|null', $user->address()->getResults()); assertType('Illuminate\Database\Eloquent\Collection', $user->address()->get()); + assertType('Illuminate\Types\Relations\Address', $user->address()->make()); + assertType('Illuminate\Types\Relations\Address', $user->address()->create()); + assertType('Illuminate\Database\Eloquent\Relations\HasOne', $child->address()); + assertType('Illuminate\Types\Relations\Address', $child->address()->make()); + assertType('Illuminate\Types\Relations\Address', $child->address()->create([])); + assertType('Illuminate\Types\Relations\Address', $child->address()->getRelated()); + assertType('Illuminate\Types\Relations\ChildUser', $child->address()->getParent()); assertType('Illuminate\Database\Eloquent\Relations\HasMany', $user->posts()); assertType('Illuminate\Database\Eloquent\Collection', $user->posts()->getResults()); + assertType('Illuminate\Database\Eloquent\Collection', $user->posts()->makeMany([])); + assertType('Illuminate\Database\Eloquent\Collection', $user->posts()->createMany([])); + assertType('Illuminate\Database\Eloquent\Collection', $user->posts()->createManyQuietly([])); assertType('Illuminate\Database\Eloquent\Relations\HasOne', $user->latestPost()); + assertType('Illuminate\Types\Relations\Post', $user->posts()->make()); + assertType('Illuminate\Types\Relations\Post', $user->posts()->create()); + 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('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('Illuminate\Types\Relations\Role|null', $user->roles()->findOr(1)); + assertType('Illuminate\Types\Relations\Role|int', $user->roles()->findOr(1, callback: fn () => 42)); + assertType('Illuminate\Types\Relations\Role|null', $user->roles()->first()); + assertType('Illuminate\Types\Relations\Role|null', $user->roles()->firstOr()); + assertType('Illuminate\Types\Relations\Role|int', $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())); + $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\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()); + assertType('Illuminate\Database\Eloquent\Collection', $user->car()->find([1])); + assertType('Illuminate\Database\Eloquent\Collection', $user->car()->findOr([1], callback: fn () => 42)); + assertType('Illuminate\Types\Relations\Car|null', $user->car()->find(1)); + assertType('Illuminate\Types\Relations\Car|null', $user->car()->findOr(1)); + assertType('Illuminate\Types\Relations\Car|int', $user->car()->findOr(1, callback: fn () => 42)); + assertType('Illuminate\Types\Relations\Car|null', $user->car()->first()); + assertType('Illuminate\Types\Relations\Car|null', $user->car()->firstOr()); + assertType('Illuminate\Types\Relations\Car|int', $user->car()->firstOr(callback: fn () => 42)); + assertType('Illuminate\Support\LazyCollection', $user->car()->lazy()); + assertType('Illuminate\Support\LazyCollection', $user->car()->lazyById()); + assertType('Illuminate\Support\LazyCollection', $user->car()->cursor()); assertType('Illuminate\Database\Eloquent\Relations\HasManyThrough', $user->parts()); assertType('Illuminate\Database\Eloquent\Collection', $user->parts()->getResults()); @@ -39,9 +94,16 @@ function test(User $user, Post $post, Comment $comment, ChildUser $child): void assertType('Illuminate\Database\Eloquent\Relations\BelongsTo', $post->user()); assertType('Illuminate\Types\Relations\User|null', $post->user()->getResults()); + assertType('Illuminate\Types\Relations\User', $post->user()->make()); + assertType('Illuminate\Types\Relations\User', $post->user()->create()); + assertType('Illuminate\Types\Relations\Post', $post->user()->associate(new User())); + assertType('Illuminate\Types\Relations\Post', $post->user()->dissociate()); + assertType('Illuminate\Types\Relations\Post', $post->user()->disassociate()); + assertType('Illuminate\Types\Relations\Post', $post->user()->getChild()); assertType('Illuminate\Database\Eloquent\Relations\MorphOne', $post->image()); assertType('Illuminate\Types\Relations\Image|null', $post->image()->getResults()); + assertType('Illuminate\Types\Relations\Image', $post->image()->forceCreate([])); assertType('Illuminate\Database\Eloquent\Relations\MorphMany', $post->comments()); assertType('Illuminate\Database\Eloquent\Collection', $post->comments()->getResults()); @@ -49,6 +111,10 @@ function test(User $user, Post $post, Comment $comment, ChildUser $child): void assertType('Illuminate\Database\Eloquent\Relations\MorphTo', $comment->commentable()); assertType('Illuminate\Database\Eloquent\Model|null', $comment->commentable()->getResults()); + assertType('Illuminate\Database\Eloquent\Collection', $comment->commentable()->getEager()); + assertType('Illuminate\Database\Eloquent\Model', $comment->commentable()->createModelByType('foo')); + 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()); @@ -104,7 +170,6 @@ public function car(): HasOneThrough $hasOneThrough = $this->hasOneThrough(Car::class, Mechanic::class); assertType('Illuminate\Database\Eloquent\Relations\HasOneThrough', $hasOneThrough); - /** @phpstan-ignore argument.templateType (unable to resolve template type from string) */ $through = $this->through('mechanic'); assertType( 'Illuminate\Database\Eloquent\PendingHasThroughRelationship', @@ -112,7 +177,6 @@ public function car(): HasOneThrough ); assertType( 'Illuminate\Database\Eloquent\Relations\HasManyThrough|Illuminate\Database\Eloquent\Relations\HasOneThrough', - /** @phpstan-ignore argument.templateType (unable to resolve template type from string) */ $through->has('car'), ); diff --git a/types/Database/Query/Builder.php b/types/Database/Query/Builder.php new file mode 100644 index 000000000000..09a8f441bb8d --- /dev/null +++ b/types/Database/Query/Builder.php @@ -0,0 +1,57 @@ + $userQuery */ +function test(Builder $query, EloquentBuilder $userQuery): void +{ + assertType('object|null', $query->first()); + assertType('Illuminate\Database\Query\Builder', $query->selectSub($userQuery, 'alias')); + assertType('Illuminate\Database\Query\Builder', $query->fromSub($userQuery, 'alias')); + assertType('Illuminate\Database\Query\Builder', $query->from($userQuery, 'alias')); + assertType('Illuminate\Database\Query\Builder', $query->joinSub($userQuery, 'alias', 'foo')); + assertType('Illuminate\Database\Query\Builder', $query->joinLateral($userQuery, 'alias')); + assertType('Illuminate\Database\Query\Builder', $query->leftJoinLateral($userQuery, 'alias')); + assertType('Illuminate\Database\Query\Builder', $query->leftJoinSub($userQuery, 'alias', 'foo')); + assertType('Illuminate\Database\Query\Builder', $query->rightJoinSub($userQuery, 'alias', 'foo')); + assertType('Illuminate\Database\Query\Builder', $query->crossJoinSub($userQuery, 'alias')); + assertType('Illuminate\Database\Query\Builder', $query->whereExists($userQuery)); + assertType('Illuminate\Database\Query\Builder', $query->orWhereExists($userQuery)); + assertType('Illuminate\Database\Query\Builder', $query->whereNotExists($userQuery)); + assertType('Illuminate\Database\Query\Builder', $query->orWhereNotExists($userQuery)); + assertType('Illuminate\Database\Query\Builder', $query->orderBy($userQuery)); + assertType('Illuminate\Database\Query\Builder', $query->orderByDesc($userQuery)); + assertType('Illuminate\Database\Query\Builder', $query->union($userQuery)); + assertType('Illuminate\Database\Query\Builder', $query->unionAll($userQuery)); + assertType('int', $query->insertUsing([], $userQuery)); + assertType('int', $query->insertOrIgnoreUsing([], $userQuery)); + + $query->chunk(1, function ($users, $page) { + assertType('Illuminate\Support\Collection', $users); + assertType('int', $page); + }); + $query->chunkById(1, function ($users, $page) { + assertType('Illuminate\Support\Collection', $users); + assertType('int', $page); + }); + $query->chunkMap(function ($users) { + assertType('object', $users); + }); + $query->chunkByIdDesc(1, function ($users, $page) { + assertType('Illuminate\Support\Collection', $users); + assertType('int', $page); + }); + $query->each(function ($users, $page) { + assertType('object', $users); + assertType('int', $page); + }); + $query->eachById(function ($users, $page) { + assertType('object', $users); + assertType('int', $page); + }); +} From 76a3a0dc5a7343be0cf66b253a85f1c003962f0e Mon Sep 17 00:00:00 2001 From: Caleb White Date: Thu, 20 Jun 2024 15:03:24 -0500 Subject: [PATCH 4/4] feat: create HasBuilder trait --- .../Database/Eloquent/HasBuilder.php | 124 ++++++++++++++++++ src/Illuminate/Database/Eloquent/Model.php | 9 +- types/Database/Eloquent/Builder.php | 101 ++++++++------ 3 files changed, 193 insertions(+), 41 deletions(-) create mode 100644 src/Illuminate/Database/Eloquent/HasBuilder.php diff --git a/src/Illuminate/Database/Eloquent/HasBuilder.php b/src/Illuminate/Database/Eloquent/HasBuilder.php new file mode 100644 index 000000000000..9431bb46f736 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/HasBuilder.php @@ -0,0 +1,124 @@ +> + */ + protected static string $builder = Builder::class; + /** * The name of the "created at" column. * @@ -1568,7 +1575,7 @@ public function newQueryForRestoration($ids) */ public function newEloquentBuilder($query) { - return new Builder($query); + return new static::$builder($query); } /** diff --git a/types/Database/Eloquent/Builder.php b/types/Database/Eloquent/Builder.php index ea7569ce85c2..d5678898005f 100644 --- a/types/Database/Eloquent/Builder.php +++ b/types/Database/Eloquent/Builder.php @@ -3,16 +3,23 @@ namespace Illuminate\Types\Builder; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\HasBuilder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Database\Query\Builder as QueryBuilder; use User; use function PHPStan\Testing\assertType; /** @param \Illuminate\Database\Eloquent\Builder<\User> $query */ -function test(Builder $query, Post $post, ChildPost $childPost, Comment $comment): void -{ +function test( + Builder $query, + Post $post, + ChildPost $childPost, + Comment $comment, + QueryBuilder $queryBuilder +): void { assertType('Illuminate\Database\Eloquent\Builder', $query->where('id', 1)); assertType('Illuminate\Database\Eloquent\Builder', $query->orWhere('name', 'John')); assertType('Illuminate\Database\Eloquent\Builder', $query->whereNot('status', 'active')); @@ -103,22 +110,59 @@ function test(Builder $query, Post $post, ChildPost $childPost, Comment $comment assertType('int', $page); }); - assertType('Illuminate\Types\Builder\CommonBuilder', $post->query()); - assertType('Illuminate\Types\Builder\CommonBuilder', $post->query()->where('foo', 'bar')); - assertType('Illuminate\Types\Builder\CommonBuilder', $post->query()->foo()); - assertType('Illuminate\Types\Builder\Post', $post->query()->create(['name' => 'John'])); - assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->query()); - assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->query()->where('foo', 'bar')); - assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->query()->foo()); - assertType('Illuminate\Types\Builder\ChildPost', $childPost->query()->create(['name' => 'John'])); - assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query()); - assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query()->where('foo', 'bar')); - assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query()->foo()); - assertType('Illuminate\Types\Builder\Comment', $comment->query()->create(['name' => 'John'])); + assertType('Illuminate\Types\Builder\CommonBuilder', Post::query()); + assertType('Illuminate\Types\Builder\CommonBuilder', Post::on()); + assertType('Illuminate\Types\Builder\CommonBuilder', Post::onWriteConnection()); + assertType('Illuminate\Types\Builder\CommonBuilder', Post::with([])); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newQuery()); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newEloquentBuilder($queryBuilder)); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newModelQuery()); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newQueryWithoutRelationships()); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newQueryWithoutScopes()); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newQueryWithoutScope('foo')); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newQueryForRestoration(1)); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newQuery()->where('foo', 'bar')); + assertType('Illuminate\Types\Builder\CommonBuilder', $post->newQuery()->foo()); + assertType('Illuminate\Types\Builder\Post', $post->newQuery()->create(['name' => 'John'])); + + assertType('Illuminate\Types\Builder\CommonBuilder', ChildPost::query()); + assertType('Illuminate\Types\Builder\CommonBuilder', ChildPost::on()); + assertType('Illuminate\Types\Builder\CommonBuilder', ChildPost::onWriteConnection()); + assertType('Illuminate\Types\Builder\CommonBuilder', ChildPost::with([])); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newQuery()); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newEloquentBuilder($queryBuilder)); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newModelQuery()); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newQueryWithoutRelationships()); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newQueryWithoutScopes()); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newQueryWithoutScope('foo')); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newQueryForRestoration(1)); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newQuery()->where('foo', 'bar')); + assertType('Illuminate\Types\Builder\CommonBuilder', $childPost->newQuery()->foo()); + assertType('Illuminate\Types\Builder\ChildPost', $childPost->newQuery()->create(['name' => 'John'])); + + assertType('Illuminate\Types\Builder\CommentBuilder', Comment::query()); + assertType('Illuminate\Types\Builder\CommentBuilder', Comment::on()); + assertType('Illuminate\Types\Builder\CommentBuilder', Comment::onWriteConnection()); + assertType('Illuminate\Types\Builder\CommentBuilder', Comment::with([])); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQuery()); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newEloquentBuilder($queryBuilder)); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newModelQuery()); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQueryWithoutRelationships()); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQueryWithoutScopes()); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQueryWithoutScope('foo')); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQueryForRestoration(1)); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQuery()->where('foo', 'bar')); + assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQuery()->foo()); + assertType('Illuminate\Types\Builder\Comment', $comment->newQuery()->create(['name' => 'John'])); } class Post extends Model { + /** @use HasBuilder> */ + use HasBuilder; + + protected static string $builder = CommonBuilder::class; + /** @return HasMany */ public function users(): HasMany { @@ -130,22 +174,6 @@ public function taggable(): MorphTo { return $this->morphTo(); } - - /** @return CommonBuilder */ - public static function query(): CommonBuilder - { - /** @var CommonBuilder */ - return parent::query(); - } - - /** - * @param \Illuminate\Database\Query\Builder $query - * @return CommonBuilder<*> - */ - public function newEloquentBuilder($query): CommonBuilder - { - return new CommonBuilder($query); - } } class ChildPost extends Post @@ -154,17 +182,10 @@ class ChildPost extends Post class Comment extends Model { - public static function query(): CommentBuilder - { - /** @var CommentBuilder */ - return parent::query(); - } + /** @use HasBuilder */ + use HasBuilder; - /** @param \Illuminate\Database\Query\Builder $query */ - public function newEloquentBuilder($query): CommentBuilder - { - return new CommentBuilder($query); - } + protected static string $builder = CommentBuilder::class; } /**