Completed
Push — master ( 941193...d84682 )
by Lars
02:25
created

ArrayyAbstract   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 427
Duplicated Lines 3.51 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 86.51%

Importance

Changes 6
Bugs 2 Features 4
Metric Value
wmc 39
c 6
b 2
f 4
lcom 1
cbo 1
dl 15
loc 427
ccs 109
cts 126
cp 0.8651
rs 8.2857

15 Methods

Rating   Name   Duplication   Size   Complexity  
A has() 0 7 1
A set() 0 6 1
C get() 0 27 7
A setAndGet() 0 9 2
A remove() 0 15 3
A filterBy() 0 59 3
A findBy() 0 6 1
A keys() 0 6 1
A values() 0 6 1
A replace() 0 6 1
B sort() 0 32 4
B group() 0 25 5
B internalSet() 6 25 3
A internalRemove() 9 22 3
A indexBy() 0 12 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 18
  public function has($key)
30
  {
31
    // Generate unique string to use as marker.
32 18
    $unFound = (string)uniqid('arrayy', true);
33
34 18
    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 31
  public function get($key, $default = null, $array = null)
52
  {
53 31
    if (is_array($array) === true) {
54 3
      $usedArray = $array;
55
    } else {
56 29
      $usedArray = $this->array;
57
    }
58
59 31
    if (null === $key) {
60 1
      return $usedArray;
61
    }
62
63 31
    if (isset($usedArray[$key])) {
64 22
      return $usedArray[$key];
65
    }
66
67
    // Crawl through array, get key according to object or not
68 16
    foreach (explode('.', $key) as $segment) {
69 16
      if (!isset($usedArray[$segment])) {
70 16
        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 14
  public function set($key, $value)
88
  {
89 14
    $this->internalSet($key, $value);
90
91 14
    return Arrayy::create($this->array);
92
  }
93
94
  /**
95
   * Get a value from a array and set it if it was not.
96
   *
97
   * WARNING: this method only set the value, if the $key is not already set
98
   *
99
   * @param string $key     The key
100
   * @param mixed  $default The default value to set if it isn't
101
   *
102
   * @return mixed
103
   */
104 9
  public function setAndGet($key, $default = null)
105
  {
106
    // If the key doesn't exist, set it
107 9
    if (!$this->has($key)) {
108 4
      $this->array = $this->set($key, $default)->getArray();
109
    }
110
111 9
    return $this->get($key);
112
  }
113
114
  /**
115
   * Remove a value from an array using dot notation.
116
   *
117
   * @param mixed $key
118
   *
119
   * @return mixed
120
   */
121 10
  public function remove($key)
122
  {
123
    // Recursive call
124 10
    if (is_array($key)) {
125
      foreach ($key as $k) {
126
        $this->internalRemove($k);
127
      }
128
129
      return $this->array;
130
    }
131
132 10
    $this->internalRemove($key);
133
134 10
    return $this->array;
135
  }
136
137
  /**
138
   * Filters an array of objects (or a numeric array of associative arrays) based on the value of a particular property
139
   * within that.
140
   *
141
   * @param        $property
142
   * @param        $value
143
   * @param string $comparisonOp
144
   *
145
   * @return Arrayy
146
   */
147 1
  public function filterBy($property, $value, $comparisonOp = null)
148
  {
149 1
    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...
150 1
      $comparisonOp = is_array($value) ? 'contains' : 'eq';
151
    }
152
153
    $ops = array(
154
        'eq'          => function ($item, $prop, $value) {
155 1
          return $item[$prop] === $value;
156 1
        },
157
        'gt'          => function ($item, $prop, $value) {
158
          return $item[$prop] > $value;
159 1
        },
160
        'gte'         => function ($item, $prop, $value) {
161
          return $item[$prop] >= $value;
162 1
        },
163
        'lt'          => function ($item, $prop, $value) {
164 1
          return $item[$prop] < $value;
165 1
        },
166
        'lte'         => function ($item, $prop, $value) {
167
          return $item[$prop] <= $value;
168 1
        },
169
        'ne'          => function ($item, $prop, $value) {
170
          return $item[$prop] !== $value;
171 1
        },
172
        'contains'    => function ($item, $prop, $value) {
173 1
          return in_array($item[$prop], (array)$value, true);
174 1
        },
175
        'notContains' => function ($item, $prop, $value) {
176
          return !in_array($item[$prop], (array)$value, true);
177 1
        },
178
        'newer'       => function ($item, $prop, $value) {
179
          return strtotime($item[$prop]) > strtotime($value);
180 1
        },
181
        'older'       => function ($item, $prop, $value) {
182
          return strtotime($item[$prop]) < strtotime($value);
183 1
        },
184
    );
185
186 1
    $result = array_values(
187
        array_filter(
188 1
            (array)$this->array,
189
            function ($item) use (
190 1
                $property,
191 1
                $value,
192 1
                $ops,
193 1
                $comparisonOp
194
            ) {
195 1
              $item = (array)$item;
196 1
              $itemArrayy = new Arrayy($item);
197 1
              $item[$property] = $itemArrayy->get($property, array());
198
199 1
              return $ops[$comparisonOp]($item, $property, $value);
200 1
            }
201
        )
202
    );
203
204 1
    return Arrayy::create($result);
205
  }
206
207
  /**
208
   * find by ...
209
   *
210
   * @param        $property
211
   * @param        $value
212
   * @param string $comparisonOp
213
   *
214
   * @return Arrayy
215
   */
216
  public function findBy($property, $value, $comparisonOp = 'eq')
217
  {
218
    $return = $this->filterBy($property, $value, $comparisonOp);
219
220
    return Arrayy::create($return);
0 ignored issues
show
Documentation introduced by
$return is of type object<Arrayy\Arrayy>, 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...
221
  }
222
223
  ////////////////////////////////////////////////////////////////////
224
  ///////////////////////////// ANALYZE //////////////////////////////
225
  ////////////////////////////////////////////////////////////////////
226
227
  /**
228
   * Get all keys from the current array.
229
   *
230
   * @return Arrayy
231
   */
232 1
  public function keys()
233
  {
234 1
    $return = array_keys((array)$this->array);
235
236 1
    return Arrayy::create($return);
237
  }
238
239
  /**
240
   * Get all values from a array.
241
   *
242
   * @return Arrayy
243
   */
244 1
  public function values()
245
  {
246 1
    $return = array_values((array)$this->array);
247
248 1
    return Arrayy::create($return);
249
  }
250
251
  ////////////////////////////////////////////////////////////////////
252
  ////////////////////////////// ALTER ///////////////////////////////
253
  ////////////////////////////////////////////////////////////////////
254
255
  /**
256
   * Replace a key with a new key/value pair.
257
   *
258
   * @param $replace
259
   * @param $key
260
   * @param $value
261
   *
262
   * @return Arrayy
263
   */
264 1
  public function replace($replace, $key, $value)
265
  {
266 1
    $this->remove($replace);
267
268 1
    return $this->set($key, $value);
269
  }
270
271
  /**
272
   * Sort a array by value, by a closure or by a property
273
   * If the sorter is null, the array is sorted naturally.
274
   *
275
   * @param null   $sorter
276
   * @param string $direction
277
   *
278
   * @return Arrayy
279
   */
280 1
  public function sort($sorter = null, $direction = 'asc')
281
  {
282 1
    $array = (array)$this->array;
283
284
    // Get correct PHP constant for direction
285 1
    $direction = strtolower($direction);
286
287 1
    if ($direction === 'desc') {
288 1
      $directionType = SORT_DESC;
289
    } else {
290 1
      $directionType = SORT_ASC;
291
    }
292
293
    // Transform all values into their results
294 1
    if ($sorter) {
295 1
      $arrayy = new Arrayy($array);
296
297 1
      $results = $arrayy->each(
298 1
          function ($value) use ($sorter) {
299
            // WARNING: $this in Closure work only in php >= 5.4
300 1
            return is_callable($sorter) ? $sorter($value) : $this->get($sorter, null, $value);
301 1
          }
302
      );
303
    } else {
304 1
      $results = $array;
305
    }
306
307
    // Sort by the results and replace by original values
308 1
    array_multisort($results, $directionType, SORT_REGULAR, $array);
309
310 1
    return Arrayy::create($array);
311
  }
312
313
  /**
314
   * Group values from a array according to the results of a closure.
315
   *
316
   * @param string $grouper a callable function name
317
   * @param bool   $saveKeys
318
   *
319
   * @return Arrayy
320
   */
321 3
  public function group($grouper, $saveKeys = false)
322
  {
323 3
    $array = (array)$this->array;
324 3
    $result = array();
325
326
    // Iterate over values, group by property/results from closure
327 3
    foreach ($array as $key => $value) {
328 3
      $groupKey = is_callable($grouper) ? $grouper($value, $key) : $this->get($grouper, null, $value);
329 3
      $newValue = $this->get($groupKey, null, $result);
330
331
      // Add to results
332 3
      if ($groupKey !== null) {
333 2
        if ($saveKeys) {
334 1
          $result[$groupKey] = $newValue;
335 1
          $result[$groupKey][$key] = $value;
336
        } else {
337 1
          $result[$groupKey] = $newValue;
338 3
          $result[$groupKey][] = $value;
339
        }
340
      }
341
342
    }
343
344 3
    return Arrayy::create($result);
345
  }
346
347
  ////////////////////////////////////////////////////////////////////
348
  ////////////////////////////// HELPERS /////////////////////////////
349
  ////////////////////////////////////////////////////////////////////
350
351
  /**
352
   * Internal mechanic of set method.
353
   *
354
   * @param string $key
355
   * @param mixed  $value
356
   *
357
   * @return mixed
358
   */
359 14
  protected function internalSet($key, $value)
360
  {
361 14
    if (null === $key) {
362
      /** @noinspection OneTimeUseVariablesInspection */
363
      $array = $value;
364
365
      return $array;
366
    }
367
368
    // Explode the keys
369 14
    $keys = explode('.', $key);
370
371
    // Crawl through the keys
372 14 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...
373
      $key = array_shift($keys);
374
375
      $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...
376
      $this->array = &$this->array[$key];
377
    }
378
379
    // Bind final tree on the array
380 14
    $key = array_shift($keys);
381
382 14
    $this->array[$key] = $value;
383 14
  }
384
385
  /**
386
   * Internal mechanics of remove method.
387
   *
388
   * @param $key
389
   *
390
   * @return boolean
391
   */
392 10
  protected function internalRemove($key)
393
  {
394
    // Explode keys
395 10
    $keys = explode('.', $key);
396
397
    // Crawl though the keys
398 10 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...
399
      $key = array_shift($keys);
400
401
      if (!$this->has($key)) {
402
        return false;
403
      }
404
405
      $this->array = &$this->array[$key];
406
    }
407
408 10
    $key = array_shift($keys);
409
410 10
    unset($this->array[$key]);
411
412 10
    return true;
413
  }
414
415
  /**
416
   * Given a list, and an iteratee function that returns
417
   * a key for each element in the list (or a property name),
418
   * returns an object with an index of each item.
419
   * Just like groupBy, but for when you know your keys are unique.
420
   *
421
   * @param mixed $key
422
   *
423
   * @return Arrayy
424
   */
425 3
  public function indexBy($key)
426
  {
427 3
    $results = array();
428
429 3
    foreach ($this->array as $a) {
430 3
      if (isset($a[$key])) {
431 3
        $results[$a[$key]] = $a;
432
      }
433
    }
434
435 3
    return Arrayy::create($results);
436
  }
437
}
438