Collection.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. <?php
  2. namespace Illuminate\Database\Eloquent;
  3. use LogicException;
  4. use Illuminate\Support\Arr;
  5. use Illuminate\Support\Str;
  6. use Illuminate\Contracts\Support\Arrayable;
  7. use Illuminate\Database\Eloquent\Relations\Pivot;
  8. use Illuminate\Contracts\Queue\QueueableCollection;
  9. use Illuminate\Support\Collection as BaseCollection;
  10. class Collection extends BaseCollection implements QueueableCollection
  11. {
  12. /**
  13. * Find a model in the collection by key.
  14. *
  15. * @param mixed $key
  16. * @param mixed $default
  17. * @return \Illuminate\Database\Eloquent\Model|static
  18. */
  19. public function find($key, $default = null)
  20. {
  21. if ($key instanceof Model) {
  22. $key = $key->getKey();
  23. }
  24. if ($key instanceof Arrayable) {
  25. $key = $key->toArray();
  26. }
  27. if (is_array($key)) {
  28. if ($this->isEmpty()) {
  29. return new static;
  30. }
  31. return $this->whereIn($this->first()->getKeyName(), $key);
  32. }
  33. return Arr::first($this->items, function ($model) use ($key) {
  34. return $model->getKey() == $key;
  35. }, $default);
  36. }
  37. /**
  38. * Load a set of relationships onto the collection.
  39. *
  40. * @param array|string $relations
  41. * @return $this
  42. */
  43. public function load($relations)
  44. {
  45. if ($this->isNotEmpty()) {
  46. if (is_string($relations)) {
  47. $relations = func_get_args();
  48. }
  49. $query = $this->first()->newQueryWithoutRelationships()->with($relations);
  50. $this->items = $query->eagerLoadRelations($this->items);
  51. }
  52. return $this;
  53. }
  54. /**
  55. * Load a set of relationships onto the collection if they are not already eager loaded.
  56. *
  57. * @param array|string $relations
  58. * @return $this
  59. */
  60. public function loadMissing($relations)
  61. {
  62. if (is_string($relations)) {
  63. $relations = func_get_args();
  64. }
  65. foreach ($relations as $key => $value) {
  66. if (is_numeric($key)) {
  67. $key = $value;
  68. }
  69. $segments = explode('.', explode(':', $key)[0]);
  70. if (Str::contains($key, ':')) {
  71. $segments[count($segments) - 1] .= ':'.explode(':', $key)[1];
  72. }
  73. $path = array_combine($segments, $segments);
  74. if (is_callable($value)) {
  75. $path[end($segments)] = $value;
  76. }
  77. $this->loadMissingRelation($this, $path);
  78. }
  79. return $this;
  80. }
  81. /**
  82. * Load a relationship path if it is not already eager loaded.
  83. *
  84. * @param \Illuminate\Database\Eloquent\Collection $models
  85. * @param array $path
  86. * @return void
  87. */
  88. protected function loadMissingRelation(Collection $models, array $path)
  89. {
  90. $relation = array_splice($path, 0, 1);
  91. $name = explode(':', key($relation))[0];
  92. if (is_string(reset($relation))) {
  93. $relation = reset($relation);
  94. }
  95. $models->filter(function ($model) use ($name) {
  96. return ! is_null($model) && ! $model->relationLoaded($name);
  97. })->load($relation);
  98. if (empty($path)) {
  99. return;
  100. }
  101. $models = $models->pluck($name);
  102. if ($models->first() instanceof BaseCollection) {
  103. $models = $models->collapse();
  104. }
  105. $this->loadMissingRelation(new static($models), $path);
  106. }
  107. /**
  108. * Load a set of relationships onto the mixed relationship collection.
  109. *
  110. * @param string $relation
  111. * @param array $relations
  112. * @return $this
  113. */
  114. public function loadMorph($relation, $relations)
  115. {
  116. $this->pluck($relation)
  117. ->filter()
  118. ->groupBy(function ($model) {
  119. return get_class($model);
  120. })
  121. ->filter(function ($models, $className) use ($relations) {
  122. return Arr::has($relations, $className);
  123. })
  124. ->each(function ($models, $className) use ($relations) {
  125. $className::with($relations[$className])
  126. ->eagerLoadRelations($models->all());
  127. });
  128. return $this;
  129. }
  130. /**
  131. * Add an item to the collection.
  132. *
  133. * @param mixed $item
  134. * @return $this
  135. */
  136. public function add($item)
  137. {
  138. $this->items[] = $item;
  139. return $this;
  140. }
  141. /**
  142. * Determine if a key exists in the collection.
  143. *
  144. * @param mixed $key
  145. * @param mixed $operator
  146. * @param mixed $value
  147. * @return bool
  148. */
  149. public function contains($key, $operator = null, $value = null)
  150. {
  151. if (func_num_args() > 1 || $this->useAsCallable($key)) {
  152. return parent::contains(...func_get_args());
  153. }
  154. if ($key instanceof Model) {
  155. return parent::contains(function ($model) use ($key) {
  156. return $model->is($key);
  157. });
  158. }
  159. return parent::contains(function ($model) use ($key) {
  160. return $model->getKey() == $key;
  161. });
  162. }
  163. /**
  164. * Get the array of primary keys.
  165. *
  166. * @return array
  167. */
  168. public function modelKeys()
  169. {
  170. return array_map(function ($model) {
  171. return $model->getKey();
  172. }, $this->items);
  173. }
  174. /**
  175. * Merge the collection with the given items.
  176. *
  177. * @param \ArrayAccess|array $items
  178. * @return static
  179. */
  180. public function merge($items)
  181. {
  182. $dictionary = $this->getDictionary();
  183. foreach ($items as $item) {
  184. $dictionary[$item->getKey()] = $item;
  185. }
  186. return new static(array_values($dictionary));
  187. }
  188. /**
  189. * Run a map over each of the items.
  190. *
  191. * @param callable $callback
  192. * @return \Illuminate\Support\Collection|static
  193. */
  194. public function map(callable $callback)
  195. {
  196. $result = parent::map($callback);
  197. return $result->contains(function ($item) {
  198. return ! $item instanceof Model;
  199. }) ? $result->toBase() : $result;
  200. }
  201. /**
  202. * Reload a fresh model instance from the database for all the entities.
  203. *
  204. * @param array|string $with
  205. * @return static
  206. */
  207. public function fresh($with = [])
  208. {
  209. if ($this->isEmpty()) {
  210. return new static;
  211. }
  212. $model = $this->first();
  213. $freshModels = $model->newQueryWithoutScopes()
  214. ->with(is_string($with) ? func_get_args() : $with)
  215. ->whereIn($model->getKeyName(), $this->modelKeys())
  216. ->get()
  217. ->getDictionary();
  218. return $this->map(function ($model) use ($freshModels) {
  219. return $model->exists && isset($freshModels[$model->getKey()])
  220. ? $freshModels[$model->getKey()] : null;
  221. });
  222. }
  223. /**
  224. * Diff the collection with the given items.
  225. *
  226. * @param \ArrayAccess|array $items
  227. * @return static
  228. */
  229. public function diff($items)
  230. {
  231. $diff = new static;
  232. $dictionary = $this->getDictionary($items);
  233. foreach ($this->items as $item) {
  234. if (! isset($dictionary[$item->getKey()])) {
  235. $diff->add($item);
  236. }
  237. }
  238. return $diff;
  239. }
  240. /**
  241. * Intersect the collection with the given items.
  242. *
  243. * @param \ArrayAccess|array $items
  244. * @return static
  245. */
  246. public function intersect($items)
  247. {
  248. $intersect = new static;
  249. $dictionary = $this->getDictionary($items);
  250. foreach ($this->items as $item) {
  251. if (isset($dictionary[$item->getKey()])) {
  252. $intersect->add($item);
  253. }
  254. }
  255. return $intersect;
  256. }
  257. /**
  258. * Return only unique items from the collection.
  259. *
  260. * @param string|callable|null $key
  261. * @param bool $strict
  262. * @return static|\Illuminate\Support\Collection
  263. */
  264. public function unique($key = null, $strict = false)
  265. {
  266. if (! is_null($key)) {
  267. return parent::unique($key, $strict);
  268. }
  269. return new static(array_values($this->getDictionary()));
  270. }
  271. /**
  272. * Returns only the models from the collection with the specified keys.
  273. *
  274. * @param mixed $keys
  275. * @return static
  276. */
  277. public function only($keys)
  278. {
  279. if (is_null($keys)) {
  280. return new static($this->items);
  281. }
  282. $dictionary = Arr::only($this->getDictionary(), $keys);
  283. return new static(array_values($dictionary));
  284. }
  285. /**
  286. * Returns all models in the collection except the models with specified keys.
  287. *
  288. * @param mixed $keys
  289. * @return static
  290. */
  291. public function except($keys)
  292. {
  293. $dictionary = Arr::except($this->getDictionary(), $keys);
  294. return new static(array_values($dictionary));
  295. }
  296. /**
  297. * Make the given, typically visible, attributes hidden across the entire collection.
  298. *
  299. * @param array|string $attributes
  300. * @return $this
  301. */
  302. public function makeHidden($attributes)
  303. {
  304. return $this->each(function ($model) use ($attributes) {
  305. $model->addHidden($attributes);
  306. });
  307. }
  308. /**
  309. * Make the given, typically hidden, attributes visible across the entire collection.
  310. *
  311. * @param array|string $attributes
  312. * @return $this
  313. */
  314. public function makeVisible($attributes)
  315. {
  316. return $this->each(function ($model) use ($attributes) {
  317. $model->makeVisible($attributes);
  318. });
  319. }
  320. /**
  321. * Get a dictionary keyed by primary keys.
  322. *
  323. * @param \ArrayAccess|array|null $items
  324. * @return array
  325. */
  326. public function getDictionary($items = null)
  327. {
  328. $items = is_null($items) ? $this->items : $items;
  329. $dictionary = [];
  330. foreach ($items as $value) {
  331. $dictionary[$value->getKey()] = $value;
  332. }
  333. return $dictionary;
  334. }
  335. /**
  336. * The following methods are intercepted to always return base collections.
  337. */
  338. /**
  339. * Get an array with the values of a given key.
  340. *
  341. * @param string $value
  342. * @param string|null $key
  343. * @return \Illuminate\Support\Collection
  344. */
  345. public function pluck($value, $key = null)
  346. {
  347. return $this->toBase()->pluck($value, $key);
  348. }
  349. /**
  350. * Get the keys of the collection items.
  351. *
  352. * @return \Illuminate\Support\Collection
  353. */
  354. public function keys()
  355. {
  356. return $this->toBase()->keys();
  357. }
  358. /**
  359. * Zip the collection together with one or more arrays.
  360. *
  361. * @param mixed ...$items
  362. * @return \Illuminate\Support\Collection
  363. */
  364. public function zip($items)
  365. {
  366. return call_user_func_array([$this->toBase(), 'zip'], func_get_args());
  367. }
  368. /**
  369. * Collapse the collection of items into a single array.
  370. *
  371. * @return \Illuminate\Support\Collection
  372. */
  373. public function collapse()
  374. {
  375. return $this->toBase()->collapse();
  376. }
  377. /**
  378. * Get a flattened array of the items in the collection.
  379. *
  380. * @param int $depth
  381. * @return \Illuminate\Support\Collection
  382. */
  383. public function flatten($depth = INF)
  384. {
  385. return $this->toBase()->flatten($depth);
  386. }
  387. /**
  388. * Flip the items in the collection.
  389. *
  390. * @return \Illuminate\Support\Collection
  391. */
  392. public function flip()
  393. {
  394. return $this->toBase()->flip();
  395. }
  396. /**
  397. * Pad collection to the specified length with a value.
  398. *
  399. * @param int $size
  400. * @param mixed $value
  401. * @return \Illuminate\Support\Collection
  402. */
  403. public function pad($size, $value)
  404. {
  405. return $this->toBase()->pad($size, $value);
  406. }
  407. /**
  408. * Get the type of the entities being queued.
  409. *
  410. * @return string|null
  411. * @throws \LogicException
  412. */
  413. public function getQueueableClass()
  414. {
  415. if ($this->isEmpty()) {
  416. return;
  417. }
  418. $class = get_class($this->first());
  419. $this->each(function ($model) use ($class) {
  420. if (get_class($model) !== $class) {
  421. throw new LogicException('Queueing collections with multiple model types is not supported.');
  422. }
  423. });
  424. return $class;
  425. }
  426. /**
  427. * Get the identifiers for all of the entities.
  428. *
  429. * @return array
  430. */
  431. public function getQueueableIds()
  432. {
  433. if ($this->isEmpty()) {
  434. return [];
  435. }
  436. return $this->first() instanceof Pivot
  437. ? $this->map->getQueueableId()->all()
  438. : $this->modelKeys();
  439. }
  440. /**
  441. * Get the relationships of the entities being queued.
  442. *
  443. * @return array
  444. */
  445. public function getQueueableRelations()
  446. {
  447. return $this->isNotEmpty() ? $this->first()->getQueueableRelations() : [];
  448. }
  449. /**
  450. * Get the connection of the entities being queued.
  451. *
  452. * @return string|null
  453. * @throws \LogicException
  454. */
  455. public function getQueueableConnection()
  456. {
  457. if ($this->isEmpty()) {
  458. return;
  459. }
  460. $connection = $this->first()->getConnectionName();
  461. $this->each(function ($model) use ($connection) {
  462. if ($model->getConnectionName() !== $connection) {
  463. throw new LogicException('Queueing collections with multiple model connections is not supported.');
  464. }
  465. });
  466. return $connection;
  467. }
  468. }