PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Support/Helpers/Collection.php +358 -0 2.0.0 → 2.1.1 View file →
@@ -1,0 +1,358 @@
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 +}