Completed
Push — master ( ea513b...0f77f4 )
by Jean
02:50
created

ChainableArray_Utils_Trait   F

Complexity

Total Complexity 124

Size/Duplication

Total Lines 915
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 335
dl 0
loc 915
rs 2
c 0
b 0
f 0
wmc 124

29 Methods

Rating   Name   Duplication   Size   Complexity  
A isEmpty() 0 3 1
A mergePreservingDistincts() 0 26 4
A renameColumns() 0 18 4
A mergeIn() 0 4 1
B mergeRecursiveCustom() 0 52 9
A first() 0 15 3
A dump() 0 13 2
C dimensionsAsColumns_recurser() 0 83 12
A weightedMean() 0 6 1
A firstKey() 0 16 3
A last() 0 15 3
C generateGroupId() 0 59 14
A limit() 0 21 5
A isAssoc() 0 3 1
A contains() 0 3 1
A move() 0 15 5
A append() 0 35 6
A groupByTransformed() 0 39 5
A each() 0 11 2
A keepUniqueColumnValues() 0 15 5
B mergeWith() 0 36 6
A renameColumn() 0 3 1
A lastKey() 0 16 3
A groupInArrays() 0 25 6
B groupBy() 0 41 7
A mergeSeveralWith() 0 7 2
A dimensionsAsColumns() 0 4 1
A extract() 0 26 6
A replaceEntries() 0 15 5

How to fix   Complexity   

Complex Class

Complex classes like ChainableArray_Utils_Trait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ChainableArray_Utils_Trait, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace JClaveau\Arrays;
3
4
/**
5
 * Custom functions that can be used on arrays.
6
 */
