Arrays::keyExists()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 3
nop 2
dl 0
loc 17
rs 9.9666
c 0
b 0
f 0
1
<?php
2
namespace JClaveau\Arrays;
3
4
use       JClaveau\Exceptions\UsageException;
5
6
/**
7
 *
8
 */
9
class Arrays
10
{
11
    use Arrays_Merge_Trait;
12
    
13
    /**
14
     * Taken from Kohana's Arr class.
15
     *
16
     * Tests if an array is associative or not.
17
     *
18
     *     // Returns TRUE
19
     *     Arr::isAssoc(array('username' => 'john.doe'));
20
     *
21
     *     // Returns FALSE
22
     *     Arr::isAssoc('foo', 'bar');
23
     *
24
     * @param   array   $array  array to check
25
     * @return  boolean
26
     */
27
    public static function isAssociative(array $array)
28
    {
29
        // Keys of the array
30
        $keys = array_keys($array);
31
32
        // If the array keys of the keys match the keys, then the array must
33
        // not be associative (e.g. the keys array looked like {0:0, 1:1...}).
34
        return array_keys($keys) !== $keys;
35
    }
36
37
    /**
38
     * Replacement of array_unique, keeping the first key.
39
     *
40
     * @param  array|\Traversable $array
41
     * @return array|\Traversable With unique values
42
     *
43
     * @todo   Options to keep another key than the first one?
44
     */
45
    public static function unique($array)
46
    {
47
        static::mustBeCountable($array);
48
49
        $ids = [];
50
        foreach ($array as $key => $value) {
51
            if (is_scalar($value)) {
52
                $id = $value;
53
            }
54
            else {
55
                $id = serialize($value);
56
            }
57
58
            if (isset($ids[ $id ])) {
59
                unset($array[ $key ]);
60
                $ids[ $id ][] = $key;
61
                continue;
62
            }
63
64
            $ids[ $id ] = [$key];
65
        }
66
67
        return $array;
68
    }
69
70
    /**
71
     */
72
    public static function keyExists($key, $array)
73
    {
74
        static::mustBeTraversable($array);
75
76
        if (is_array($array)) {
77
            return array_key_exists($key, $array);
78
        }
79
        elseif ($array instanceof ChainableArray || method_exists($array, 'keyExists')) {
80
            return $array->keyExists($key);
81
        }
82
        else {
83
            throw new \InvalidArgumentException(
84
                "keyExists() method missing on :\n". var_export($array, true)
85
            );
86
        }
87
88
        return $array;
0 ignored issues
show
Unused Code introduced by
return $array is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
89
    }
90
91
    /**
92
     * Replacement of array_sum wich throws exceptions instead of skipping
93
     * bad operands.
94
     *
95
     * @param  array|\Traversable $array
96
     * @return int|double         The sum
97
     *
98
     * @todo   Support options like 'strict', 'skip_non_scalars', 'native'
99
     */
100
    public static function sum($array)
101
    {
102
        static::mustBeCountable($array);
103
104
        $sum = 0;
105
        foreach ($array as $key => &$value) { // &for optimization
106
            if (is_scalar($value)) {
107
                $sum += $value;
108
            }
109
            elseif (is_null($value)) {
110
                continue;
111
            }
112
            elseif (is_array($value)) {
113
                throw new \InvalidArgumentException(
114
                    "Trying to sum an array with '$sum': ".var_export($value, true)
115
                );
116
            }
117
            elseif (is_object($value)) {
118
                if ( ! method_exists($value, 'toNumber')) {
119
                    throw new \InvalidArgumentEXception(
120
                         "Trying to sum a ".get_class($value)." object which cannot be casted as a number. "
121
                        ."Please add a toNumber() method."
122
                    );
123
                }
124
125
                $sum += $value->toNumber();
126
            }
127
        }
128
129
        return $sum;
130
    }
131
132
    /**
133
     * This method returns a classical mathemartic weighted mean.
134
     *
135
     * @todo It would ideally handled by a bridge with this fantastic math
136
     * lib https://github.com/markrogoyski/math-php/ but we need the support
137
     * of PHP 7 first.
138
     *
139
     * @see https://en.wikipedia.org/wiki/Weighted_arithmetic_mean
140
     * @see https://github.com/markrogoyski/math-php/
141
     */
142
    public static function weightedMean($values, $weights)
143
    {
144
        if ($values instanceof ChainableArray)
145
            $values = $values->toArray();
146
147
        if ($weights instanceof ChainableArray)
148
            $weights = $weights->toArray();
149
150
        if ( ! is_array($values))
151
            $values = [$values];
152
153
        if ( ! is_array($weights))
154
            $weights = [$weights];
155
156
        if (count($values) != count($weights)) {
157
            throw new \InvalidArgumentException(
158
                "Different number of "
159
                ." values and weights for weight mean calculation: \n"
160
                .var_export($values,  true)."\n\n"
161
                .var_export($weights, true)
162
            );
163
        }
164
165
        if (!$values)
166
            return null;
167
168
        $weights_sum  = array_sum($weights);
169
        if (!$weights_sum)
170
            return 0;
171
172
        $weighted_sum = 0;
173
        foreach ($values as $i => $value) {
174
            $weighted_sum += $value * $weights[$i];
175
        }
176
177
        return $weighted_sum / $weights_sum;
178
    }
179
180
    /**
181
     * This is not required anymore with PHP 7.
182
     *
183
     * @return bool
184
     */
185
    public static function isTraversable($value)
186
    {
187
        return $value instanceof \Traversable || is_array($value);
188
    }
189
190
    /**
191
     * This is not required anymore with PHP 7.
192
     *
193
     * @return bool
194
     */
195
    public static function isCountable($value)
196
    {
197
        return $value instanceof \Countable || is_array($value);
198
    }
199
200
    /**
201
     * @param  mixed $value
202
     * @return bool  Is the $value countable or not
203
     * @throws InvalidArgumentException
204
     *
205
     * @todo   NotCountableException
206
     */
207
    public static function mustBeCountable($value)
208
    {
209
        if (static::isCountable($value))
210
            return true;
211
212
        $exception = new \InvalidArgumentException(
213
            "A value must be Countable instead of: \n"
214
            .var_export($value, true)
215
        );
216
217
        // The true location of the throw is still available through the backtrace
218
        $trace_location  = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
219
        $reflectionClass = new \ReflectionClass( get_class($exception) );
220
221
        // file
222
        if (isset($trace_location['file'])) {
223
            $reflectionProperty = $reflectionClass->getProperty('file');
224
            $reflectionProperty->setAccessible(true);
225
            $reflectionProperty->setValue($exception, $trace_location['file']);
226
        }
227
228
        // line
229
        if (isset($trace_location['line'])) {
230
            $reflectionProperty = $reflectionClass->getProperty('line');
231
            $reflectionProperty->setAccessible(true);
232
            $reflectionProperty->setValue($exception, $trace_location['line']);
233
        }
234
235
        throw $exception;
236
    }
237
238
    /**
239
     * @param  mixed $value
240
     * @return bool  Is the $value traversable or not
241
     * @throws InvalidArgumentException
242
     *
243
     * @todo   NotTraversableException
244
     */
245
    public static function mustBeTraversable($value)
246
    {
247
        if (static::isTraversable($value))
248
            return true;
249
250
        $exception = new \InvalidArgumentException(
251
            "A value must be Traversable instead of: \n"
252
            .var_export($value, true)
253
        );
254
255
        // The true location of the throw is still available through the backtrace
256
        $trace_location  = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
257
        $reflectionClass = new \ReflectionClass( get_class($exception) );
258
259
        // file
260
        if (isset($trace_location['file'])) {
261
            $reflectionProperty = $reflectionClass->getProperty('file');
262
            $reflectionProperty->setAccessible(true);
263
            $reflectionProperty->setValue($exception, $trace_location['file']);
264
        }
265
266
        // line
267
        if (isset($trace_location['line'])) {
268
            $reflectionProperty = $reflectionClass->getProperty('line');
269
            $reflectionProperty->setAccessible(true);
270
            $reflectionProperty->setValue($exception, $trace_location['line']);
271
        }
272
273
        throw $exception;
274
    }
275
276
    /**
277
     * Generates an id usable in hashes to identify a single grouped row.
278
     *
279
     * @param array $row    The row of the array to group by.
280
     * @param array $groups A list of the different groups. Groups can be
281
     *                      strings describing a column name or a callable
282
     *                      function, an array representing a callable,
283
     *                      a function or an integer representing a column.
284
     *                      If the index of the group is a string, it will
285
     *                      be used as a prefix for the group name.
286
     *                      Example:
287
     *                      [
288
     *                          'column_name',
289
     *                          'function_to_call',
290
     *                          4,  //column_number
291
     *                          'group_prefix'  => function($row){},
292
     *                          'group_prefix2' => [$object, 'method'],
293
     *                      ]
294
     *
295
     * @return string       The unique identifier of the group
296
     */
297
    public static function generateGroupId($row, array $groups_definitions, array $options=[])
298
    {
299
        Arrays::mustBeCountable($row);
300
301
        $key_value_separator = ! empty($options['key_value_separator'])
302
                             ? $options['key_value_separator']
303
                             : ':'
304
                             ;
305
306
        $groups_separator    = ! empty($options['groups_separator'])
307
                             ? $options['groups_separator']
308
                             : '-'
309
                             ;
310
311
        $group_parts = [];
312
        foreach ($groups_definitions as $group_definition_key => $group_definition_value) {
313
            $part_name = '';
314
315
            if (is_string($group_definition_key)) {
316
                $part_name .= $group_definition_key.'_';
317
            }
318
319
            if (is_string($group_definition_value)) {
320
                if (    (is_array($row)              && ! array_key_exists($group_definition_value, $row))
321
                    ||  ($row instanceof \ArrayAcces && ! $row->offsetExists($group_definition_value))
322
                ) {
323
                    throw new UsageException(
324
                        'Unset column for group id generation: '
325
                        .var_export($group_definition_value, true)
326
                        ."\n" . var_export($row, true)
327
                    );
328
                }
329
330
                $part_name         .= $group_definition_value;
331
                $group_result_value = $row[ $group_definition_value ];
332
            }
333
            elseif (is_int($group_definition_value)) {
334
                if (    (is_array($row)              && ! array_key_exists($group_definition_value, $row))
335
                    ||  ($row instanceof \ArrayAcces && ! $row->offsetExists($group_definition_value))
336
                ) {
337
                    throw new UsageException(
338
                        'Unset column for group id generation: '
339
                        .var_export($group_definition_value, true)
340
                        ."\n" . var_export($row, true)
341
                    );
342
                }
343
344
                $part_name         .= $group_definition_value ? : '0';
345
                $group_result_value = $row[ $group_definition_value ];
346
            }
347
            elseif (is_callable($group_definition_value)) {
348
349
                if (is_string($group_definition_value)) {
350
                    $part_name .= $group_definition_value;
351
                }
352
                // elseif (is_function($value)) {
353
                elseif (is_object($group_definition_value) && ($group_definition_value instanceof \Closure)) {
354
                    $part_name .= 'unnamed-closure-'
355
                                . hash('crc32b', var_export($group_definition_value, true));
356
                }
357
                elseif (is_array($group_definition_value)) {
358
                    $part_name .= implode('::', $group_definition_value);
359
                }
360
361
                $group_result_value = call_user_func_array($group_definition_value, [
362
                    $row, &$part_name
363
                ]);
364
            }
365
            else {
366
                throw new UsageException(
367
                    'Bad value provided for group id generation: '
368
                    .var_export($group_definition_value, true)
369
                    ."\n" . var_export($row, true)
370
                );
371
            }
372
373
            if (!is_null($part_name))
374
                $group_parts[ $part_name ] = $group_result_value;
375
        }
376
377
        // sort the groups by names (without it the same group could have multiple ids)
378
        ksort($group_parts);
379
380
        // bidimensional implode
381
        $out = [];
382
        foreach ($group_parts as $group_name => $group_value) {
383
            if (is_object($group_value)) {
384
                $group_value = get_class($group_value)
385
                             . '_'
386
                             . hash( 'crc32b', var_export($group_value, true) );
387
            }
388
            elseif (is_array($group_value)) {
389
                $group_value = 'array_' . hash( 'crc32b', var_export($group_value, true) );
390
            }
391
392
            $out[] = $group_name . $key_value_separator . $group_value;
393
        }
394
395
        return implode($groups_separator, $out);
396
    }
397
398
    /**
399
     * Search the value associated to the location_parts parameter into the input.
400
     *
401
     * @param  array|ArrayAccess $input          Typically $_POST|$_GET...
0 ignored issues
show
Bug introduced by
The type JClaveau\Arrays\ArrayAccess was not found. Did you mean ArrayAccess? If so, make sure to prefix the type with \.
Loading history...
402
     * @param  scalar|scalar[]   $location_parts
403
     *
404
     * @return mixed The value if it exists, null or $default_value otherwize.
405
     * 
406
     * Example:
407
     * $array = [
408
     *      'entry_1' => [
409
     *          'subentry_1' => 'lolo',
410
     *          'subentry_2' => 'lala',
411
     *      ],
412
     *      'entry_2' => [
413
     *          0 => 'lili',
414
     *          1 => 'lulu',
415
     *      ],
416
     *      'entry_3' => 'plop',
417
     * ];
418
     * 
419
     * Arrays::getValueAt($array, ['entry_2', 1]);                    => 'lulu'
420
     * Arrays::getValueAt($array, ['entry_1', 'subentry_2']);         => null
421
     * Arrays::getValueAt($array, ['entry_1', 'subentry_2'], 'lele'); => 'lele'
422
     * Arrays::getValueAt($array, 'entry_3');                         => 'plop'
423
     */
424
    public static function getValueAt($input, $location_parts, $default_value = null)
425
    {
426
        if (is_scalar($location_parts)) {
427
            $location_parts = [$location_parts];
428
        }
429
430
        if (! is_array($location_parts)) {
0 ignored issues
show
introduced by
The condition is_array($location_parts) is always true.
Loading history...
431
            throw new \InvalidArgumentException(
432
                "\$location_parts must be a scalar or an array of scalars (as entries of array/ArrayAccess)"
433
                ." instead of ".var_export($location_parts, true)
434
            );
435
        }
436
437
        // The value can be inside a multidimension array
438
        $value = $input;
439
        foreach ($location_parts as $location_part) {
440
            if (isset($value[$location_part])) {
441
                $value = $value[$location_part];
442
            }
443
            else {
444
                return $default_value;
445
            }
446
        }
447
448
        return $value;
449
    }
450
451
    /**/
452
}
453