Completed
Push — master ( e51320...cdbde6 )
by Lars
06:20
created

ArrayyAbstract::sort()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 31
ccs 0
cts 17
cp 0
rs 8.5806
cc 4
eloc 16
nc 4
nop 2
crap 20
1
<?php
2
3
namespace Arrayy;
4
5
use Closure;
6
7
/**
8
 * Abstract Arrayy
9
 * Methods that apply to both objects and arrays.
10
 */
11
abstract class ArrayyAbstract
12
{
13
  /**
14
   * @var array
15
   */
16
  protected $array = array();
17
18
  ////////////////////////////////////////////////////////////////////
19
  ///////////////////////////// ANALYZE //////////////////////////////
20
  ////////////////////////////////////////////////////////////////////
21
22
  /**
23
   * Check if an array has a given key.
24
   *
25
   * @param mixed $key
26
   *
27
   * @return bool
28
   */
29 9
  public function has($key)
30
  {
31
    // Generate unique string to use as marker.
32 9
    $unFound = (string)uniqid('arrayy', true);
33
34 9
    return $this->get($key, $unFound) !== $unFound;
35
  }
36
37
  ////////////////////////////////////////////////////////////////////
38
  //////////////////////////// FETCH FROM ////////////////////////////
39
  ////////////////////////////////////////////////////////////////////
40
41
  /**
42
   * Get a value from an array (optional using dot-notation).
43
   *
44
   * @param string $key     The key to look for
45
   * @param mixed  $default Default value to fallback to
46
   * @param array  $array   The array to get from,
47
   *                        if it's set to "null" we use the current array from the class
48
   *
49
   * @return mixed
50
   */
51 18
  public function get($key, $default = null, $array = null)
52
  {
53 18
    if (is_array($array) === true) {
54
      $usedArray = $array;
55
    } else {
56 18
      $usedArray = $this->array;
57
    }
58
59 18
    if (null === $key) {
60
      return $usedArray;
61
    }
62
63 18
    if (isset($usedArray[$key])) {
64 10
      return $usedArray[$key];
65
    }
66
67
    // Crawl through array, get key according to object or not
68 8
    foreach (explode('.', $key) as $segment) {
69 8
      if (!isset($usedArray[$segment])) {
70 8
        return $default instanceof Closure ? $default() : $default;
71
      }
72
73
      $usedArray = $usedArray[$segment];
74
    }
75
76
    return $usedArray;
77
  }
78
79
  /**
80
   * Set a value in a array using dot notation.
81
   *
82
   * @param string $key   The key to set
83
   * @param mixed  $value Its value
84
   *
85
   * @return Arrayy
86
   */
87 9
  public function set($key, $value)
88
  {
89 9
    $this->internalSet($key, $value);
90
91 9
    return Arrayy::create($this->array);
92
  }
93
94
  /**
95
   * Get a value from a array and set it if it was not.
96
   *
97
   * @param string $key     The key
98
   * @param mixed  $default The default value to set if it isn't
99
   *
100
   * @return mixed
101
   */
102
  public function setAndGet($key, $default = null)
103
  {
104
    // If the key doesn't exist, set it
105
    if (!$this->has($key)) {
106
      $this->array = $this->set($key, $default)->getArray();
107
    }
108
109
    return $this->get($key);
110
  }
111
112
  /**
113
   * Remove a value from an array using dot notation.
114
   *
115
   * @param $key
116
   *
117
   * @return mixed
118
   */
119
  public function remove($key)
120
  {
121
    // Recursive call
122
    if (is_array($key)) {
123
      foreach ($key as $k) {
124
        $this->internalRemove($k);
125
      }
126
127
      return $this->array;
128
    }
129
130
    $this->internalRemove($key);
131
132
    return $this->array;
133
  }
134
135
  /**
136
   * Fetches all columns $property from a multimensionnal array.
137
   *
138
   * @param $property
139
   *
140
   * @return array
141
   */
142
  public function pluck($property)
143
  {
144
    $plucked = array_map(
145
        function ($value) use ($property) {
146
          return $this->get($property, null, $value);
147
        },
148
        (array)$this->array
149
    );
150
151
    return $plucked;
152
  }
153
154
  /**
155
   * Filters an array of objects (or a numeric array of associative arrays) based on the value of a particular property
156
   * within that.
157
   *
158
   * @param        $property
159
   * @param        $value
160
   * @param string $comparisonOp
161
   *
162
   * @return array
163
   */
164
  public function filterBy($property, $value, $comparisonOp = null)
165
  {
166
    if (!$comparisonOp) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $comparisonOp of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
167
      $comparisonOp = is_array($value) ? 'contains' : 'eq';
168
    }
169
170
    $ops = array(
171
        'eq'          => function ($item, $prop, $value) {
172
          return $item[$prop] === $value;
173
        },
174
        'gt'          => function ($item, $prop, $value) {
175
          return $item[$prop] > $value;
176
        },
177
        'gte'         => function ($item, $prop, $value) {
178
          return $item[$prop] >= $value;
179
        },
180
        'lt'          => function ($item, $prop, $value) {
181
          return $item[$prop] < $value;
182
        },
183
        'lte'         => function ($item, $prop, $value) {
184
          return $item[$prop] <= $value;
185
        },
186
        'ne'          => function ($item, $prop, $value) {
187
          return $item[$prop] !== $value;
188
        },
189
        'contains'    => function ($item, $prop, $value) {
190
          return in_array($item[$prop], (array)$value, true);
191
        },
192
        'notContains' => function ($item, $prop, $value) {
193
          return !in_array($item[$prop], (array)$value, true);
194
        },
195
        'newer'       => function ($item, $prop, $value) {
196
          return strtotime($item[$prop]) > strtotime($value);
197
        },
198
        'older'       => function ($item, $prop, $value) {
199
          return strtotime($item[$prop]) < strtotime($value);
200
        },
201
    );
202
203
    $result = array_values(
204
        array_filter(
205
            (array)$this->array, function ($item) use (
206
            $property,
207
            $value,
208
            $ops,
209
            $comparisonOp
210
        ) {
211
          $item = (array)$item;
212
          $item[$property] = $this->get($property, array(), $item);
213
214
          return $ops[$comparisonOp]($item, $property, $value);
215
        }
216
        )
217
    );
218
219
    return $result;
220
  }
