Completed
Push — master ( 5bf4d9...ab13ef )
by Lars
03:55
created

ArrayyAbstract   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 426
Duplicated Lines 3.52 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 86.51%

Importance

Changes 5
Bugs 2 Features 3
Metric Value
wmc 39
c 5
b 2
f 3
lcom 1
cbo 1
dl 15
loc 426
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 31 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 1
            return is_callable($sorter) ? $sorter($value) : $this->get($sorter, null, $value);
300 1
          }
301
      );
302
    } else {
303 1
      $results = $array;
304
    }
305
306
    // Sort by the results and replace by original values
307 1
    array_multisort($results, $directionType, SORT_REGULAR, $array);
308
309 1
    return Arrayy::create($array);
310
  }
311
312
  /**
313
   * Group values from a array according to the results of a closure.
314
   *
315
   * @param string $grouper a callable function name
316
   * @param bool   $saveKeys
317
   *
318
   * @return Arrayy
319
   */
320 3
  public function group($grouper, $saveKeys = false)
321
  {
322 3
    $array = (array)$this->array;
323 3
    $result = array();
324
325
    // Iterate over values, group by property/results from closure
326 3
    foreach ($array as $key => $value) {
327 3
      $groupKey = is_callable($grouper) ? $grouper($value, $key) : $this->get($grouper, null, $value);
328 3
      $newValue = $this->get($groupKey, null, $result);
329
330
      // Add to results
331 3
      if ($groupKey !== null) {
332 2
        if ($saveKeys) {
333 1
          $result[$groupKey] = $newValue;
334 1
          $result[$groupKey][$key] = $value;
335
        } else {
336 1
          $result[$groupKey] = $newValue;
337 3
          $result[$groupKey][] = $value;
338
        }
339
      }
340
341
    }
342
343 3
    return Arrayy::create($result);
344
  }
345
346
  ////////////////////////////////////////////////////////////////////
347
  ////////////////////////////// HELPERS /////////////////////////////
348
  ////////////////////////////////////////////////////////////////////
349
350
  /**
351
   * Internal mechanic of set method.
352
   *
353
   * @param string $key
354
   * @param mixed  $value
355
   *
356
   * @return mixed
357
   */
358 14
  protected function internalSet($key, $value)
359
  {
360 14
    if (null === $key) {
361
      /** @noinspection OneTimeUseVariablesInspection */
362
      $array = $value;
363
364
      return $array;
365
    }
366
367
    // Explode the keys
368 14
    $keys = explode('.', $key);
369
370
    // Crawl through the keys
371 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...
372
      $key = array_shift($keys);
373
374
      $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...
375
      $this->array = &$this->array[$key];
376
    }
377
378
    // Bind final tree on the array
379 14
    $key = array_shift($keys);
380
381 14
    $this->array[$key] = $value;
382 14
  }
383
384
  /**
385
   * Internal mechanics of remove method.
386
   *
387
   * @param $key
388
   *
389
   * @return boolean
390
   */
391 10
  protected function internalRemove($key)
392
  {
393
    // Explode keys
394 10
    $keys = explode('.', $key);
395
396
    // Crawl though the keys
397 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...
398
      $key = array_shift($keys);
399
400
      if (!$this->has($key)) {
401
        return false;
402
      }
403
404
      $this->array = &$this->array[$key];
405
    }
406
407 10
    $key = array_shift($keys);
408
409 10
    unset($this->array[$key]);
410
411 10
    return true;
412
  }
413
414
  /**
415
   * Given a list, and an iteratee function that returns
416
   * a key for each element in the list (or a property name),
417
   * returns an object with an index of each item.
418
   * Just like groupBy, but for when you know your keys are unique.
419
   *
420
   * @param mixed $key
421
   *
422
   * @return Arrayy
423
   */
424 3
  public function indexBy($key)
425
  {
426 3
    $results = array();
427
428 3
    foreach ($this->array as $a) {
429 3
      if (isset($a[$key])) {
430 3
        $results[$a[$key]] = $a;
431
      }
432
    }
433
434 3
    return Arrayy::create($results);
435
  }
436
}
437