PluginProbe
Metricool – Social media and site statistics / trunk
Metricool – Social media and site statistics vtrunk
2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
metricool / app / Support / Helpers / Collection.php

Collection.php in Metricool – Social media and site statistics trunk, at app/Support/Helpers/Collection.php

359 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Metricool\Support\Helpers;
6
7 use ArrayIterator;
8 use Closure;
9 use IteratorAggregate;
10
11 final class Collection implements IteratorAggregate
12 {
13 /**
14 * The items contained in the collection.
15 */
16 protected array $items = [];
17
18 /**
19 * Create a new collection.
20 */
21 public function __construct(array $items = [])
22 {
23 $this->items = $items;
24 }
25
26 /**
27 * Get all the items in the collection.
28 */
29 public function all(): array
30 {
31 return $this->items;
32 }
33
34 /**
35 * Get first item from the collection
36 * @return mixed|null
37 */
38 public function first()
39 {
40 return reset($this->items) ?: null;
41 }
42
43 public function take(int $count): Collection
44 {
45 return new Collection(array_slice($this->items, 0, $count));
46 }
47
48 /**
49 * Push one or more items to the end of the collection
50 * @param mixed ...$values
51 */
52 public function push(...$values): self
53 {
54 foreach ($values as $value) {
55 $this->items[] = $value;
56 }
57
58 return $this;
59 }
60
61 /**
62 * Sort the collection using the given callback.
63 * @param callable|string|null $callback
64 */
65 public function sortBy($callback = null, bool $descending = false, bool $preserveKeys = false, int $options = SORT_REGULAR): self
66 {
67 $results = [];
68
69 $callback = $this->valueRetriever($callback);
70
71 // First we will loop through the items and get the comparator from a callback
72 // function which we were given. Then, we will sort the returned values and
73 // grab all the corresponding values for the sorted keys from this array.
74 foreach ($this->items as $key => $value) {
75 $results[$key] = $callback($value, $key);
76 }
77
78 $descending ? arsort($results, $options)
79 : asort($results, $options);
80
81 // Once we have sorted all of the keys in the array, we will loop through them
82 // and grab the corresponding model so we can set the underlying items list
83 // to the sorted version. Then we'll just return the collection instance.
84 foreach (array_keys($results) as $key) {
85 $results[$key] = $this->items[$key];
86 }
87
88 if (!$preserveKeys) {
89 $results = array_values($results);
90 }
91
92 return new Collection($results);
93 }
94
95 /**
96 * Sort the collection in ascending order.
97 * @uses sortBy
98 * @param callable|string|null $callback
99 */
100 public function sortByAsc($callback = null, bool $preserveKeys = false, int $options = SORT_REGULAR): self
101 {
102 return $this->sortBy($callback, false, $preserveKeys, $options);
103 }
104
105 /**
106 * Sort the collection in descending order.
107 * @uses sortBy
108 * @param callable|string|null $callback
109 */
110 public function sortByDesc($callback = null, bool $preserveKeys = false, int $options = SORT_REGULAR): self
111 {
112 return $this->sortBy($callback, true, $preserveKeys, $options);
113 }
114
115 /**
116 * Run a filter over each of the items.
117 */
118 public function filter(?callable $callback = null): self
119 {
120 if ($callback) {
121 return new Collection(array_filter($this->items, $callback, ARRAY_FILTER_USE_BOTH));
122 }
123
124 return new Collection(array_filter($this->items));
125 }
126
127 /**
128 * Return all keys of the collection items.
129 */
130 public function keys(): array
131 {
132 return array_keys($this->items);
133 }
134
135 /**
136 * Get the sum of the given values.
137 * @param callable|string|null $callback
138 * @return float|int
139 */
140 public function sum($callback = null)
141 {
142 $callback = is_null($callback)
143 ? $this->closure()
144 : $this->valueRetriever($callback);
145
146 return $this->reduce(function ($result, $item) use ($callback) {
147 return $result + $callback($item);
148 }, 0);
149 }
150
151 /**
152 * Reduce the collection to a single value.
153 * @param mixed $initial
154 * @return mixed
155 */
156 public function reduce(callable $callback, $initial = null)
157 {
158 $result = $initial;
159
160 foreach ($this as $key => $value) {
161 $result = $callback($result, $value, $key);
162 }
163
164 return $result;
165 }
166
167 /**
168 * Pluck an array of values from an array.
169 * @param mixed $value
170 * @param mixed|null $key
171 */
172 public function pluck($value, $key = null): array
173 {
174 $results = [];
175
176 foreach ($this->items as $item) {
177 $itemValue = $this->get($item, $value);
178
179 if (is_null($key)) {
180 $results[] = $itemValue;
181 } else {
182 $itemKey = $this->get($item, $key);
183 $results[$itemKey] = $itemValue;
184 }
185 }
186
187 return $results;
188 }
189
190 /**
191 * Filter items by the given key value pair. Allows shorthands for:
192 * $collection->where('property', 'value'); for a loose comparison check
193 * and
194 * $collection->where('property'); for a loose boolean check
195 * @param mixed $value
196 */
197 public function where(string $key, ?string $operator = null, $value = null): self
198 {
199 return $this->filter($this->operatorForWhere(...func_get_args()));
200 }
201
202 /**
203 * Filter items by the given key/value pairs.
204 */
205 public function whereIn(string $key, array $values): self
206 {
207 return $this->filter(function ($item) use ($key, $values) {
208 return in_array($this->get($item, $key), $values);
209 });
210 }
211
212 /**
213 * Run a map over each of the items.
214 */
215 public function map(callable $callback): self
216 {
217 $keys = array_keys($this->items);
218
219 $items = array_map($callback, $this->items, $keys);
220
221 return new Collection(array_combine($keys, $items));
222 }
223
224 /**
225 * Count the number of items in the collection.
226 */
227 public function count(): int
228 {
229 return count($this->items);
230 }
231
232 /**
233 * Get the collection of items as a plain array.
234 */
235 public function toArray(): array
236 {
237 return $this->map(function ($value) {
238 return is_object($value) && method_exists($value, 'toArray') ? $value->toArray() : $value;
239 })->all();
240 }
241
242 /**
243 * Get a value retrieving callback.
244 * @param callable|string|null $value
245 */
246 protected function valueRetriever($value): callable
247 {
248 return function ($item) use ($value) {
249 return $this->get($item, $value);
250 };
251 }
252
253 /**
254 * Return the default value of the given value.
255 * @param mixed $value
256 * @param mixed ...$args
257 * @return mixed
258 */
259 protected function value($value, ...$args)
260 {
261 return $value instanceof Closure ? $value(...$args) : $value;
262 }
263
264 /**
265 * Get an item from an array or object using "dot" notation.
266 * @param mixed $target
267 * @param string|array|int|null $key
268 * @param mixed $default
269 * @return mixed
270 */
271 protected function get($target, $key = null, $default = null)
272 {
273 if (is_null($key)) {
274 return $target;
275 }
276
277 $key = is_array($key) ? $key : explode('.', $key);
278
279 foreach ($key as $i => $segment) {
280 unset($key[$i]);
281
282 if (is_null($segment)) {
283 return $target;
284 }
285
286 if (is_array($target) && array_key_exists($segment, $target)) {
287 $target = $target[$segment];
288 } elseif (is_object($target) && isset($target->{$segment})) {
289 $target = $target->{$segment};
290 } else {
291 return $this->value($default);
292 }
293 }
294
295 return $target;
296 }
297
298 /**
299 * Make a function that returns what's passed to it.
300 */
301 protected function closure(): Closure
302 {
303 return function ($value) {
304 return $value;
305 };
306 }
307
308 /**
309 * Get an iterator for the items.
310 */
311 public function getIterator(): ArrayIterator
312 {
313 return new ArrayIterator($this->items);
314 }
315
316 /**
317 * Get an operator checker callback.
318 * @param mixed $value
319 */
320 protected function operatorForWhere(string $key, ?string $operator = null, $value = null): Closure
321 {
322 // Allow shorthands
323 if (func_num_args() === 1) {
324 $value = true;
325 $operator = '=';
326 }
327 if (func_num_args() === 2) {
328 $value = $operator;
329 $operator = '=';
330 }
331
332 return function ($item) use ($key, $operator, $value) {
333 $retrieved = $this->get($item, $key);
334
335 switch ($operator) {
336 default:
337 case '=':
338 case '==':
339 return $retrieved == $value;
340 case '!=':
341 case '<>':
342 return $retrieved != $value;
343 case '<':
344 return $retrieved < $value;
345 case '>':
346 return $retrieved > $value;
347 case '<=':
348 return $retrieved <= $value;
349 case '>=':
350 return $retrieved >= $value;
351 case '===':
352 return $retrieved === $value;
353 case '!==':
354 return $retrieved !== $value;
355 }
356 };
357 }
358 }
359