From 893a2d53772ccf61d12d5934568b2807062be648 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 24 Aug 2021 15:17:05 +0100 Subject: [PATCH] Adds work in progress regarding generic collections --- src/Illuminate/Collections/Collection.php | 12 +- src/Illuminate/Collections/Enumerable.php | 26 +-- .../Collections/Traits/EnumeratesValues.php | 10 +- .../Contracts/Queue/QueueableCollection.php | 4 +- .../Database/Eloquent/Collection.php | 121 ++++++++------ src/Illuminate/Database/Eloquent/Model.php | 2 +- types/Database/Eloquent/Collection.php | 153 ++++++++++++++++++ types/Support/Collection.php | 3 + 8 files changed, 252 insertions(+), 79 deletions(-) create mode 100644 types/Database/Eloquent/Collection.php diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 2e177f804646..c5cdf2381327 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -166,8 +166,8 @@ public function collapse() * Determine if an item exists in the collection. * * @param (callable(TValue): bool)|TValue|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param mixed $operator + * @param mixed $value * @return bool */ public function contains($key, $operator = null, $value = null) @@ -378,7 +378,7 @@ public function first(callable $callback = null, $default = null) * Get a flattened array of the items in the collection. * * @param int $depth - * @return static + * @return static */ public function flatten($depth = INF) { @@ -637,7 +637,7 @@ public function last(callable $callback = null, $default = null) /** * Get the values of a given key. * - * @param string|array $value + * @param string|array $value * @param string|null $key * @return static */ @@ -1135,8 +1135,8 @@ public function splitIn($numberOfGroups) * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. * * @param (callable(TValue, TKey): bool)|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param mixed $operator + * @param mixed $value * @return TValue * * @throws \Illuminate\Support\ItemNotFoundException diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 7ca78f77c9b7..c16e23077a6b 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -116,9 +116,9 @@ public function collapse(); /** * Alias for the "contains" method. * - * @param (callable(TValue): bool)|TValue|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value * @return bool */ public function some($key, $operator = null, $value = null); @@ -143,9 +143,9 @@ public function avg($callback = null); /** * Determine if an item exists in the enumerable. * - * @param (callable(TValue): bool)|TValue|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value * @return bool */ public function contains($key, $operator = null, $value = null); @@ -264,8 +264,8 @@ public function eachSpread(callable $callback); * Determine if all items pass the given truth test. * * @param (callable(TValue, TKey): bool)|TValue|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param mixed $operator + * @param mixed $value * @return bool */ public function every($key, $operator = null, $value = null); @@ -746,8 +746,8 @@ public function forPage($page, $perPage); * Partition the collection into two arrays using the given callback or key. * * @param (callable(TValue, TKey): bool)|TValue|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param mixed $operator + * @param mixed $value * @return array> */ public function partition($key, $operator = null, $value = null); @@ -867,8 +867,8 @@ public function split($numberOfGroups); * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. * * @param (callable(TValue, TKey): bool)|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param mixed $operator + * @param mixed $value * @return TValue * * @throws \Illuminate\Support\ItemNotFoundException @@ -997,7 +997,7 @@ public function pipe(callable $callback); /** * Get the values of a given key. * - * @param string|array $value + * @param string|array $value * @param string|null $key * @return static */ diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index e8318cfacdf0..37a0c8f5b4a8 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -174,9 +174,9 @@ public function average($callback = null) /** * Alias for the "contains" method. * - * @param (callable(TValue): bool)|TValue|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value * @return bool */ public function some($key, $operator = null, $value = null) @@ -277,8 +277,8 @@ public function eachSpread(callable $callback) * Determine if all items pass the given truth test. * * @param (callable(TValue, TKey): bool)|TValue|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param mixed $operator + * @param mixed $value * @return bool */ public function every($key, $operator = null, $value = null) diff --git a/src/Illuminate/Contracts/Queue/QueueableCollection.php b/src/Illuminate/Contracts/Queue/QueueableCollection.php index 7f1ea19c5436..750d10d4b088 100644 --- a/src/Illuminate/Contracts/Queue/QueueableCollection.php +++ b/src/Illuminate/Contracts/Queue/QueueableCollection.php @@ -14,14 +14,14 @@ public function getQueueableClass(); /** * Get the identifiers for all of the entities. * - * @return array + * @return array */ public function getQueueableIds(); /** * Get the relationships of the entities being queued. * - * @return array + * @return array */ public function getQueueableRelations(); diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index ff9b2747fe3e..39672138f5e8 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -10,14 +10,22 @@ use Illuminate\Support\Str; use LogicException; +/** + * @template TKey of array-key + * @template TModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Support\Collection + */ class Collection extends BaseCollection implements QueueableCollection { /** * Find a model in the collection by key. * + * @template TFindDefault + * * @param mixed $key - * @param mixed $default - * @return \Illuminate\Database\Eloquent\Model|static|null + * @param TFindDefault $default + * @return static|TModel|TFindDefault */ public function find($key, $default = null) { @@ -45,7 +53,7 @@ public function find($key, $default = null) /** * Load a set of relationships onto the collection. * - * @param array|string $relations + * @param array|string $relations * @return $this */ public function load($relations) @@ -66,9 +74,9 @@ public function load($relations) /** * Load a set of aggregations over relationship's column onto the collection. * - * @param array|string $relations + * @param array|string $relations * @param string $column - * @param string $function + * @param string|null $function * @return $this */ public function loadAggregate($relations, $column, $function = null) @@ -101,7 +109,7 @@ public function loadAggregate($relations, $column, $function = null) /** * Load a set of relationship counts onto the collection. * - * @param array|string $relations + * @param array|string $relations * @return $this */ public function loadCount($relations) @@ -112,7 +120,7 @@ public function loadCount($relations) /** * Load a set of relationship's max column values onto the collection. * - * @param array|string $relations + * @param array|string $relations * @param string $column * @return $this */ @@ -124,7 +132,7 @@ public function loadMax($relations, $column) /** * Load a set of relationship's min column values onto the collection. * - * @param array|string $relations + * @param array|string $relations * @param string $column * @return $this */ @@ -136,7 +144,7 @@ public function loadMin($relations, $column) /** * Load a set of relationship's column summations onto the collection. * - * @param array|string $relations + * @param array|string $relations * @param string $column * @return $this */ @@ -148,7 +156,7 @@ public function loadSum($relations, $column) /** * Load a set of relationship's average column values onto the collection. * - * @param array|string $relations + * @param array|string $relations * @param string $column * @return $this */ @@ -160,7 +168,7 @@ public function loadAvg($relations, $column) /** * Load a set of related existences onto the collection. * - * @param array|string $relations + * @param array|string $relations * @return $this */ public function loadExists($relations) @@ -171,7 +179,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|string $relations * @return $this */ public function loadMissing($relations) @@ -245,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 $relations * @return $this */ public function loadMorph($relation, $relations) @@ -266,7 +274,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 $relations * @return $this */ public function loadMorphCount($relation, $relations) @@ -286,7 +294,7 @@ public function loadMorphCount($relation, $relations) /** * Determine if a key exists in the collection. * - * @param mixed $key + * @param (callable(TModel, TKey): bool)|TModel|string $key * @param mixed $operator * @param mixed $value * @return bool @@ -311,7 +319,7 @@ public function contains($key, $operator = null, $value = null) /** * Get the array of primary keys. * - * @return array + * @return array */ public function modelKeys() { @@ -323,8 +331,8 @@ public function modelKeys() /** * Merge the collection with the given items. * - * @param \ArrayAccess|array $items - * @return static + * @param iterable $items + * @return static */ public function merge($items) { @@ -340,8 +348,10 @@ public function merge($items) /** * Run a map over each of the items. * - * @param callable $callback - * @return \Illuminate\Support\Collection|static + * @template TMapValue + * + * @param callable(TModel, TKey): TMapValue $callback + * @return \Illuminate\Support\Collection|static */ public function map(callable $callback) { @@ -357,8 +367,11 @@ public function map(callable $callback) * * The callback should return an associative array with a single key / value pair. * - * @param callable $callback - * @return \Illuminate\Support\Collection|static + * @template TMapWithKeysKey of array-key + * @template TMapWithKeysValue + * + * @param callable(TModel, TKey): array $callback + * @return \Illuminate\Support\Collection|static */ public function mapWithKeys(callable $callback) { @@ -372,8 +385,8 @@ public function mapWithKeys(callable $callback) /** * Reload a fresh model instance from the database for all the entities. * - * @param array|string $with - * @return static + * @param array|string $with + * @return static */ public function fresh($with = []) { @@ -400,8 +413,8 @@ public function fresh($with = []) /** * Diff the collection with the given items. * - * @param \ArrayAccess|array $items - * @return static + * @param iterable $items + * @return static */ public function diff($items) { @@ -421,8 +434,8 @@ public function diff($items) /** * Intersect the collection with the given items. * - * @param \ArrayAccess|array $items - * @return static + * @param iterable $items + * @return static */ public function intersect($items) { @@ -446,9 +459,9 @@ public function intersect($items) /** * Return only unique items from the collection. * - * @param string|callable|null $key + * @param (callable(TModel, TKey): bool)|string|null $key * @param bool $strict - * @return static + * @return static */ public function unique($key = null, $strict = false) { @@ -462,8 +475,8 @@ public function unique($key = null, $strict = false) /** * Returns only the models from the collection with the specified keys. * - * @param mixed $keys - * @return static + * @param array|null $keys + * @return static */ public function only($keys) { @@ -479,8 +492,8 @@ public function only($keys) /** * Returns all models in the collection except the models with specified keys. * - * @param mixed $keys - * @return static + * @param array|null $keys + * @return static */ public function except($keys) { @@ -492,7 +505,7 @@ public function except($keys) /** * Make the given, typically visible, attributes hidden across the entire collection. * - * @param array|string $attributes + * @param array|string $attributes * @return $this */ public function makeHidden($attributes) @@ -503,7 +516,7 @@ public function makeHidden($attributes) /** * Make the given, typically hidden, attributes visible across the entire collection. * - * @param array|string $attributes + * @param array|string $attributes * @return $this */ public function makeVisible($attributes) @@ -514,7 +527,7 @@ public function makeVisible($attributes) /** * Append an attribute across the entire collection. * - * @param array|string $attributes + * @param array|string $attributes * @return $this */ public function append($attributes) @@ -525,8 +538,8 @@ public function append($attributes) /** * Get a dictionary keyed by primary keys. * - * @param \ArrayAccess|array|null $items - * @return array + * @param iterable|null $items + * @return array */ public function getDictionary($items = null) { @@ -548,9 +561,9 @@ public function getDictionary($items = null) /** * Get an array with the values of a given key. * - * @param string|array $value + * @param string|array $value * @param string|null $key - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function pluck($value, $key = null) { @@ -560,7 +573,7 @@ public function pluck($value, $key = null) /** * Get the keys of the collection items. * - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function keys() { @@ -570,8 +583,10 @@ public function keys() /** * Zip the collection together with one or more arrays. * - * @param mixed ...$items - * @return \Illuminate\Support\Collection + * @template TZipValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$items + * @return \Illuminate\Support\Collection> */ public function zip($items) { @@ -581,7 +596,7 @@ public function zip($items) /** * Collapse the collection of items into a single array. * - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function collapse() { @@ -592,7 +607,7 @@ public function collapse() * Get a flattened array of the items in the collection. * * @param int $depth - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function flatten($depth = INF) { @@ -602,7 +617,7 @@ public function flatten($depth = INF) /** * Flip the items in the collection. * - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function flip() { @@ -612,9 +627,11 @@ public function flip() /** * Pad collection to the specified length with a value. * + * @template TPadValue + * * @param int $size - * @param mixed $value - * @return \Illuminate\Support\Collection + * @param TPadValue $value + * @return \Illuminate\Support\Collection */ public function pad($size, $value) { @@ -625,7 +642,7 @@ public function pad($size, $value) * Get the comparison function to detect duplicates. * * @param bool $strict - * @return \Closure + * @return callable(TValue, TValue): bool */ protected function duplicateComparator($strict) { @@ -661,7 +678,7 @@ public function getQueueableClass() /** * Get the identifiers for all of the entities. * - * @return array + * @return array */ public function getQueueableIds() { @@ -677,7 +694,7 @@ public function getQueueableIds() /** * Get the relationships of the entities being queued. * - * @return array + * @return array */ public function getQueueableRelations() { diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 2222ffe1a46e..8fee029b5357 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -526,7 +526,7 @@ public static function onWriteConnection() * Get all of the models from the database. * * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Collection|static[] + * @return \Illuminate\Database\Eloquent\Collection */ public static function all($columns = ['*']) { diff --git a/types/Database/Eloquent/Collection.php b/types/Database/Eloquent/Collection.php new file mode 100644 index 000000000000..74a42c8358b7 --- /dev/null +++ b/types/Database/Eloquent/Collection.php @@ -0,0 +1,153 @@ +', $collection); + +assertType('Illuminate\Database\Eloquent\Collection|User|null', $collection->find(1)); +assertType('Illuminate\Database\Eloquent\Collection|string|User', $collection->find(1, 'string')); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->load('string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->load(['string'])); + +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->loadCount('string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount(['string'])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax('string', 'string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax(['string'], '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->loadSum('string', 'string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum(['string'], '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->loadExists('string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists(['string'])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing('string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing(['string'])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorph('string', ['string'])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorphCount('string', ['string'])); + +assertType('bool', $collection->contains(function ($user) { + assertType('User', $user); + + return true; +})); +assertType('bool', $collection->contains('string', '=', 'string')); + +assertType('array', $collection->modelKeys()); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->merge($collection)); +assertType('Illuminate\Database\Eloquent\Collection', $collection->merge([new User])); + +assertType( + 'Illuminate\Support\Collection', + $collection->map(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); + + return new User; + }) +); + +assertType( + 'Illuminate\Support\Collection', + $collection->mapWithKeys(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); + + return [new User]; + }) +); +assertType( + 'Illuminate\Support\Collection', + $collection->mapWithKeys(function ($user, $int) { + return ['string' => new User]; + }) +); + +assertType( + 'Illuminate\Database\Eloquent\Collection', + $collection->fresh() +); +assertType( + 'Illuminate\Database\Eloquent\Collection', + $collection->fresh('string') +); +assertType( + 'Illuminate\Database\Eloquent\Collection', + $collection->fresh(['string']) +); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->diff($collection)); +assertType('Illuminate\Database\Eloquent\Collection', $collection->diff([new User])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->intersect($collection)); +assertType('Illuminate\Database\Eloquent\Collection', $collection->intersect([new User])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->unique()); +assertType('Illuminate\Database\Eloquent\Collection', $collection->unique(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); + + return true; +})); +assertType('Illuminate\Database\Eloquent\Collection', $collection->unique('string')); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->only(null)); +assertType('Illuminate\Database\Eloquent\Collection', $collection->only(['string'])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->except(null)); +assertType('Illuminate\Database\Eloquent\Collection', $collection->except(['string'])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->makeHidden('string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->makeHidden(['string'])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->makeVisible('string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->makeVisible(['string'])); + +assertType('Illuminate\Database\Eloquent\Collection', $collection->append('string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->append(['string'])); + +assertType('array', $collection->getDictionary()); +assertType('array', $collection->getDictionary($collection)); +assertType('array', $collection->getDictionary([new User])); + +assertType('Illuminate\Support\Collection', $collection->pluck('string')); +assertType('Illuminate\Support\Collection', $collection->pluck(['string'])); + +assertType('Illuminate\Support\Collection', $collection->keys()); + +assertType('Illuminate\Support\Collection>', $collection->zip([1])); +assertType('Illuminate\Support\Collection>', $collection->zip(['string'])); + +assertType('Illuminate\Support\Collection', $collection->collapse()); + +assertType('Illuminate\Support\Collection', $collection->flatten()); +assertType('Illuminate\Support\Collection', $collection->flatten(4)); + +assertType('Illuminate\Support\Collection', $collection->flip()); + +assertType('Illuminate\Support\Collection', $collection->pad(2, 0)); +assertType('Illuminate\Support\Collection', $collection->pad(2, 'string')); + +assertType('array', $collection->getQueueableIds()); + +assertType('array', $collection->getQueueableRelations()); diff --git a/types/Support/Collection.php b/types/Support/Collection.php index 2e36227252f2..a1cf2c82feee 100644 --- a/types/Support/Collection.php +++ b/types/Support/Collection.php @@ -368,6 +368,9 @@ class User extends Authenticatable return false; }, 'string')); +assertType('Illuminate\Support\Collection', $collection->flatten()); +assertType('Illuminate\Support\Collection', $collection::make(['string' => 'string'])->flatten(4)); + assertType('User|null', $collection->firstWhere('string', 'string')); assertType('User|null', $collection->firstWhere('string', 'string', 'string'));