221
222
  /**
223
   * find by ...
224
   *
225
   * @param        $property
226
   * @param        $value
227
   * @param string $comparisonOp
228
   *
229
   * @return array
230
   */
231
  public function findBy($property, $value, $comparisonOp = 'eq')
232
  {
233
    return $this->filterBy($property, $value, $comparisonOp);
234
  }
235
236
  ////////////////////////////////////////////////////////////////////
237
  ///////////////////////////// ANALYZE //////////////////////////////
238
  ////////////////////////////////////////////////////////////////////
239
240
  /**
241
   * Get all keys from the current array.
242
   *
243
   * @return array
244
   */
245
  public function keys()
246
  {
247
    return array_keys((array)$this->array);
248
  }
249
250
  /**
251
   * Get all values from a array.
252
   *
253
   * @return array
254
   */
255
  public function values()
256
  {
257
    return array_values((array)$this->array);
258
  }
259
260
  ////////////////////////////////////////////////////////////////////
261
  ////////////////////////////// ALTER ///////////////////////////////
262
  ////////////////////////////////////////////////////////////////////
263
264
  /**
265
   * Replace a key with a new key/value pair.
266
   *
267
   * @param $replace
268
   * @param $key
269
   * @param $value
270
   *
271
   * @return Arrayy
272
   */
273
  public function replace($replace, $key, $value)
274
  {
275
    $this->remove($replace);
276
277
    return $this->set($key, $value);
278
  }
279
280
  /**
281
   * Sort a array by value, by a closure or by a property
282
   * If the sorter is null, the array is sorted naturally.
283
   *
284
   * @param null   $sorter
285
   * @param string $direction
286
   *
287
   * @return array
288
   */
289
  public function sort($sorter = null, $direction = 'asc')
