1: <?php declare(strict_types=1);
2:
3: namespace Salient\Collection;
4:
5: use Salient\Contract\Collection\CollectionInterface;
6:
7: /**
8: * @api
9: *
10: * @template TKey of array-key
11: * @template TValue
12: *
13: * @phpstan-require-implements CollectionInterface
14: */
15: trait CollectionTrait
16: {
17: /** @use ReadOnlyCollectionTrait<TKey,TValue> */
18: use ReadOnlyCollectionTrait;
19:
20: /**
21: * @inheritDoc
22: */
23: public function set($key, $value)
24: {
25: $items = $this->Items;
26: $items[$key] = $value;
27: return $this->maybeReplaceItems($items);
28: }
29:
30: /**
31: * @inheritDoc
32: */
33: public function unset($key)
34: {
35: if (!array_key_exists($key, $this->Items)) {
36: return $this;
37: }
38: $items = $this->Items;
39: unset($items[$key]);
40: return $this->replaceItems($items);
41: }
42:
43: /**
44: * @inheritDoc
45: */
46: public function add($value)
47: {
48: $items = $this->Items;
49: $items[] = $value;
50: return $this->replaceItems($items, true);
51: }
52:
53: /**
54: * @inheritDoc
55: */
56: public function merge($items)
57: {
58: $items = $this->getItemsArray($items);
59: if (!$items) {
60: return $this;
61: }
62: // array_merge() can't be used here because it renumbers numeric keys
63: $merged = $this->Items;
64: foreach ($items as $key => $item) {
65: if (is_int($key)) {
66: $merged[] = $item;
67: continue;
68: }
69: $merged[$key] = $item;
70: }
71: return $this->maybeReplaceItems($merged);
72: }
73:
74: /**
75: * @inheritDoc
76: */
77: public function sort()
78: {
79: $items = $this->Items;
80: uasort($items, fn($a, $b) => $this->compareItems($a, $b));
81: return $this->maybeReplaceItems($items);
82: }
83:
84: /**
85: * @inheritDoc
86: */
87: public function reverse()
88: {
89: $items = array_reverse($this->Items, true);
90: return $this->maybeReplaceItems($items);
91: }
92:
93: /**
94: * @inheritDoc
95: */
96: public function map(callable $callback, int $mode = CollectionInterface::CALLBACK_USE_VALUE)
97: {
98: $items = [];
99: $prev = null;
100: $item = null;
101: $key = null;
102:
103: foreach ($this->Items as $nextKey => $nextValue) {
104: $next = $this->getCallbackValue($mode, $nextKey, $nextValue);
105: if ($item !== null) {
106: /** @var TKey $key */
107: $items[$key] = $callback($item, $next, $prev);
108: }
109: $prev = $item;
110: $item = $next;
111: $key = $nextKey;
112: }
113: if ($item !== null) {
114: /** @var TKey $key */
115: $items[$key] = $callback($item, null, $prev);
116: }
117:
118: // @phpstan-ignore argument.type, return.type
119: return $this->maybeReplaceItems($items, true);
120: }
121:
122: /**
123: * @inheritDoc
124: */
125: public function filter(callable $callback, int $mode = CollectionInterface::CALLBACK_USE_VALUE)
126: {
127: $items = [];
128: $prev = null;
129: $item = null;
130: $key = null;
131: $value = null;
132:
133: foreach ($this->Items as $nextKey => $nextValue) {
134: $next = $this->getCallbackValue($mode, $nextKey, $nextValue);
135: if ($item !== null) {
136: if ($callback($item, $next, $prev)) {
137: /** @var TKey $key */
138: /** @var TValue $value */
139: $items[$key] = $value;
140: }
141: }
142: $prev = $item;
143: $item = $next;
144: $key = $nextKey;
145: $value = $nextValue;
146: }
147: if ($item !== null && $callback($item, null, $prev)) {
148: /** @var TKey $key */
149: /** @var TValue $value */
150: $items[$key] = $value;
151: }
152:
153: return $this->maybeReplaceItems($items);
154: }
155:
156: /**
157: * @inheritDoc
158: */
159: public function only(array $keys)
160: {
161: $items = array_intersect_key($this->Items, array_flip($keys));
162: return $this->maybeReplaceItems($items);
163: }
164:
165: /**
166: * @inheritDoc
167: */
168: public function onlyIn(array $index)
169: {
170: $items = array_intersect_key($this->Items, $index);
171: return $this->maybeReplaceItems($items);
172: }
173:
174: /**
175: * @inheritDoc
176: */
177: public function except(array $keys)
178: {
179: $items = array_diff_key($this->Items, array_flip($keys));
180: return $this->maybeReplaceItems($items);
181: }
182:
183: /**
184: * @inheritDoc
185: */
186: public function exceptIn(array $index)
187: {
188: $items = array_diff_key($this->Items, $index);
189: return $this->maybeReplaceItems($items);
190: }
191:
192: /**
193: * @inheritDoc
194: */
195: public function slice(int $offset, ?int $length = null)
196: {
197: $items = array_slice($this->Items, $offset, $length, true);
198: return $this->maybeReplaceItems($items);
199: }
200:
201: /**
202: * @inheritDoc
203: */
204: public function push(...$items)
205: {
206: if (!$items) {
207: return $this;
208: }
209: $_items = $this->Items;
210: array_push($_items, ...$items);
211: return $this->replaceItems($_items, true);
212: }
213:
214: /**
215: * @inheritDoc
216: */
217: public function pop(&$last = null)
218: {
219: if (!$this->Items) {
220: $last = null;
221: return $this;
222: }
223: $items = $this->Items;
224: $last = array_pop($items);
225: return $this->replaceItems($items);
226: }
227:
228: /**
229: * @inheritDoc
230: */
231: public function shift(&$first = null)
232: {
233: if (!$this->Items) {
234: $first = null;
235: return $this;
236: }
237: $items = $this->Items;
238: $first = reset($items);
239: unset($items[key($items)]);
240: return $this->replaceItems($items);
241: }
242:
243: /**
244: * @inheritDoc
245: */
246: public function unshift(...$items)
247: {
248: if (!$items) {
249: return $this;
250: }
251: $_items = $this->Items;
252: array_unshift($_items, ...$items);
253: return $this->replaceItems($_items, true);
254: }
255:
256: // Partial implementation of `ArrayAccess`:
257:
258: /**
259: * @param TKey|null $offset
260: * @param TValue $value
261: */
262: public function offsetSet($offset, $value): void
263: {
264: $items = $this->Items;
265: if ($offset === null) {
266: $items[] = $value;
267: $this->replaceItems($items, false, false);
268: } else {
269: $items[$offset] = $value;
270: $this->maybeReplaceItems($items, false, false);
271: }
272: }
273:
274: /**
275: * @param TKey $offset
276: */
277: public function offsetUnset($offset): void
278: {
279: $items = $this->Items;
280: unset($items[$offset]);
281: $this->maybeReplaceItems($items, false, false);
282: }
283:
284: // --
285:
286: /**
287: * @param array<TKey,TValue> $items
288: * @return static
289: */
290: protected function maybeReplaceItems(array $items, bool $trustKeys = false, bool $getClone = true)
291: {
292: if ($items === $this->Items) {
293: return $this;
294: }
295: return $this->replaceItems($items, $trustKeys, $getClone);
296: }
297:
298: /**
299: * @param array<TKey,TValue> $items
300: * @return static
301: */
302: protected function replaceItems(array $items, bool $trustKeys = false, bool $getClone = true)
303: {
304: $clone = $getClone
305: ? clone $this
306: : $this;
307: $clone->Items = $items;
308: $clone->handleItemsReplaced();
309: return $clone;
310: }
311:
312: /**
313: * Called when items in the collection are replaced
314: */
315: protected function handleItemsReplaced(): void {}
316: }
317: