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: * @template TKeyless
13: *
14: * @phpstan-require-implements CollectionInterface
15: */
16: trait CollectionTrait
17: {
18: /** @use ReadOnlyCollectionTrait<TKey,TValue> */
19: use ReadOnlyCollectionTrait;
20:
21: /**
22: * @inheritDoc
23: */
24: public function set($key, $value)
25: {
26: $items = $this->Items;
27: $items[$key] = $value;
28: return $this->maybeReplaceItems($items);
29: }
30:
31: /**
32: * @inheritDoc
33: */
34: public function unset($key)
35: {
36: if (!array_key_exists($key, $this->Items)) {
37: return $this;
38: }
39: $items = $this->Items;
40: unset($items[$key]);
41: return $this->replaceItems($items);
42: }
43:
44: /**
45: * @inheritDoc
46: */
47: public function add($value)
48: {
49: $items = $this->Items;
50: $items[] = $value;
51: /** @var TKeyless */
52: return $this->replaceItems($items, true);
53: }
54:
55: /**
56: * @inheritDoc
57: */
58: public function merge($items)
59: {
60: $items = $this->getItemsArray($items);
61: if (!$items) {
62: return $this;
63: }
64: // array_merge() can't be used here because it renumbers numeric keys
65: $merged = $this->Items;
66: foreach ($items as $key => $item) {
67: if (is_int($key)) {
68: $merged[] = $item;
69: continue;
70: }
71: $merged[$key] = $item;
72: }
73: return $this->maybeReplaceItems($merged);
74: }
75:
76: /**
77: * @inheritDoc
78: */
79: public function sort()
80: {
81: $items = $this->Items;
82: uasort($items, fn($a, $b) => $this->compareItems($a, $b));
83: return $this->maybeReplaceItems($items);
84: }
85:
86: /**
87: * @inheritDoc
88: */
89: public function reverse()
90: {
91: $items = array_reverse($this->Items, true);
92: return $this->maybeReplaceItems($items);
93: }
94:
95: /**
96: * @inheritDoc
97: */
98: public function map(callable $callback, int $mode = CollectionInterface::CALLBACK_USE_VALUE)
99: {
100: $prev = null;
101: $item = null;
102: $key = null;
103:
104: foreach ($this->Items as $nextKey => $nextValue) {
105: $next = $this->getCallbackValue($mode, $nextKey, $nextValue);
106: if ($item !== null) {
107: /** @var TKey $key */
108: $items[$key] = $callback($item, $next, $prev);
109: }
110: $prev = $item;
111: $item = $next;
112: $key = $nextKey;
113: }
114: if ($item !== null) {
115: /** @var TKey $key */
116: $items[$key] = $callback($item, null, $prev);
117: }
118:
119: // @phpstan-ignore argument.type, return.type
120: return $this->maybeReplaceItems($items ?? [], true);
121: }
122:
123: /**
124: * @inheritDoc
125: */
126: public function filter(callable $callback, int $mode = CollectionInterface::CALLBACK_USE_VALUE)
127: {
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 && $callback($item, $next, $prev)) {
136: /** @var TKey $key */
137: /** @var TValue $value */
138: $items[$key] = $value;
139: }
140: $prev = $item;
141: $item = $next;
142: $key = $nextKey;
143: $value = $nextValue;
144: }
145: if ($item !== null && $callback($item, null, $prev)) {
146: /** @var TKey $key */
147: /** @var TValue $value */
148: $items[$key] = $value;
149: }
150:
151: return $this->maybeReplaceItems($items ?? []);
152: }
153:
154: /**
155: * @inheritDoc
156: */
157: public function only(array $keys)
158: {
159: $items = array_intersect_key($this->Items, array_flip($keys));
160: return $this->maybeReplaceItems($items);
161: }
162:
163: /**
164: * @inheritDoc
165: */
166: public function onlyIn(array $index)
167: {
168: $items = array_intersect_key($this->Items, $index);
169: return $this->maybeReplaceItems($items);
170: }
171:
172: /**
173: * @inheritDoc
174: */
175: public function except(array $keys)
176: {
177: $items = array_diff_key($this->Items, array_flip($keys));
178: return $this->maybeReplaceItems($items);
179: }
180:
181: /**
182: * @inheritDoc
183: */
184: public function exceptIn(array $index)
185: {
186: $items = array_diff_key($this->Items, $index);
187: return $this->maybeReplaceItems($items);
188: }
189:
190: /**
191: * @inheritDoc
192: */
193: public function slice(int $offset, ?int $length = null)
194: {
195: $items = array_slice($this->Items, $offset, $length, true);
196: return $this->maybeReplaceItems($items);
197: }
198:
199: /**
200: * @inheritDoc
201: */
202: public function push(...$items)
203: {
204: if (!$items) {
205: /** @var TKeyless */
206: return $this;
207: }
208: $_items = $this->Items;
209: array_push($_items, ...$items);
210: /** @var TKeyless */
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: /** @var TKeyless */
250: return $this;
251: }
252: $_items = $this->Items;
253: array_unshift($_items, ...$items);
254: /** @var TKeyless */
255: return $this->replaceItems($_items, true);
256: }
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: * @param array<TKey,TValue> $items
286: * @return static
287: */
288: protected function maybeReplaceItems(array $items, bool $trustKeys = false, bool $getClone = true)
289: {
290: if ($items === $this->Items) {
291: return $this;
292: }
293: return $this->replaceItems($items, $trustKeys, $getClone);
294: }
295:
296: /**
297: * @param array<TKey,TValue> $items
298: * @return static
299: */
300: protected function replaceItems(array $items, bool $trustKeys = false, bool $getClone = true)
301: {
302: $clone = $getClone
303: ? clone $this
304: : $this;
305: $clone->Items = $items;
306: $clone->handleItemsReplaced();
307: return $clone;
308: }
309:
310: /**
311: * Called when items in the collection are replaced
312: */
313: protected function handleItemsReplaced(): void {}
314: }
315: