Completed
Branch master (91fc81)
by Lars
22:28
created

CollectionMethods::internalSet()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 35
ccs 0
cts 20
cp 0
rs 8.439
cc 5
eloc 18
nc 7
nop 3
crap 30
1
<?php
2
3
/*
4
 * This file is part of Underscore.php
5
 *
6
 * (c) Maxime Fabre <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Arrayy;
13
14
use Closure;
15
16
/**
17
 * Abstract Collection type
18
 * Methods that apply to both objects and arrays.
19
 */
20
abstract class CollectionMethods
21
{
22
  ////////////////////////////////////////////////////////////////////
23
  ///////////////////////////// ANALYZE //////////////////////////////
24
  ////////////////////////////////////////////////////////////////////
25
26
  /**
27
   * Check if an array has a given key.
28
   *
29
   * @param array $array
30
   * @param mixed $key
31
   *
32
   * @return bool
33
   */
34
  public static function has(array &$array, $key)
35
  {
36
    // Generate unique string to use as marker
37
    $unfound = (string)uniqid(random_int(0, 999), true);
38
39
    return static::get($array, $key, $unfound) !== $unfound;
40
  }
41
42
  ////////////////////////////////////////////////////////////////////
43
  //////////////////////////// FETCH FROM ////////////////////////////
44
  ////////////////////////////////////////////////////////////////////
45
46
  /**
47
   * Get a value from an collection using dot-notation.
48
   *
49
   * @param array  $collection The collection to get from
50
   * @param string $key        The key to look for
51
   * @param mixed  $default    Default value to fallback to
52
   *
53
   * @return mixed
54
   */
55
  public static function get(&$collection, $key, $default = null)
56
  {
57
    if (is_null($key)) {
58
      return $collection;
59
    }
60
61
    $collection = (array)$collection;
62
63
    if (isset($collection[$key])) {
64
      return $collection[$key];
65
    }
66
67
    // Crawl through collection, get key according to object or not
68
    foreach (explode('.', $key) as $segment) {
69
      $collection = (array)$collection;
70
71
      if (!isset($collection[$segment])) {
72
        return $default instanceof Closure ? $default() : $default;
73
      }
74
75
      $collection = $collection[$segment];
76
    }
77
78
    return $collection;
79
  }
80
81
  /**
82
   * Set a value in a collection using dot notation.
83
   *
84
   * @param mixed  $collection The collection
85
   * @param string $key        The key to set
86
   * @param mixed  $value      Its value
87
   *
88
   * @return mixed
89
   */
90
  public static function set($collection, $key, $value)
91
  {
92
    static::internalSet($collection, $key, $value);
93
94
    return $collection;
95
  }
96
97
  /**
98
   * Get a value from a collection and set it if it wasn't.
99
   *
100
   * @param mixed  $collection The collection
101
   * @param string $key        The key
102
   * @param mixed  $default    The default value to set if it isn't
103
   *
104
   * @return mixed
105
   */
106
  public static function setAndGet(&$collection, $key, $default = null)
107
  {
108
    // If the key doesn't exist, set it
109
    if (!static::has($collection, $key)) {
110
      $collection = static::set($collection, $key, $default);
111
    }
112
113
    return static::get($collection, $key);
114
  }
115
116
  /**
117
   * Remove a value from an array using dot notation.
118
   *
119
   * @param $collection
120
   * @param $key
121
   *
122
   * @return mixed
123
   */
124
  public static function remove($collection, $key)
125
  {
126
    // Recursive call
127
    if (is_array($key)) {
128
      foreach ($key as $k) {
129
        static::internalRemove($collection, $k);
130
      }
131
132
      return $collection;
133
    }
134
135
    static::internalRemove($collection, $key);
136
137
    return $collection;
138
  }
139
140
  /**
141
   * Fetches all columns $property from a multimensionnal array.
142
   *
143
   * @param $collection
144
   * @param $property
145
   *
146
   * @return array|object
147
   */
148
  public static function pluck($collection, $property)
149
  {
150
    $plucked = array_map(
151
        function ($value) use ($property) {
152
          return Arrayy::get($value, $property);
153
        }, (array)$collection
154
    );
155
156
    // Convert back to object if necessary
157
    if (is_object($collection)) {
158
      $plucked = (object)$plucked;
159
    }
160
161
    return $plucked;
162
  }
163
164
  /**
165
   * Filters an array of objects (or a numeric array of associative arrays) based on the value of a particular property
166
   * within that.
167
   *
168
   * @param      $collection
169
   * @param      $property
170
   * @param      $value
171
   * @param null $comparisonOp
172
   *
173
   * @return array|object
174
   */
175
  public static function filterBy($collection, $property, $value, $comparisonOp = null)
176
  {
177
    if (!$comparisonOp) {
178
      $comparisonOp = is_array($value) ? 'contains' : 'eq';
179
    }
180
    $ops = array(
181
        'eq'          => function ($item, $prop, $value) {
182
          return $item[$prop] === $value;
183
        },
184
        'gt'          => function ($item, $prop, $value) {
185
          return $item[$prop] > $value;
186
        },
187
        'gte'         => function ($item, $prop, $value) {
188
          return $item[$prop] >= $value;
189
        },
190
        'lt'          => function ($item, $prop, $value) {
191
          return $item[$prop] < $value;
192
        },
193
        'lte'         => function ($item, $prop, $value) {
194
          return $item[$prop] <= $value;
195
        },
196
        'ne'          => function ($item, $prop, $value) {
197
          return $item[$prop] !== $value;
198
        },
199
        'contains'    => function ($item, $prop, $value) {
200
          return in_array($item[$prop], (array)$value, true);
201
        },
202
        'notContains' => function ($item, $prop, $value) {
203
          return !in_array($item[$prop], (array)$value, true);
204
        },
205
        'newer'       => function ($item, $prop, $value) {
206
          return strtotime($item[$prop]) > strtotime($value);
207
        },
208
        'older'       => function ($item, $prop, $value) {
209
          return strtotime($item[$prop]) < strtotime($value);
210
        },
211
    );
212
213
    $result = array_values(
214
        array_filter(
215
            (array)$collection, function ($item) use (
216
            $property,
217
            $value,
218
            $ops,
219
            $comparisonOp
220
        ) {
221
          $item = (array)$item;
222
          $item[$property] = static::get($item, $property, array());
223
224
          return $ops[$comparisonOp]($item, $property, $value);
225
        }
226
        )
227
    );
228
    if (is_object($collection)) {
229
      $result = (object)$result;
230
    }
231
232
    return $result;
233
  }
234
235
  /**
236
   * find by ...
237
   *
238
   * @param        $collection
239
   * @param        $property
240
   * @param        $value
241
   * @param string $comparisonOp
242
   *
243
   * @return Arrayy
244
   */
245
  public static function findBy($collection, $property, $value, $comparisonOp = 'eq')
246
  {
247
    $filtered = static::filterBy($collection, $property, $value, $comparisonOp);
248
249
    $arrayy = new Arrayy($filtered);
0 ignored issues
show
Bug introduced by
It seems like $filtered defined by static::filterBy($collec... $value, $comparisonOp) on line 247 can also be of type object; however, Arrayy\Arrayy::__construct() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
250
251
    return $arrayy->first();
252
  }
253
254
  ////////////////////////////////////////////////////////////////////
255
  ///////////////////////////// ANALYZE //////////////////////////////
256
  ////////////////////////////////////////////////////////////////////
257
258
  /**
259
   * Get all keys from a collection.
260
   *
261
   * @param $collection
262
   *
263
   * @return array
264
   */
265
  public static function keys($collection)
266
  {
267
    return array_keys((array)$collection);
268
  }
269
270
  /**
271
   * Get all values from a collection.
272
   *
273
   * @param $collection
274
   *
275
   * @return array
276
   */
277
  public static function values($collection)
278
  {
279
    return array_values((array)$collection);
280
  }
281
282
  ////////////////////////////////////////////////////////////////////
283
  ////////////////////////////// ALTER ///////////////////////////////
284
  ////////////////////////////////////////////////////////////////////
285
286
  /**
287
   * Replace a key with a new key/value pair.
288
   *
289
   * @param $collection
290
   * @param $replace
291
   * @param $key
292
   * @param $value
293
   *
294
   * @return mixed
295
   */
296
  public static function replace($collection, $replace, $key, $value)
297
  {
298
    $collection = static::remove($collection, $replace);
299
    $collection = static::set($collection, $key, $value);
300
301
    return $collection;
302
  }
303
304
  /**
305
   * Sort a collection by value, by a closure or by a property
306
   * If the sorter is null, the collection is sorted naturally.
307
   *
308
   * @param        $collection
309
   * @param null   $sorter
310
   * @param string $direction
311
   *
312
   * @return array
313
   */
314
  public static function sort($collection, $sorter = null, $direction = 'asc')
315
  {
316
    $collection = (array)$collection;
317
318
    // Get correct PHP constant for direction
319
    $direction = strtolower($direction);
320
321
    if ($direction === 'desc') {
322
      $directionType = SORT_DESC;
323
    } else {
324
      $directionType = SORT_ASC;
325
    }
326
327
    // Transform all values into their results
328
    if ($sorter) {
329
      $arrayy = new Arrayy($collection);
330
331
      $results = $arrayy->each(
332
          function ($value) use ($sorter) {
333
            return is_callable($sorter) ? $sorter($value) : Arrayy::get($value, $sorter);
334
          }
335
      );
336
    } else {
337
      $results = $collection;
338
    }
339
340
    // Sort by the results and replace by original values
341
    array_multisort($results, $directionType, SORT_REGULAR, $collection);
342
343
    return $collection;
344
  }
345
346
  /**
347
   * Group values from a collection according to the results of a closure.
348
   *
349
   * @param      $collection
350
   * @param      $grouper
351
   * @param bool $saveKeys
352
   *
353
   * @return array
354
   */
355
  public static function group($collection, $grouper, $saveKeys = false)
356
  {
357
    $collection = (array)$collection;
358
    $result = array();
359
360
    // Iterate over values, group by property/results from closure
361
    foreach ($collection as $key => $value) {
362
      $groupKey = is_callable($grouper) ? $grouper($value, $key) : Arrayy::get($value, $grouper);
363
      $newValue = static::get($result, $groupKey);
364
365
      // Add to results
366
      if ($groupKey !== null && $saveKeys) {
367
        $result[$groupKey] = $newValue;
368
        $result[$groupKey][$key] = $value;
369
      } elseif ($groupKey !== null) {
370
        $result[$groupKey] = $newValue;
371
        $result[$groupKey][] = $value;
372
      }
373
    }
374
375
    return $result;
376
  }
377
378
  ////////////////////////////////////////////////////////////////////
379
  ////////////////////////////// HELPERS /////////////////////////////
380
  ////////////////////////////////////////////////////////////////////
381
382
  /**
383
   * Internal mechanic of set method.
384
   *
385
   * @param $collection
386
   * @param $key
387
   * @param $value
388
   *
389
   * @return mixed
390
   */
391
  protected static function internalSet(&$collection, $key, $value)
392
  {
393
    if (is_null($key)) {
394
      /** @noinspection OneTimeUseVariablesInspection */
395
      $collection = $value;
396
397
      return $collection;
398
    }
399
400
    // Explode the keys
401
    $keys = explode('.', $key);
402
403
    // Crawl through the keys
404
    while (count($keys) > 1) {
405
      $key = array_shift($keys);
406
407
      // If we're dealing with an object
408
      if (is_object($collection)) {
409
        $collection->$key = static::get($collection, $key, array());
0 ignored issues
show
Documentation introduced by
$collection is of type object, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
410
        $collection = &$collection->$key;
411
        // If we're dealing with an array
412
      } else {
413
        $collection[$key] = static::get($collection, $key, array());
414
        $collection = &$collection[$key];
415
      }
416
    }
417
418
    // Bind final tree on the collection
419
    $key = array_shift($keys);
420
    if (is_array($collection)) {
421
      $collection[$key] = $value;
422
    } else {
423
      $collection->$key = $value;
424
    }
425
  }
426
427
  /**
428
   * Internal mechanics of remove method.
429
   *
430
   * @param $collection
431
   * @param $key
432
   *
433
   * @return bool
434
   */
435
  protected static function internalRemove(&$collection, $key)
436
  {
437
    // Explode keys
438
    $keys = explode('.', $key);
439
440
    // Crawl though the keys
441
    while (count($keys) > 1) {
442
      $key = array_shift($keys);
443
444
      if (!static::has($collection, $key)) {
445
        return false;
446
      }
447
448
      // If we're dealing with an object
449
      if (is_object($collection)) {
450
        $collection = &$collection->$key;
451
        // If we're dealing with an array
452
      } else {
453
        $collection = &$collection[$key];
454
      }
455
    }
456
457
    $key = array_shift($keys);
458
459
    if (is_object($collection)) {
460
      unset($collection->$key);
461
    } else {
462
      unset($collection[$key]);
463
    }
464
  }
465
466
  /**
467
   * Given a list, and an iteratee function that returns
468
   * a key for each element in the list (or a property name),
469
   * returns an object with an index of each item.
470
   * Just like groupBy, but for when you know your keys are unique.
471
   *
472
   * @param array $array
473
   * @param mixed $key
474
   *
475
   * @return array
476
   */
477
  public static function indexBy(array $array, $key)
478
  {
479
    $results = array();
480
481
    foreach ($array as $a) {
482
      if (isset($a[$key])) {
483
        $results[$a[$key]] = $a;
484
      }
485
    }
486
487
    return $results;
488
  }
489
}
490