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