Passed
Push — master ( aec349...e9c1f2 )
by Alexander
04:11
created

Merger::merge()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 10
cc 3
nc 2
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Composer\Config\Merger;
6
7
use Yiisoft\Composer\Config\Merger\Modifier\ModifierInterface;
8
use Yiisoft\Composer\Config\Merger\Modifier\ReverseBlockMerge;
9
10
final class Merger
11
{
12
    /**
13
     * Merges two or more arrays into one recursively.
14
     * If each array has an element with the same string key value, the latter
15
     * will overwrite the former (different from `array_merge_recursive`).
16
     * Recursive merging will be conducted if both arrays have an element of array
17
     * type and are having the same key.
18
     * For integer-keyed elements, the elements from the latter array will
19
     * be appended to the former array.
20
     * You can use modifiers {@see Merger::applyModifiers()} to change merging result.
21
     *
22
     * @param array ...$args arrays to be merged
23
     *
24
     * @return array the merged array (the original arrays are not changed)
25
     */
26 28
    public static function merge(...$args): array
27
    {
28 28
        $lastArray = end($args);
29
        if (
30 28
            isset($lastArray[ReverseBlockMerge::class]) &&
31 28
            $lastArray[ReverseBlockMerge::class] instanceof ReverseBlockMerge
32
        ) {
33 6
            return self::applyModifiers(self::performReverseBlockMerge(...$args));
34
        }
35
36 22
        return self::applyModifiers(self::performMerge(...$args));
37
    }
38
39 22
    private static function performMerge(array ...$args): array
40
    {
41 22
        $res = array_shift($args) ?: [];
42 22
        while (!empty($args)) {
43
            /** @psalm-var mixed $v */
44 18
            foreach (array_shift($args) as $k => $v) {
45 18
                if (is_int($k)) {
46 10
                    if (array_key_exists($k, $res) && $res[$k] !== $v) {
47
                        /** @var mixed */
48 6
                        $res[] = $v;
49
                    } else {
50
                        /** @var mixed */
51 10
                        $res[$k] = $v;
52
                    }
53 14
                } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
54 6
                    $res[$k] = self::performMerge($res[$k], $v);
55
                } else {
56
                    /** @var mixed */
57 14
                    $res[$k] = $v;
58
                }
59
            }
60
        }
61
62 22
        return $res;
63
    }
64
65 6
    private static function performReverseBlockMerge(array ...$args): array
66
    {
67 6
        $res = array_pop($args) ?: [];
68 6
        while (!empty($args)) {
69
            /** @psalm-var mixed $v */
70 6
            foreach (array_pop($args) as $k => $v) {
71 6
                if (is_int($k)) {
72 4
                    if (array_key_exists($k, $res) && $res[$k] !== $v) {
73
                        /** @var mixed */
74 4
                        $res[] = $v;
75
                    } else {
76
                        /** @var mixed */
77 4
                        $res[$k] = $v;
78
                    }
79 2
                } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
80 2
                    $res[$k] = self::performReverseBlockMerge($v, $res[$k]);
81 2
                } elseif (!isset($res[$k])) {
82
                    /** @var mixed */
83 2
                    $res[$k] = $v;
84
                }
85
            }
86
        }
87
88 6
        return $res;
89
    }
90
91
    /**
92
     * Apply modifiers (classes that implement {@link ModifierInterface}) in array.
93
     *
94
     * For example, {@link \Yiisoft\Composer\Config\Merger\Modifier\UnsetValue} to unset value from previous
95
     * array or {@link \Yiisoft\Composer\Config\Merger\Modifier\ReplaceArrayValue} to force replace former
96
     * value instead of recursive merging.
97
     *
98
     * @param array $data
99
     *
100
     * @return array
101
     *
102
     * @see ModifierInterface
103
     */
104 28
    private static function applyModifiers(array $data): array
105
    {
106 28
        $modifiers = [];
107
        /** @psalm-var mixed $v */
108 28
        foreach ($data as $k => $v) {
109 26
            if ($v instanceof ModifierInterface) {
110 16
                $modifiers[$k] = $v;
111 16
                unset($data[$k]);
112 26
            } elseif (is_array($v)) {
113 14
                $data[$k] = self::applyModifiers($v);
114
            }
115
        }
116 28
        ksort($modifiers);
117 28
        foreach ($modifiers as $key => $modifier) {
118 16
            $data = $modifier->apply($data, $key);
119
        }
120 28
        return $data;
121
    }
122
}
123