1 <?php
2
3 namespace Docolight\Support;
4
5 use CActiveRecord;
6 use Closure;
7 use Docolight\Support\Traits\Macroable;
8
9 /**
10 * Array helper.
11 *
12 * @author Krisan Alfa Timur <krisanalfa@docotel.co.id>
13 */
14 class Arr
15 {
16 use Macroable;
17
18 protected static $fetchedRelated = array();
19
20 /**
21 * Add an element to an array using "dot" notation if it doesn't exist.
22 *
23 * @param array $array
24 * @param string $key
25 * @param mixed $value
26 *
27 * @return array
28 */
29 public static function add($array, $key, $value)
30 {
31 if (is_null(static::get($array, $key))) {
32 static::set($array, $key, $value);
33 }
34
35 return $array;
36 }
37
38 /**
39 * Build a new array using a callback.
40 *
41 * @param array $array
42 * @param callable $callback
43 *
44 * @return array
45 */
46 public static function build($array, callable $callback)
47 {
48 $results = [];
49
50 foreach ($array as $key => $value) {
51 list($innerKey, $innerValue) = call_user_func($callback, $key, $value);
52
53 $results[$innerKey] = $innerValue;
54 }
55
56 return $results;
57 }
58
59 /**
60 * Collapse an array of arrays into a single array.
61 *
62 * @param array|\ArrayAccess $array
63 *
64 * @return array
65 */
66 public static function collapse($array)
67 {
68 $results = [];
69
70 foreach ($array as $values) {
71 if ($values instanceof Collection) {
72 $values = $values->all();
73 }
74
75 $results = array_merge($results, $values);
76 }
77
78 return $results;
79 }
80
81 /**
82 * Divide an array into two arrays. One with keys and the other with values.
83 *
84 * @param array $array
85 *
86 * @return array
87 */
88 public static function divide($array)
89 {
90 return [array_keys($array), array_values($array)];
91 }
92
93 /**
94 * Flatten a multi-dimensional associative array with dots.
95 *
96 * @param array $array
97 * @param string $prepend
98 *
99 * @return array
100 */
101 public static function dot($array, $prepend = '')
102 {
103 $results = [];
104
105 foreach ($array as $key => $value) {
106 if (is_array($value)) {
107 $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
108 } else {
109 $results[$prepend.$key] = $value;
110 }
111 }
112
113 return $results;
114 }
115
116 /**
117 * Get all of the given array except for a specified array of items.
118 *
119 * @param array $array
120 * @param array|string $keys
121 *
122 * @return array
123 */
124 public static function except($array, $keys)
125 {
126 static::forget($array, $keys);
127
128 return $array;
129 }
130
131 /**
132 * Return the first element in an array passing a given truth test.
133 *
134 * @param array $array
135 * @param callable $callback
136 * @param mixed $default
137 *
138 * @return mixed
139 */
140 public static function first(array $array, callable $callback = null, $default = null)
141 {
142 if ($callback === null) {
143 return reset($array);
144 }
145
146 foreach ($array as $key => $value) {
147 if (call_user_func($callback, $key, $value)) {
148 return $value;
149 }
150 }
151
152 return value($default);
153 }
154
155 /**
156 * Return the last element in an array passing a given truth test.
157 *
158 * @param array $array
159 * @param callable $callback
160 * @param mixed $default
161 *
162 * @return mixed
163 */
164 public static function last(array $array, callable $callback = null, $default = null)
165 {
166 if ($callback === null) {
167 return end($array);
168 }
169
170 return static::first(array_reverse($array), $callback, $default);
171 }
172
173 /**
174 * Flatten a multi-dimensional array into a single level.
175 *
176 * @param array $array
177 *
178 * @return array
179 */
180 public static function flatten($array)
181 {
182 $return = [];
183
184 array_walk_recursive($array, function ($x) use (&$return) { $return[] = $x; });
185
186 return $return;
187 }
188
189 /**
190 * Remove one or many array items from a given array using "dot" notation.
191 *
192 * @param array $array
193 * @param array|string $keys
194 */
195 public static function forget(&$array, $keys)
196 {
197 $original = &$array;
198
199 foreach ((array) $keys as $key) {
200 $parts = explode('.', $key);
201
202 while (count($parts) > 1) {
203 $part = array_shift($parts);
204
205 if (isset($array[$part]) and is_array($array[$part])) {
206 $array = &$array[$part];
207 }
208 }
209
210 unset($array[array_shift($parts)]);
211
212 // clean up after each pass
213 $array = &$original;
214 }
215 }
216
217 /**
218 * Get an item from an array using "dot" notation.
219 *
220 * @param array $array
221 * @param string $key
222 * @param mixed $default
223 *
224 * @return mixed
225 */
226 public static function get($array, $key, $default = null)
227 {
228 if (is_null($key)) {
229 return $array;
230 }
231
232 if (isset($array[$key])) {
233 return $array[$key];
234 }
235
236 foreach (explode('.', $key) as $segment) {
237 if (!is_array($array) or !array_key_exists($segment, $array)) {
238 return value($default);
239 }
240
241 $array = $array[$segment];
242 }
243
244 return $array;
245 }
246
247 /**
248 * Check if an item exists in an array using "dot" notation.
249 *
250 * @param array $array
251 * @param string $key
252 *
253 * @return bool
254 */
255 public static function has($array, $key)
256 {
257 if (empty($array) or is_null($key)) {
258 return false;
259 }
260
261 if (array_key_exists($key, $array)) {
262 return true;
263 }
264
265 foreach (explode('.', $key) as $segment) {
266 if (!is_array($array) or !array_key_exists($segment, $array)) {
267 return false;
268 }
269
270 $array = $array[$segment];
271 }
272
273 return true;
274 }
275
276 /**
277 * Get a subset of the items from the given array.
278 *
279 * @param array $array
280 * @param array|string $keys
281 *
282 * @return array
283 */
284 public static function only($array, $keys)
285 {
286 return array_intersect_key($array, array_flip((array) $keys));
287 }
288
289 /**
290 * Pluck an array of values from an array.
291 *
292 * @param array $array
293 * @param string|array $value
294 * @param string|array|null $key
295 *
296 * @return array
297 */
298 public static function pluck($array, $value, $key = null)
299 {
300 $results = [];
301
302 list($value, $key) = static::explodePluckParameters($value, $key);
303
304 foreach ($array as $item) {
305 $itemValue = data_get($item, $value);
306
307 // If the key is "null", we will just append the value to the array and keep
308 // looping. Otherwise we will key the array using the value of the key we
309 // received from the developer. Then we'll return the final array form.
310 if (is_null($key)) {
311 $results[] = $itemValue;
312 } else {
313 $itemKey = data_get($item, $key);
314
315 $results[$itemKey] = $itemValue;
316 }
317 }
318
319 return $results;
320 }
321
322 /**
323 * Explode the "value" and "key" arguments passed to "pluck".
324 *
325 * @param string|array $value
326 * @param string|array|null $key
327 *
328 * @return array
329 */
330 protected static function explodePluckParameters($value, $key)
331 {
332 $value = is_array($value) ? $value : explode('.', $value);
333
334 $key = is_null($key) or is_array($key) ? $key : explode('.', $key);
335
336 return [$value, $key];
337 }
338
339 /**
340 * Get a value from the array, and remove it.
341 *
342 * @param array $array
343 * @param string $key
344 * @param mixed $default
345 *
346 * @return mixed
347 */
348 public static function pull(&$array, $key, $default = null)
349 {
350 $value = static::get($array, $key, $default);
351
352 static::forget($array, $key);
353
354 return $value;
355 }
356
357 /**
358 * Set an array item to a given value using "dot" notation.
359 *
360 * If no key is given to the method, the entire array will be replaced.
361 *
362 * @param array $array
363 * @param string $key
364 * @param mixed $value
365 *
366 * @return array
367 */
368 public static function set(&$array, $key, $value)
369 {
370 if (is_null($key)) {
371 return $array = $value;
372 }
373
374 $keys = explode('.', $key);
375
376 while (count($keys) > 1) {
377 $key = array_shift($keys);
378
379 // If the key doesn't exist at this depth, we will just create an empty array
380 // to hold the next value, allowing us to create the arrays to hold final
381 // values at the correct depth. Then we'll keep digging into the array.
382 if (!isset($array[$key]) or !is_array($array[$key])) {
383 $array[$key] = [];
384 }
385
386 $array = &$array[$key];
387 }
388
389 $array[array_shift($keys)] = $value;
390
391 return $array;
392 }
393
394 /**
395 * Sort the array using the given callback.
396 *
397 * @param array $array
398 * @param callable $callback
399 *
400 * @return array
401 */
402 public static function sort($array, callable $callback)
403 {
404 return Collection::make($array)->sortBy($callback)->all();
405 }
406
407 /**
408 * Filter the array using the given callback.
409 *
410 * @param array $array
411 * @param callable $callback
412 *
413 * @return array
414 */
415 public static function where($array, callable $callback)
416 {
417 $filtered = [];
418
419 foreach ($array as $key => $value) {
420 if (call_user_func($callback, $key, $value)) {
421 $filtered[$key] = $value;
422 }
423 }
424
425 return $filtered;
426 }
427
428 /**
429 * Convert `CActiveRecord` implemetation to array, including all of it's relation.
430 *
431 * @param CActiveRecord $model Your model implementation.
432 * @param bool $withRelation Choose whether to fetch with your relation too or not.
433 * @param bool $store Store relation name in a temporary container. Useful to prefent infinite loop.
434 *
435 * @return array
436 *
437 * @link http://www.yiiframework.com/doc/api/1.1/CActiveRecord CActiveRecord is the base class for classes representing relational data.
438 */
439 public static function arToArray(CActiveRecord $model, $withRelation = true, $store = true)
440 {
441 $return = $model->getAttributes();
442
443 if ($withRelation) {
444 if ($store) {
445 static::$fetchedRelated[] = lcfirst(get_class($model));
446 }
447
448 foreach ($model->relations() as $relationName => $relationConfiguration) {
449 if (! in_array($relationName, static::$fetchedRelated)) {
450 if ($store) {
451 static::$fetchedRelated[] = $relationName;
452 }
453
454 $relation = $model->getRelated($relationName);
455
456 if ($relation instanceof CActiveRecord) {
457 $return[$relationName] = static::arToArray($relation, true);
458 } elseif (is_array($relation) and ! empty($relation)) {
459 $return[$relationName] = array();
460
461 foreach ($relation as $key => $relationModel) {
462 if ($relationModel instanceof CActiveRecord) {
463 $return[$relationName][] = static::arToArray($relationModel, true, false);
464 }
465 }
466 }
467 }
468 }
469 }
470
471 return $return;
472 }
473
474 /**
475 * Flip your array, mostly comes from stackable form like this:.
476 *
477 * ```php
478 * <input name="first-name[]" />
479 * <input name="last-name[]" />
480 *
481 * // It will produce $_POST array like this:
482 *
483 * [
484 * "first-name" => ["Ganesha", "Krisan", "Farid"],
485 * "last-name" => ["Muharso", "Timur", "Hidayat"] ]
486 *
487 * // This method will convert the array into this form:
488 *
489 * [
490 *
491 * [
492 * "first-name" => "Ganesha",
493 * "last-name" => "Muharso" ],
494 *
495 * [
496 * "first-name" => "Krisan",
497 * "last-name" => "Timur" ],
498 *
499 * [
500 * "first-name" => "Farid",
501 * "last-name" => "Hidayat" ]
502 * ]
503 *
504 * // So you can loop them, and save them to your model via:
505 *
506 * foreach(Arr::group($personInput) as $person) {
507 * $model = new User;
508 *
509 * $model->set($person)->save();
510 * }
511 * ```
512 *
513 * @param array $array
514 *
515 * @return array
516 */
517 public static function group(array $array)
518 {
519 $data = array();
520
521 for ($i = 0; $i < count(reset($array)); $i++) {
522 $data[$i] = array();
523
524 foreach ($array as $key => $value) {
525 $data[$i][$key] = $value[$i];
526 }
527 }
528
529 return $data;
530 }
531
532 /**
533 * Group array based on return from closure.
534 *
535 * @param array $array
536 * @param Closure $callback
537 *
538 * @return array
539 */
540 public static function groupBy(array $array, Closure $callback)
541 {
542 $return = array();
543
544 foreach ($array as $key => $item) {
545 $return[call_user_func($callback, $key, $item)][] = $item;
546 }
547
548 ksort($return, SORT_NUMERIC);
549
550 return $return;
551 }
552
553 /**
554 * Get get array identified by a regex for it's index name.
555 *
556 * @param string $pattern
557 * @param array $input
558 * @param int $flags
559 *
560 * @return array
561 */
562 public static function pregOnly($pattern, array $input, $flags = 0)
563 {
564 return array_intersect_key($input, array_flip(preg_grep($pattern, array_keys($input), $flags)));
565 }
566
567 /**
568 * Replace your array keys.
569 *
570 * ```php
571 * $array = [
572 * ':type_address' => 'Foo',
573 * ':type_citizenship' => 'Bar',
574 * ':type_city' => 'Baz',
575 * ':type_country' => 'Qux' ]
576 *
577 * Arr::replaceKey($array, ':type', 'user')
578 *
579 * // Will produce
580 *
581 * $array = [
582 * 'user_address' => 'Foo',
583 * 'user_citizenship' => 'Bar',
584 * 'user_city' => 'Baz',
585 * 'user_country' => 'Qux' ]
586 * ```
587 *
588 * @param array $input
589 * @param string|callable $search
590 * @param string $replacement
591 *
592 * @return array
593 */
594 public static function replaceKey(array $input, $search, $replacement = '')
595 {
596 $array = array();
597
598 foreach ($input as $key => $value) {
599 if (is_callable($search)) {
600 $array[$search($key)] = $value;
601 } else {
602 $array[preg_replace($search, $replacement, $key)] = $value;
603 }
604 }
605
606 return $array;
607 }
608
609 /**
610 * Replace your array value.
611 *
612 * ```php
613 * $header = [
614 * ':type_address',
615 * ':type_citizenship',
616 * ':type_city',
617 * ':type_country' ]
618 *
619 * Arr::replaceValue($header, ':type_')
620 *
621 * // Will produce:
622 *
623 * $header = [
624 * 'address',
625 * 'citizenship',
626 * 'city',
627 * 'country' ]
628 * ```
629 *
630 * @param array $input
631 * @param string|callable $search
632 * @param string $replacement
633 *
634 * @return array
635 */
636 public static function replaceValue(array $input, $search, $replacement = '')
637 {
638 $array = array();
639
640 foreach ($input as $key => $value) {
641 if (is_callable($search)) {
642 $array[$key] = $search($value);
643 } else {
644 $array[$key] = str_replace($search, $replacement, $value);
645 }
646 }
647
648 return $array;
649 }
650
651 /**
652 * Determine your multidimension array depth.
653 *
654 * @param array $array
655 *
656 * @return int
657 */
658 public static function depth(array $array)
659 {
660 $maxDepth = 1;
661
662 foreach ($array as $value) {
663 if (is_array($value)) {
664 $depth = static::depth($value) + 1;
665
666 if ($depth > $maxDepth) {
667 $maxDepth = $depth;
668 }
669 }
670 }
671
672 return $maxDepth;
673 }
674
675 /**
676 * Determine if array is empty, works on multidimension array.
677 *
678 * @param array $array Array you want to check whether it's empty or not
679 *
680 * @return bool
681 */
682 public static function isEmpty(array $array)
683 {
684 if (static::depth($array) > 0) {
685 $empty = true;
686
687 foreach ($array as $value) {
688 $empty = (empty($value) or is_null($value));
689 }
690
691 return $empty;
692 }
693
694 return empty($array);
695 }
696 }
697