290
  {
291
    $array = (array)$this->array;
292
293
    // Get correct PHP constant for direction
294
    $direction = strtolower($direction);
295
296
    if ($direction === 'desc') {
297
      $directionType = SORT_DESC;
298
    } else {
299
      $directionType = SORT_ASC;
300
    }
301
302
    // Transform all values into their results
303
    if ($sorter) {
304
      $arrayy = new Arrayy($array);
305
306
      $results = $arrayy->each(
307
          function ($value) use ($sorter) {
308
            return is_callable($sorter) ? $sorter($value) : $this->get($sorter, null, $value);
309
          }
310
      );
311
    } else {
312
      $results = $array;
313
    }
314
315
    // Sort by the results and replace by original values
316
    array_multisort($results, $directionType, SORT_REGULAR, $array);
317
318
    return $array;
319
  }
320
321
  /**
322
   * Group values from a array according to the results of a closure.
323
   *
324
   * @param string $grouper a callable function name
325
   * @param bool   $saveKeys
326
   *
327
   * @return array
328
   */
329
  public function group($grouper, $saveKeys = false)
330
  {
331
    $array = (array)$this->array;
332
    $result = array();
333
334
    // Iterate over values, group by property/results from closure
335
    foreach ($array as $key => $value) {
336
      $groupKey = is_callable($grouper) ? $grouper($value, $key) : $this->get($grouper, null, $value);
337
      $newValue = $this->get($groupKey, null, $result);
338
339
      // Add to results
340
      if ($groupKey !== null) {
341
        if ($saveKeys) {
342
          $result[$groupKey] = $newValue;
343
          $result[$groupKey][$key] = $value;
344
        } else {
345
          $result[$groupKey] = $newValue;
346
          $result[$groupKey][] = $value;
347
        }
348
      }
349
350
    }
351
352
    return $result;
353
  }
354
355
  ////////////////////////////////////////////////////////////////////
356
  ////////////////////////////// HELPERS /////////////////////////////
357
  ////////////////////////////////////////////////////////////////////
358
359
  /**
360
   * Internal mechanic of set method.
361
   *
362
   * @param string $key
363
   * @param mixed  $value
364
   *
365
   * @return mixed
366
   */
367 9
  protected function internalSet($key, $value)
368
  {
369 9
    if (null === $key) {
370
      /** @noinspection OneTimeUseVariablesInspection */
371
      $array = $value;
372
373
      return $array;
374
    }
375
376
    // Explode the keys
377 9
    $keys = explode('.', $key);
378
379
    // Crawl through the keys
380 9 View Code Duplication
    while (count($keys) > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
381
      $key = array_shift($keys);
382
383
      $this->array[$key] = $this->get(array(), null, $key);
0 ignored issues
show
Documentation introduced by
array() is of type array, but the function expects a string.

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...
384
      $this->array = &$this->array[$key];
385
    }
386
387
    // Bind final tree on the array
388 9
    $key = array_shift($keys);
389
390 9
    $this->array[$key] = $value;
391 9
  }
392
393
  /**
394
   * Internal mechanics of remove method.
395
   *
396
   * @param $key
397
   *
398
   * @return boolean
399
   */
400
  protected function internalRemove($key)
401
  {
402
    // Explode keys
403
    $keys = explode('.', $key);
404
405
    // Crawl though the keys
406 View Code Duplication
    while (count($keys) > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
407
      $key = array_shift($keys);
408
409
      if (!$this->has($key)) {
410
        return false;
411
      }
412
413
      $this->array = &$this->array[$key];
414
    }
415
416
    $key = array_shift($keys);
417
418
    unset($this->array[$key]);
419
420
    return true;
421
  }
422
423
  /**
424
   * Given a list, and an iteratee function that returns
425
   * a key for each element in the list (or a property name),
426
   * returns an object with an index of each item.
427
   * Just like groupBy, but for when you know your keys are unique.
428
   *
429
   * @param mixed $key
430
   *
431
   * @return array
432
   */
433
  public function indexBy($key)
434
  {
435
    $results = array();
436
437
    foreach ($this->array as $a) {
438
      if (isset($a[$key])) {
439
        $results[$a[$key]] = $a;
440
      }
441
    }
442
443
    return $results;
444
  }
445
}
446