Passed
Push — master ( ff5f97...686573 )
by Michael
02:29
created

Config::sortConfigBlocks()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.128

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 8
cts 10
cp 0.8
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 2
crap 4.128
1
<?php
2
3
namespace micmania1\config;
4
5
use micmania1\config\MergeStrategy\NoKeyConflict;
6
use micmania1\config\MergeStrategy\Priority;
7
8
class Config
9
{
10
    /**
11
     * @var array
12
     */
13
    protected $transformers = [];
14
15
    /**
16
     * @var array
17
     */
18
    protected $unmerged = [];
19
20
    /**
21
     * @var array
22
     */
23
    protected $merged = [];
24
25
    /**
26
     * This takes a list of transformaers which are responsible for fetching and tranforming
27
     * their config into PHP array.
28
     *
29
     * @param Transformer $transformers
30
     */
31 1
    public function __construct(...$transformers)
32
    {
33 1
        $this->transformers = $transformers;
34 1
    }
35
36
    /**
37
     * This loops through each transformer and trnasforms the config into the common php
38
     * array format.
39
     *
40
     * @return array
41
     */
42 1
    public function transform()
43
    {
44 1
        $this->unmerged = [];
45 1
        $this->merged = [];
46
47 1
        if (empty($this->transformers)) {
48
            return $this->merged;
49
        }
50
51
        // Each transformer returns config with sorted keys. These are then
52
        // all merged together into a sorted, but unmerged config.
53 1
        $this->unmerged = $this->transformAndSort();
54
55
        // Merge the config by sort order. Now that we have our config ordered
56
        // by priority we can merge the config together.
57 1
        $this->merged = $this->mergeByPriority($this->unmerged);
58
59
        // Return the final merged config
60 1
        return $this->merged;
61
    }
62
63
    /**
64
     * This is responsible for calling each transformer and then creating an unmerged
65
     * config array in blocks ordered by sort key.
66
     *
67
     * @example
68
     * The unmerged config looks as follows:
69
     * array(
70
     * 	10 => array(...)
71
     * );
72
     *
73
     * @return array
74
     */
75 1
    protected function transformAndSort()
76
    {
77 1
        $unmerged = [];
78
79
        // Run through each transformer and merge into a sorted array of configurations.
80
        // These will then need to be merged by priorty (lower number is higher priortiy)
81 1
        foreach ($this->transformers as $transformer) {
82 1
            $config = $transformer->transform();
83 1
            $unmerged = $this->sortConfigBlocks($config, $unmerged);
84 1
        }
85
86 1
        return $unmerged;
87
    }
88
89
    /**
90
     * This method takes a config block with a key which is used to sort. Its merged into
91
     * the existing config cleanly. If the sort key exists, then we will attempt to merge
92
     * that config block. If there are any key clashes at this stage, then we will throw an
93
     * exception.
94
     *
95
     * @example
96
     * $mine = array(
97
     * 	10 => array(...)
98
     * );
99
     *
100
     * $theirs = array(
101
     * 	5 => array(...)
102
     * 	15 => array(...)
103
     * );
104
     *
105
     * In this example, $mine would be placed in between the 5 and 10 sort keys.
106
     * $return = array(
107
     * 	5 => array(...)
108
     * 	10 => array(...)
109
     * 	15 => array(...)
110
     * );
111
     *
112
     * @param array $mine
113
     * @param array $theirs
114
     *
115
     * @return array
116
     */
117 1
    protected function sortConfigBlocks($mine, $theirs)
118
    {
119 1
        foreach ($mine as $sort => $value) {
120 1
            if (!is_int($sort)) {
121
                throw new Exception('Unable to sort config. Sort key must be an integer');
122
            }
123
124 1
            if (!array_key_exists($sort, $this->merged)) {
125 1
                $this->merged[$sort] = $value;
126 1
            } else {
127
                // If we get to this point, we have a potential key clash with the same
128
                // priority. If both values are an array, we can attempt to merge.
129
                // However, if the value is a string we cannot tell which has greater
130
                // priority and therefore must throw an exception. Even if the value is an
131
                // array, we can run into a key clash when merging those and an exception
132
                // will be thrown.
133
                $this->merged[$sort] = $this->mergeUniqueKeys($mine[$sort], $theirs[$sort]);
134
            }
135 1
        }
136
137 1
        return $this->merged;
138
    }
139
140
    /**
141
     * This will merge config blocks, but throw an exception if there is a key clash.
142
     *
143
     * @param array $mine
144
     * @param array $theirs
145
     *
146
     * @return array
147
     */
148
    protected function mergeUniqueKeys($mine, $theirs)
149
    {
150
        return (new NoKeyConflict())->merge($mine, $theirs);
151
    }
152
153
    /**
154
     * This will merge by priority order and overwrite any existing values that aren't arrays
155
     * or try to merge values that are arrays recursively.
156
     *
157
     * @param array $mine
158
     * @param array $theirs
159
     *
160
     * @return array
161
     */
162 1
    protected function mergeByPriority($mine, $theirs = [])
163
    {
164 1
        $merged = [];
165 1
        foreach ($mine as $sort => $block) {
166 1
            foreach ($block as $key => $value) {
167 1
                if(!is_array($value)) {
168 1
                    $merged[$key] = $value;
169 1
                    continue;
170
                }
171
172
                $merged[$key] = (new Priority())->merge($value, $theirs);
173 1
            }
174 1
        }
175
176 1
        return $merged;
177
    }
178
}
179