Collection.php 14 KB

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