Collection.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. <?php
  2. declare(strict_types=1);
  3. namespace Yansongda\Supports;
  4. use ArrayAccess;
  5. use ArrayIterator;
  6. use Countable;
  7. use IteratorAggregate;
  8. use JsonSerializable;
  9. use Yansongda\Supports\Traits\Accessable;
  10. use Yansongda\Supports\Traits\Arrayable;
  11. use Yansongda\Supports\Traits\Serializable;
  12. class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
  13. {
  14. use Serializable;
  15. use Accessable;
  16. use Arrayable;
  17. /**
  18. * The collection data.
  19. */
  20. protected array $items = [];
  21. /**
  22. * set data.
  23. *
  24. * @param mixed $items
  25. */
  26. public function __construct($items = [])
  27. {
  28. foreach ($this->getArrayableItems($items) as $key => $value) {
  29. $this->set($key, $value);
  30. }
  31. }
  32. /**
  33. * Wrap the given value in a collection if applicable.
  34. *
  35. * @param mixed $value
  36. */
  37. public static function wrap($value): self
  38. {
  39. return $value instanceof self ? new static($value) : new static(Arr::wrap($value));
  40. }
  41. /**
  42. * Get the underlying items from the given collection if applicable.
  43. *
  44. * @param array|static $value
  45. */
  46. public static function unwrap($value): array
  47. {
  48. return $value instanceof self ? $value->all() : $value;
  49. }
  50. /**
  51. * Return all items.
  52. */
  53. public function all(): array
  54. {
  55. return $this->items;
  56. }
  57. /**
  58. * Return specific items.
  59. */
  60. public function only(array $keys): array
  61. {
  62. $return = [];
  63. foreach ($keys as $key) {
  64. $value = $this->get($key);
  65. if (!is_null($value)) {
  66. $return[$key] = $value;
  67. }
  68. }
  69. return $return;
  70. }
  71. /**
  72. * Get all items except for those with the specified keys.
  73. *
  74. * @param mixed $keys
  75. */
  76. public function except($keys): self
  77. {
  78. $keys = is_array($keys) ? $keys : func_get_args();
  79. return new static(Arr::except($this->items, $keys));
  80. }
  81. /**
  82. * Run a filter over each of the items.
  83. */
  84. public function filter(callable $callback = null): self
  85. {
  86. if ($callback) {
  87. return new static(Arr::where($this->items, $callback));
  88. }
  89. return new static(array_filter($this->items));
  90. }
  91. /**
  92. * Merge the collection with the given items.
  93. *
  94. * @param mixed $items
  95. */
  96. public function merge($items): self
  97. {
  98. return new static(array_merge($this->items, $this->getArrayableItems($items)));
  99. }
  100. /**
  101. * To determine Whether the specified element exists.
  102. *
  103. * @param string|int $key
  104. */
  105. public function has($key): bool
  106. {
  107. return !is_null(Arr::get($this->items, $key));
  108. }
  109. /**
  110. * Retrieve the first item.
  111. *
  112. * @return mixed
  113. */
  114. public function first()
  115. {
  116. return reset($this->items);
  117. }
  118. /**
  119. * Retrieve the last item.
  120. *
  121. * @return mixed
  122. */
  123. public function last()
  124. {
  125. $end = end($this->items);
  126. reset($this->items);
  127. return $end;
  128. }
  129. /**
  130. * add the item value.
  131. *
  132. * @param string|int|null $key
  133. * @param mixed $value
  134. */
  135. public function add($key, $value): void
  136. {
  137. Arr::set($this->items, $key, $value);
  138. }
  139. /**
  140. * Set the item value.
  141. *
  142. * @param string|int|null $key
  143. * @param mixed $value
  144. */
  145. public function set($key, $value): void
  146. {
  147. Arr::set($this->items, $key, $value);
  148. }
  149. /**
  150. * Retrieve item from Collection.
  151. *
  152. * @param string|int|null $key
  153. * @param mixed $default
  154. *
  155. * @return mixed
  156. */
  157. public function get($key = null, $default = null)
  158. {
  159. return Arr::get($this->items, $key, $default);
  160. }
  161. /**
  162. * Remove item form Collection.
  163. *
  164. * @param string|int $key
  165. */
  166. public function forget($key): void
  167. {
  168. Arr::forget($this->items, $key);
  169. }
  170. /**
  171. * Get a flattened array of the items in the collection.
  172. *
  173. * @param float|int $depth
  174. */
  175. public function flatten($depth = INF): self
  176. {
  177. return new static(Arr::flatten($this->items, $depth));
  178. }
  179. /**
  180. * Run a map over each of the items.
  181. */
  182. public function map(callable $callback): self
  183. {
  184. $keys = array_keys($this->items);
  185. $items = array_map($callback, $this->items, $keys);
  186. return new static(array_combine($keys, $items));
  187. }
  188. /**
  189. * Get and remove the last item from the collection.
  190. *
  191. * @return mixed
  192. */
  193. public function pop()
  194. {
  195. return array_pop($this->items);
  196. }
  197. /**
  198. * Push an item onto the beginning of the collection.
  199. *
  200. * @param mixed|null $key
  201. * @param mixed $value
  202. */
  203. public function prepend($value, $key = null): self
  204. {
  205. $this->items = Arr::prepend($this->items, $value, $key);
  206. return $this;
  207. }
  208. /**
  209. * Push an item onto the end of the collection.
  210. *
  211. * @param mixed $value
  212. */
  213. public function push($value): self
  214. {
  215. $this->offsetSet(null, $value);
  216. return $this;
  217. }
  218. /**
  219. * Get and remove an item from the collection.
  220. *
  221. * @param mixed $default
  222. * @param mixed $key
  223. *
  224. * @return mixed
  225. */
  226. public function pull($key, $default = null)
  227. {
  228. return Arr::pull($this->items, $key, $default);
  229. }
  230. /**
  231. * Put an item in the collection by key.
  232. *
  233. * @param mixed $key
  234. * @param mixed $value
  235. */
  236. public function put($key, $value): self
  237. {
  238. $this->offsetSet($key, $value);
  239. return $this;
  240. }
  241. /**
  242. * Get one or a specified number of items randomly from the collection.
  243. *
  244. * @throws \InvalidArgumentException
  245. */
  246. public function random(?int $number = null): self
  247. {
  248. return new static(Arr::random($this->items, $number ?? 1));
  249. }
  250. /**
  251. * Reduce the collection to a single value.
  252. *
  253. * @param mixed|null $initial
  254. */
  255. public function reduce(callable $callback, $initial = null)
  256. {
  257. return array_reduce($this->items, $callback, $initial);
  258. }
  259. /**
  260. * Reset the keys on the underlying array.
  261. */
  262. public function values(): self
  263. {
  264. return new static(array_values($this->items));
  265. }
  266. /**
  267. * Determine if all items in the collection pass the given test.
  268. *
  269. * @param callable|string $key
  270. */
  271. public function every($key): bool
  272. {
  273. $callback = $this->valueRetriever($key);
  274. foreach ($this->items as $k => $v) {
  275. if (!$callback($v, $k)) {
  276. return false;
  277. }
  278. }
  279. return true;
  280. }
  281. /**
  282. * Chunk the underlying collection array.
  283. */
  284. public function chunk(int $size): self
  285. {
  286. if ($size <= 0) {
  287. return new static();
  288. }
  289. $chunks = [];
  290. foreach (array_chunk($this->items, $size, true) as $chunk) {
  291. $chunks[] = new static($chunk);
  292. }
  293. return new static($chunks);
  294. }
  295. /**
  296. * Sort through each item with a callback.
  297. */
  298. public function sort(callable $callback = null): self
  299. {
  300. $items = $this->items;
  301. $callback ? uasort($items, $callback) : asort($items);
  302. return new static($items);
  303. }
  304. /**
  305. * Sort the collection using the given callback.
  306. *
  307. * @param callable|string $callback
  308. */
  309. public function sortBy($callback, int $options = SORT_REGULAR, bool $descending = false): self
  310. {
  311. $results = [];
  312. $callback = $this->valueRetriever($callback);
  313. // First we will loop through the items and get the comparator from a callback
  314. // function which we were given. Then, we will sort the returned values and
  315. // and grab the corresponding values for the sorted keys from this array.
  316. foreach ($this->items as $key => $value) {
  317. $results[$key] = $callback($value, $key);
  318. }
  319. $descending ? arsort($results, $options) : asort($results, $options);
  320. // Once we have sorted all of the keys in the array, we will loop through them
  321. // and grab the corresponding model so we can set the underlying items list
  322. // to the sorted version. Then we'll just return the collection instance.
  323. foreach (array_keys($results) as $key) {
  324. $results[$key] = $this->items[$key];
  325. }
  326. return new static($results);
  327. }
  328. /**
  329. * Sort the collection in descending order using the given callback.
  330. *
  331. * @param callable|string $callback
  332. */
  333. public function sortByDesc($callback, int $options = SORT_REGULAR): self
  334. {
  335. return $this->sortBy($callback, $options, true);
  336. }
  337. /**
  338. * Sort the collection keys.
  339. */
  340. public function sortKeys(int $options = SORT_REGULAR, bool $descending = false): self
  341. {
  342. $items = $this->items;
  343. $descending ? krsort($items, $options) : ksort($items, $options);
  344. return new static($items);
  345. }
  346. /**
  347. * Sort the collection keys in descending order.
  348. */
  349. public function sortKeysDesc(int $options = SORT_REGULAR): self
  350. {
  351. return $this->sortKeys($options, true);
  352. }
  353. public function query(int $encodingType = PHP_QUERY_RFC1738): string
  354. {
  355. return Arr::query($this->all(), $encodingType);
  356. }
  357. public function toString(string $separator = '&'): string
  358. {
  359. return Arr::toString($this->all(), $separator);
  360. }
  361. /**
  362. * Build to array.
  363. */
  364. public function toArray(): array
  365. {
  366. return $this->all();
  367. }
  368. /**
  369. * Build to json.
  370. */
  371. public function toJson(int $option = JSON_UNESCAPED_UNICODE): string
  372. {
  373. return json_encode($this->all(), $option);
  374. }
  375. /**
  376. * (PHP 5 &gt;= 5.0.0)<br/>
  377. * Retrieve an external iterator.
  378. *
  379. * @see http://php.net/manual/en/iteratoraggregate.getiterator.php
  380. *
  381. * @return ArrayIterator An instance of an object implementing <b>Iterator</b> or
  382. * <b>ArrayIterator</b>
  383. */
  384. #[\ReturnTypeWillChange]
  385. public function getIterator()
  386. {
  387. return new ArrayIterator($this->items);
  388. }
  389. /**
  390. * (PHP 5 &gt;= 5.1.0)<br/>
  391. * Count elements of an object.
  392. *
  393. * @see http://php.net/manual/en/countable.count.php
  394. *
  395. * @return int The custom count as an integer.
  396. * </p>
  397. * <p>
  398. * The return value is cast to an integer
  399. */
  400. #[\ReturnTypeWillChange]
  401. public function count()
  402. {
  403. return count($this->items);
  404. }
  405. /**
  406. * (PHP 5 &gt;= 5.0.0)<br/>
  407. * Offset to unset.
  408. *
  409. * @see http://php.net/manual/en/arrayaccess.offsetunset.php
  410. *
  411. * @param mixed $offset <p>
  412. * The offset to unset.
  413. * </p>
  414. */
  415. #[\ReturnTypeWillChange]
  416. public function offsetUnset($offset)
  417. {
  418. if ($this->offsetExists($offset)) {
  419. $this->forget($offset);
  420. }
  421. }
  422. /**
  423. * Determine if the given value is callable, but not a string.
  424. *
  425. * @param mixed $value
  426. */
  427. protected function useAsCallable($value): bool
  428. {
  429. return !is_string($value) && is_callable($value);
  430. }
  431. /**
  432. * Get a value retrieving callback.
  433. *
  434. * @param mixed $value
  435. */
  436. protected function valueRetriever($value): callable
  437. {
  438. if ($this->useAsCallable($value)) {
  439. return $value;
  440. }
  441. return function ($item) use ($value) {
  442. return data_get($item, $value);
  443. };
  444. }
  445. /**
  446. * Results array of items from Collection or Arrayable.
  447. *
  448. * @param mixed $items
  449. */
  450. protected function getArrayableItems($items): array
  451. {
  452. if (is_array($items)) {
  453. return $items;
  454. }
  455. if ($items instanceof self) {
  456. return $items->all();
  457. }
  458. if ($items instanceof JsonSerializable) {
  459. return $items->jsonSerialize();
  460. }
  461. return (array) $items;
  462. }
  463. }