7
trait ChainableArray_Utils_Trait
8
{
9
    /**
10
     * Same as $this->groupByTransformed() without the transformer to
11
     * improve the readability in some cases.
12
     *
13
     * @param  callable $indexGenerator   Can return a scalar or an array.
14
     *         Multiple indexes allow to add a row to multiple groups.
15
     * @param  callable $conflictResolver
16
     *
17
     * @throws Missing conflict resolver
18
     *
19
     * @return array The array containing the grouped rows.
20
     */
21
    public function groupBy( callable $indexGenerator, callable $conflictResolver=null )
22
    {
23
        // todo : this doesn't work
24
        // return $this->groupByTransformed($indexGenerator, null, $conflictResolver);
25
26
        $out = [];
27
        foreach ($this->data as $key => $row) {
28
29
            if (!$row)
30
                continue;
31
32
            $newIndexes     = call_user_func($indexGenerator, $key, $row);
33
            if (!is_array($newIndexes))
34
                $newIndexes = [$newIndexes];
35
36
            foreach ($newIndexes as $newIndex) {
37
                if (!isset($out[$newIndex])) {
38
                    $out[$newIndex] = $row;
39
                }
40
                else {
41
                    if ($conflictResolver === null) {
42
                        self::throwUsageException(
43
                            "A 'group by' provoking a conflict"
44
                            ." has no conflict resolver defined:\n"
45
                            ." + key: ".$key."\n"
46
                            ." + existing: ".var_export($out[$newIndex], true)."\n"
47
                            ." + conflict: ".var_export($row, true)."\n"
48
                        );
49
                    }
50
51
                    $out[$newIndex] = call_user_func(
52
                        $conflictResolver,
53
                        $newIndex,
54
                        $out[$newIndex],
55
                        $row
56
                    );
57
                }
58
            }
59
        }
60
61
        return $this->returnConstant($out);
0 ignored issues
show
Bug introduced by
It seems like returnConstant() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

61
        return $this->/** @scrutinizer ignore-call */ returnConstant($out);
Loading history...
62
    }
63
64
    /**
65
     * Group rows in arrays indexed by the index generated by $indexGenerator
66
     *
67
     * @param  callable $indexGenerator   Can return a scalar or an array.
68
     *         Multiple indexes allow to add a row to multiple groups.
69
     *
70
     * @return array The array containing the grouped rows.
71
     */
72
    public function groupInArrays( callable $indexGenerator )
73
    {
74
        $out = [];
75
        foreach ($this->data as $key => $row) {
76
77
            if (!$row)
78
                continue;
79
80
            $new_keys = call_user_func($indexGenerator, $key, $row);
81
            if (!is_array($new_keys))
82
                $new_keys = [$new_keys];
83
84
            foreach ($new_keys as $new_key) {
85
                if (!isset($out[ $new_key ])) {
86
                    $out[ $new_key ] = [
87
                        $key => $row
88
                    ];
89
                }
90
                else {
91
                    $out[ $new_key ][ $key ] = $row;
92
                }
93
            }
94
        }
95
96
        return $this->returnConstant($out);
97
    }
98
99
    /**
100
     * Equivalent of array_merge_recursive with more options.
101
     *
102
     * @param array         $existing_row
103
     * @param array         $conflict_row
104
     * @param callable|null $merge_resolver
105
     * @param int           $max_depth
106
     *
107
     * + If exist only in conflict row => add
108
     * + If same continue
109
     * + If different merge as array
110
     */
111
    public static function mergeRecursiveCustom(
112
        array $existing_row,
113
        array $conflict_row,
114
        callable $merge_resolver=null,
115
        $max_depth=null
116
    ){
117
        foreach ($conflict_row as $column => $conflict_value) {
118
119
            // not existing in first array
120
            if (!isset($existing_row[$column])) {
121
                $existing_row[$column] = $conflict_value;
122
                continue;
123
            }
124
125
            $existing_value = $existing_row[$column];
126
127
            // two arrays so we recurse
128
            if (is_array($existing_value) && is_array($conflict_value)) {
129
130
                if ($max_depth === null || $max_depth > 0) {
131
                    $existing_row[$column] = self::mergeRecursiveCustom(
132
                        $existing_value,
133
                        $conflict_value,
134
                        $merge_resolver,
135
                        $max_depth - 1
136
                    );
137
                    continue;
138
                }
139
            }
140
141
            if ($merge_resolver) {
142
                $existing_row[$column] = call_user_func_array(
143
                    $merge_resolver,
144
                    [
145
                        $existing_value,
146
                        $conflict_value,
147
                        $column,
148
                    ]
149
                );
150
            }
151
            else {
152
                // same reslution as array_merge_recursive
153
                if (!is_array($existing_value)) {
154
                    $existing_row[$column] = [$existing_value];
155
                }
156
157
                // We store the new value with their previous ones
158
                $existing_row[$column][] = $conflict_value;
159
            }
160
        }
161
162
        return $existing_row;
163
    }
164
165
    /**
166
     * This specific merge
167
     *
168
     * @param  array $existing_row
169
     * @param  array $conflict_row
170
     *
171
     * @return array
172
     */
173
    public static function mergePreservingDistincts(
174
        array $existing_row,
175
        array $conflict_row
176
    ){
177
        return static::mergeRecursiveCustom(
178
            $existing_row,
179
            $conflict_row,
180
            function ($existing_value, $conflict_value, $column) {
181
182
                if (!is_array($existing_value))
183
                    $existing_value = [$existing_value];
184
185
                // We store the new value with their previous ones
186
                if ( ! is_array($conflict_value)) {
187
                    $conflict_value = [
188
                        $conflict_value
189
                    ];
190
                }
191
192
                foreach ($conflict_value as $conflict_key => $conflict_entry) {
193
                    $existing_value[] = $conflict_entry;
194
                }
195
196
                return $existing_value;
197
            },
198
            1
199
        );
200
    }
201
202
    /**
203
     * This is the cleaning part of self::mergePreservingDistincts()
204
     *
205
     * @see mergePreservingDistincts()
206
     */
207
    public static function keepUniqueColumnValues(array $row, array $excluded_columns=[])
208
    {
209
        foreach ($row as $column => &$values) {
210
            if (!is_array($values))
211
                continue;
212
213
            if (in_array($column, $excluded_columns))
214
                continue;
215
216
            $values = array_unique($values);
217
            if (count($values) == 1)
218
                $values = $values[0];
219
        }
220
221
        return $row;
222
    }
223
224
    /**
225
     * Parses an array and group it rows by index. This index is generated
226
     * by the first parameter.
227
     * The row corresponding to the new index can be different from the
228
     * grouped ones so the second parameter allows us to transform them.
229
     * Finally, the third parameter is used to resolve the conflict e.g.
230
     * when two rows generate the same index.
231
     *
232
     * @paramb callable $indexGenerator
233
     * @paramb callable $rowTransformer
234
     * @paramb callable $conflictResolver
235
     *
236
     * @return array The array containing the grouped rows.
237
     */
238
    public function groupByTransformed(
239
        callable $indexGenerator,
240
        callable $rowTransformer,      // todo check this behavior
241
        callable $conflictResolver )
242
    {
243
        // The goal here is to remove the second parameter has it makes the
244
        // grouping process too complicated
245
        // if (!$conflictResolver) {
246
            // $conflictResolver = $rowTransformer;
247
            // $rowTransformer   = null;
248
        // }
249
250
        $out = [];
251
        foreach ($this->data as $key => $row) {
252
253
            if (!$row)
254
                continue;
255
256
            $newIndex       = call_user_func($indexGenerator, $key, $row);
257
258
            $transformedRow = $rowTransformer
259
                            ? call_user_func($rowTransformer, $row)
260
                            : $row;
261
262
            if (!isset($out[$newIndex])) {
263
                $out[$newIndex] = $transformedRow;
264
            }
265
            else {
266
                $out[$newIndex] = call_user_func(
267
                    $conflictResolver,
268
                    $newIndex,
269
                    $out[$newIndex],
270
                    $transformedRow,
271
                    $row
272
                );
273
            }
274
        }
275
276
        return $this->returnConstant($out);
277
    }
278
279
    /**
280
     * Merge a table into another one
281
     *
282
     * @param static $otherTable       The table to merge into
283
     * @param callable     $conflictResolver Defines what to do if two
284
     *                                       rows have the same index.
285
     * @return static
286
     */
287
    public function mergeWith( $otherTable, callable $conflictResolver=null )
288
    {
289
        if (is_array($otherTable))
0 ignored issues
show
introduced by
The condition is_array($otherTable) is always false.
Loading history...
290
            $otherTable = new static($otherTable);
291
292
        if (!$otherTable instanceof static) {
0 ignored issues
show
introduced by
$otherTable is always a sub-type of static.
Loading history...
293
            self::throwUsageException(
294
                '$otherTable must be an array or an instance of '.static::class.' instead of: '
295
                .var_export($otherTable, true)
296
            );
297
        }
298
299
        $out = $this->data;
300
        foreach ($otherTable->getArray() as $key => $row) {
0 ignored issues
show
Bug introduced by
It seems like getArray() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

300
        foreach ($otherTable->/** @scrutinizer ignore-call */ getArray() as $key => $row) {
Loading history...
301
302
            if (!isset($out[$key])) {
303
                $out[$key] = $row;
304
            }
305
            else {
306
                if ($conflictResolver === null)
307
                    self::throwUsageException('No conflict resolver for a merge provoking one');
308
309
                $arguments = [
310
                    &$key,
311
                    $out[$key],
312
                    $row
313
                ];
314
315
                $out[$key] = call_user_func_array(
316
                    $conflictResolver,
317
                    $arguments
318
                );
319
            }
320
        }
321
322
        return $this->returnConstant($out);
323
    }
324
325
    /**
326
     * Merge the table $otherTable into the current table.
327
     * (same as self::mergeWith with the other table as $this)
328
     * @return static
329
     */
330
    public function mergeIn( $otherTable, callable $conflictResolver=null )
331
    {
332
        $otherTable->mergeWith($this, $conflictResolver);
333
        return $this;
334
    }
335
336
    /**
337
     * The same as self::mergeWith with an array of tables.
338
     *
339
     * @param array $othersTable array of HelperTable
340
     * @param func  $conflictResolver callback resolver
0 ignored issues
show
Bug introduced by
The type JClaveau\Arrays\func was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
341
     */
342
    public function mergeSeveralWith(array $othersTable, callable $conflictResolver = null)
343
    {
344
        foreach ($othersTable as $otherTable) {
345
            $this->mergeWith($otherTable, $conflictResolver);
346
        }
347
348
        return $this;
349
    }
350
351
    /**
352
     *
353
     */
354
    public function each(callable $rowTransformer)
355
    {
356
        $out  = [];
357
        foreach ($this->data as $key => $row) {
358
            $out[$key] = call_user_func_array(
359
                $rowTransformer,
360
                [$row, &$key, $this->data]
361
            );
362
        }
363
364
        return $this->returnConstant($out);
365
    }
366
367
    /**
368
     * Rename a column on every row.
369
     *
370
     * @todo remove this method and force the usage of $this->renameColumns()?
371
     * @deprecated use $this->renameColumns(Array) instead]
372
     *
373
     */
374
    public function renameColumn($old_name, $new_name)
375
    {
376
        return $this->renameColumns([$old_name => $new_name]);
377
    }
378
379
    /**
380
     * Rename a column on every row.
381
     *
382
     * @return static
383
     */
384
    public function renameColumns(array $old_to_new_names)
385
    {
386
        $out  = [];
387
        foreach ($this->data as $key => $row) {
388
            try {
389
                foreach ($old_to_new_names as $old_name => $new_name) {
390
                    $row[$new_name] = $row[$old_name];
391
                    unset($row[$old_name]);
392
                }
393
            }
394
            catch (\Exception $e) {
395
                self::throwUsageException( $e->getMessage() );
396
            }
397
398
            $out[$key] = $row;
399
        }
400
401
        return $this->returnConstant($out);
402
    }
403
404
    /**
405
     * Limits the size of the array.
406
     *
407
     * @param  int         $max
408
     * @return Heper_Table $this
0 ignored issues
show
Bug introduced by
The type JClaveau\Arrays\Heper_Table was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
409
     *
410
     * @todo implement other parameters for this function like in SQL
411
     */
412
    public function limit()
413
    {
414
        $arguments = func_get_args();
415
        if (count($arguments) == 1 && is_numeric($arguments[0]))
416
            $max = $arguments[0];
417
        else
418
            self::throwUsageException("Bad arguments type and count for limit()");
419
420
        $out   = [];
421
        $count = 0;
422
        foreach ($this->data as $key => $row) {
423
424
            if ($max <= $count)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $max does not seem to be defined for all execution paths leading up to this point.
Loading history...
425
                break;
426
427
            $out[$key] = $row;
428
429
            $count++;
430
        }
431
432
        return $this->returnConstant($out);
433
    }
434
435
    /**
436
     * Appends an array to the current one.
437
     *
438
     * @param  array|static $new_rows to append
439
     * @param  callable           $conflict_resolver to use if a new row as the
440
     *                            same key as an existing row. By default, the new
441
     *                            key will be lost and the row appended as natively.
442
     *
443
     * @throws UsageException     If the $new_rows parameter is neither an array
444
     *                            nor a static.
445
     * @return static       $this
446
     */
447
    public function append($new_rows, callable $conflict_resolver=null)
448
    {
449
        if ($new_rows instanceof static)
450
            $new_rows = $new_rows->getArray();
451
452
        if (!is_array($new_rows)) {
453
            $this->throwUsageException(
0 ignored issues
show
Bug introduced by
It seems like throwUsageException() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

453
            $this->/** @scrutinizer ignore-call */ 
454
                   throwUsageException(
Loading history...
454
                "\$new_rows parameter must be an array or an instance of " . __CLASS__
455
            );
456
        }
457
458
        if (!$conflict_resolver) {
459
            // default conflict resolver: append with numeric key
460
            $conflict_resolver = function (&$data, $existing_row, $confliuct_row, $key) {
461
                $data[] = $confliuct_row;
462
            };
463
        }
464
465
        foreach ($new_rows as $key => $new_row) {
466
            if (isset($this->data[$key])) {
467
                $arguments = [
468
                    &$this->data,
469
                    $existing_row,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $existing_row seems to be never defined.
Loading history...
470
                    $confliuct_row,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $confliuct_row seems to be never defined.
Loading history...
471
                    $key
472
                ];
473
474
                call_user_func_array($conflict_resolver, $arguments);
475
            }
476
            else {
477
                $this->data[$key] = $new_row;
0 ignored issues
show
Bug Best Practice introduced by
The property data does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
478
            }
479
        }
480
481
        return $this;
482
    }
483
484
    /**
485
     * @param $columnNames scalar[] The names of the newly created columns.
486
     * @param $options     array    Unsed presently
487
     *
488
     * @see self::dimensionsAsColumns_recurser()
489
     *
490
     * @return static
491
     */
492
    public function dimensionsAsColumns(array $columnNames, array $options=null)
493
    {
494
        $out = $this->dimensionsAsColumns_recurser($this->data, $columnNames);
495
        return $this->returnConstant($out);
496
    }
497
498
    /**
499
     *
500
     * @todo Fix case of other columns
501
     *
502
     * Example:
503
     *  dimensionsAsColumns_recurser([
504
     *      [
505
     *          0,
506
     *          'me',
507
     *      ],
508
     *      [
509
     *          1,
510
     *          'me_too',
511
     *      ],
512
     *  ],
513
     *  [
514
     *      'id',
515
     *      'name',
516
     *  ]
517
     *
518
     * => [
519
     *      'id:0-name:me'     => [
520
     *          'id'   => 0,
521
     *          'name' => 'me',
522
     *      ],
523
     *      'id:1-name:me_too' => [
524
     *          'id'   => 1,
525
     *          'name' => 'me_too',
526
     *      ],
527
     * ]
528
     */
529
    protected function dimensionsAsColumns_recurser(array $data, $columnNames, $rowIdParts=[])
530
    {
531
        $out = [];
532
        // if (!$columnNames)
533
            // return $data;
534
        $no_more_column = !(bool) $columnNames;
0 ignored issues
show
Unused Code introduced by
The assignment to $no_more_column is dead and can be removed.
Loading history...
535
536
        // If all the names have been given to the dimensions
537
        // we compile the index key of the row at the current level
538
        if (empty($columnNames)) {
539
            // echo json_encode([
540
                // 'columnNames' => $columnNames,
541
                // 'rowIdParts'  => $rowIdParts,
542
                // 'data'        => $data,
543
            // ]);
544
            // exit;
545
546
            $indexParts = [];
547
            foreach ($rowIdParts as $name => $value) {
548
                $indexParts[] = $name.':'.$value;
549
            }
550
            $row_id = implode('-', $indexParts);
551
552
            // If we are at a "leaf" of the tree
553
            foreach ($rowIdParts as $name => $value) {
554
                if (isset($data[$name]) && $data[$name] !== $value) {
555
                    self::throwUsageException(
556
                         "Trying to populate a column '$name' that "
557
                        ."already exists with a different value "
558
                        .var_export($data[$name], true). " => '$value'"
559
                    );
560
                }
561
                $data[$name] = $value;
562
            }
563
564
            $out = [
565
                $row_id => $data,
566
            ];
567
568
            return $out;
569
        }
570
571
        $currentDimensionName = array_shift($columnNames);
572
573
        foreach ($data as $key => $row) {
574
575
            // if (!$no_more_column)
576
                $rowIdParts[$currentDimensionName] = $key;
577
            // else
578
                // $rowIdParts[] = $key;
579
580
581
            if (is_array($row)) {
582
                $rows = $this->dimensionsAsColumns_recurser($row, $columnNames, $rowIdParts);
583
                foreach ($rows as $row_id => $joined_row) {
584
                    $out[$row_id] = $joined_row;
585
                }
586
            }
587
            else {
588
589
                if (!isset($rows)) {
590
                    echo json_encode([
591
                        '$rowIdParts' => $rowIdParts,
592
                        '$row' => $row,
593
                    ]);
594
                    exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
595
                }
596
597
                foreach ($rowIdParts as $rowIdPartName => $rowIdPartValue)
598
                    $row[$rowIdPartName] = $rowIdPartValue;
599
600
                $indexParts = [];
601
                foreach ($rowIdParts as $name => $value) {
602
                    $indexParts[] = $name.':'.$value;
603
                }
604
                $row_id = implode('-', $indexParts);
605
606
                $out[$row_id] = $row;
607
            }
608
609
        }
610
611
        return $out;
612
    }
613
614
    /**
615
     * Generates an id usable in hashes to identify a single grouped row.
616
     *
617
     * @param array $row    The row of the array to group by.
618
     * @param array $groups A list of the different groups. Groups can be
619
     *                      strings describing a column name or a callable
620
     *                      function, an array representing a callable,
621
     *                      a function or an integer representing a column.
622
     *                      If the index of the group is a string, it will
623
     *                      be used as a prefix for the group name.
624
     *                      Example:
625
     *                      [
626
     *                          'column_name',
627
     *                          'function_to_call',
628
     *                          4,  //column_number
629
     *                          'group_prefix'  => function($row){},
630
     *                          'group_prefix2' => [$object, 'method'],
631
     *                      ]
632
     *
633
     * @return string       The unique identifier of the group
634
     */
635
    public static function generateGroupId(array $row, array $groups)
636
    {
637
        $group_parts = [];
638
639
        foreach ($groups as $key => $value) {
640
            $part_name = '';
641
642
            if (is_string($key)) {
643
                $part_name .= $key.'_';
644
            }
645
646
            if (is_string($value) && array_key_exists($value, $row)) {
647
                $part_name  .= $value;
648
                $group_value = $row[ $value ];
649
            }
650
            elseif (is_callable($value)) {
651
652
                if (is_string($value)) {
653
                    $part_name  .= $value;
654
                }
655
                // elseif (is_function($value)) {
656
                elseif (is_object($value) && ($value instanceof Closure)) {
0 ignored issues
show
Bug introduced by
The type JClaveau\Arrays\Closure was not found. Did you mean Closure? If so, make sure to prefix the type with \.
Loading history...
657
                    $part_name .= 'unnamed-closure-'
658
                                . hash('crc32b', var_export($value, true));
659
                }
660
                elseif (is_array($value)) {
661
                    $part_name .= implode('::', $value);
662
                }
663
664
                $group_value = call_user_func_array($value, [
665
                    $row, &$part_name
666
                ]);
667
            }
668
            elseif (is_int($value)) {
669
                $part_name  .= $value ? : '0';
670
                $group_value = $row[ $value ];
671
            }
672
            else {
673
                self::throwUsageException(
674
                    'Bad value provided for groupBy id generation: '
675
                    .var_export($value, true)
676
                    ."\n" . var_export($row, true)
677
                );
678
            }
679
680
            if (!is_null($part_name))
681
                $group_parts[ $part_name ] = $group_value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $group_value does not seem to be defined for all execution paths leading up to this point.
Loading history...
682
        }
683
684
        // sort the groups by names (without it the same group could have multiple ids)
685
        ksort($group_parts);
686
687
        // bidimensional implode
688
        $out = [];
689
        foreach ($group_parts as $group_name => $group_value) {
690
            $out[] = $group_name.':'.$group_value;
691
        }
692
693
        return implode('-', $out);
694
    }
695
696
    /**
697
     * Returns the first element of the array
698
     */
699
    public function first($strict=false)
700
    {
701
        if (!$this->count()) {
0 ignored issues
show
Bug introduced by
It seems like count() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

701
        if (!$this->/** @scrutinizer ignore-call */ count()) {
Loading history...
702
            if ($strict)
703
                throw new \ErrorException("No first element found in this array");
704
            else
705
                $first = null;
706
        }
707
        else {
708
            $key   = key($this->data);
709
            $first = reset($this->data);
710
            $this->move($key);
711
        }
712
713
        return $first;
714
    }
715
716
    /**
717
     * Returns the last element of the array
718
     *
719
     * @todo Preserve the offset
720
     */
721
    public function last($strict=false)
722
    {
723
        if (!$this->count()) {
724
            if ($strict)
725
                throw new \ErrorException("No last element found in this array");
726
            else
727
                $last = null;
728
        }
729
        else {
730
            $key  = key($this->data);
731
            $last = end($this->data);
732
            $this->move($key);
733
        }
734
735
        return $last;
736
    }
737
738
    /**
739
     *
740
     */
741
    public function firstKey($strict=false)
742
    {
743
        if (!$this->count()) {
744
            if ($strict)
745
                throw new \ErrorException("No last element found in this array");
746
            else
747
                $firstKey = null;
748
        }
749
        else {
750
            $key      = key($this->data);
751
            reset($this->data);
752
            $firstKey = key($this->data);
753
            $this->move($key);
754
        }
755
756
        return $firstKey;
757
    }
758
759
    /**
760
     *
761
     */
762
    public function lastKey($strict=false)
763
    {
764
        if (!$this->count()) {
765
            if ($strict)
766
                throw new \ErrorException("No last element found in this array");
767
            else
768
                $lastKey = null;
769
        }
770
        else {
771
            $key  = key($this->data);
772
            end($this->data);
773
            $lastKey = key($this->data);
774
            $this->move($key);
775
        }
776
777
        return $lastKey;
778
    }
779
780
    /**
781
     * Move the internal pointer of the array to the key given as parameter
782
     */
783
    public function move($key, $strict=true)
784
    {
785
        if (array_key_exists($key, $this->data)) {
786
            foreach ($this->data as $i => &$value) {
787
                if ($i === $key) {
788
                    prev($this->data);
789
                    break;
790
                }
791
            }
792
        }
793
        elseif ($strict) {
794
            throw new \ErrorException("Unable to move the internal pointer to a key that doesn't exist.");
795
        }
796
797
        return $this;
798
    }
799
800
    /**
801
     * Chained equivalent of in_array().
802
     * @return bool
803
     */
804
    public function contains($value)
805
    {
806
        return in_array($value, $this->data);
807
    }
808
809
    /**
810
     * Checks if the array is associative or not.
811
     * @return bool
812
     */
813
    public function isAssoc()
814
    {
815
        return Arrays::isAssoc($this->getArray());
0 ignored issues
show
Bug introduced by
The method isAssoc() does not exist on JClaveau\Arrays\Arrays. Did you maybe mean isAssociative()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

815
        return Arrays::/** @scrutinizer ignore-call */ isAssoc($this->getArray());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
816
    }
817
818
    /**
819
     * Checks if the array is empty or not.
820
     * @return bool
821
     */
822
    public function isEmpty()
823
    {
824
        return empty($this->getArray());
825
    }
826
827
    /**
828
     * Computes the weighted mean of the values of a column weighted
829
     * by the values of a second one.
830
     *
831
     * @param  string $valueColumnName
832
     * @param  string $weightColumnName
833
     *
834
     * @return float The calculated weighted mean.
835
     */
836
    public function weightedMean($valueColumnName, $weightColumnName)
837
    {
838
        $values  = array_column($this->data, $valueColumnName);
839
        $weights = array_column($this->data, $weightColumnName);
840
841
        return Helper_Math::weightedMean($values, $weights);
0 ignored issues
show
Bug introduced by
The type JClaveau\Arrays\Helper_Math was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
842
    }
843
844
    /**
845
     * Equivalent of var_dump().
846
     *
847
     * @see http://php.net/manual/fr/function.var-dump.php
848
     * @todo Handle xdebug dump formatting
849
     */
850
    public function dump($exit=false)
851
    {
852
        $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
853
        $caller = $bt[0];
854
855
        header('content-type: text/html');
856
        var_dump($caller['file'] . ':' . $caller['line']);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($caller['file'] . ':' . $caller['line']) looks like debug code. Are you sure you do not want to remove it?
Loading history...
857
        var_dump($this->data);
858
859
        if ($exit)
860
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
861
862
        return $this;
863
    }
864
865
    /**
866
     * Scans the array recursivelly (until the max depthis reached) and replaces
867
     * the entries with the callback;
868
     *
869
     * @todo move it to an Arrays class storing static methods
870
     */
871
    public static function replaceEntries(
872
        array $array, callable $replacer, $max_depth=null
873
    ) {
874
        foreach ($array as $key => &$row) {
875
            $arguments = [&$row, $key];
876
            call_user_func_array($replacer, $arguments);
877
878
            if (is_array($row) && $max_depth !== 0) { // allowing null to have no depth limit
879
                $row = self::replaceEntries(
880
                    $row, $replacer, $max_depth ? $max_depth-1 : $max_depth
881
                );
882
            }
883
        }
884
885
        return $array;
886
    }
887
888
    /**
889
     * Equivalent of ->filter() but removes the matching values
890
     *
891
     * @param  callable|array $callback The filter logic with $value and $key
892
     *                            as parameters.
893
     *
894
     * @return static $this or a new static.
895
     */
896
    public function extract($callback=null)
897
    {
898
        if ($callback) {
899
900
            if (is_array($callback)) {
901
                $callback = new \JClaveau\LogicalFilter\LogicalFilter($callback);
0 ignored issues
show
Bug introduced by
The type JClaveau\LogicalFilter\LogicalFilter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
902
            }
903
904
            if (!is_callable($callback)) {
905
                $this->throwUsageException(
906
                    "\$callback must be a logical filter description array or a callable"
907
                    ." instead of "
908
                    .var_export($callback, true)
909
                );
910
            }
911
912
            $out = [];
913
            foreach ($this->data as $key => $value) {
914
                if ($callback($value, $key)) {
915
                    $out[$key] = $value;
916
                    unset( $this->data[$key] );
917
                }
918
            }
919
        }
920
921
        return new static($out);
0 ignored issues
show
Unused Code introduced by
The call to JClaveau\Arrays\Chainabl...ls_Trait::__construct() has too many arguments starting with $out. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

921
        return /** @scrutinizer ignore-call */ new static($out);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Comprehensibility Best Practice introduced by
The variable $out does not seem to be defined for all execution paths leading up to this point.
Loading history...
922
    }
923
924
    /**/
925
}
926