Completed
Pull Request — master (#552)
by Greg
03:26
created

ConfigProcessor::arrayMergeRecursiveDistinct()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 3
nop 2
1
<?php
2
3
namespace Robo\Config;
4
5
use Grasmash\YamlExpander\Expander;
6
7
/**
8
 * The config processor combines multiple configuration
9
 * files together, and processes them as necessary.
10
 */
11
class ConfigProcessor
12
{
13
    protected $processedConfig = [];
14
    protected $unprocessedConfig = [];
15
16
    /**
17
     * Extend the configuration to be processed with the
18
     * configuration provided by the specified loader.
19
     *
20
     * @param ConfigLoaderInterface $loader
21
     */
22
    public function extend(ConfigLoaderInterface $loader)
23
    {
24
        return $this->addFromSource($loader->export(), $loader->getSourceName());
25
    }
26
27
    /**
28
     * Extend the configuration to be processed with
29
     * the provided nested array.
30
     *
31
     * @param array $data
32
     */
33
    public function add($data)
34
    {
35
        $this->unprocessedConfig[] = $data;
36
        return $this;
37
    }
38
39
    /**
40
     * Extend the configuration to be processed with
41
     * the provided nested array. Also record the name
42
     * of the data source, if applicable.
43
     *
44
     * @param array $data
45
     * @param string $source
46
     */
47
    protected function addFromSource($data, $source = '')
48
    {
49
        if (empty($source)) {
50
            return $this->add($data);
51
        }
52
        $this->unprocessedConfig[$source] = $data;
53
        return $this;
54
    }
55
56
    /**
57
     * Process all of the configuration that has been collected,
58
     * and return a nested array.
59
     *
60
     * @return array
61
     */
62
    public function export()
63
    {
64
        if (!empty($this->unprocessedConfig)) {
65
            $this->processedConfig = $this->process(
66
                $this->processedConfig,
67
                $this->fetchUnprocessed()
68
            );
69
        }
70
        return $this->processedConfig;
71
    }
72
73
    /**
74
     * To aid in debugging: return the source of each configuration item.
75
     * n.b. Must call this function *before* export and save the result
76
     * if persistence is desired.
77
     */
78
    public function sources()
79
    {
80
        $sources = [];
81
        foreach ($this->unprocessedConfig as $sourceName => $config) {
82
            if (!empty($sourceName)) {
83
                $sources = static::arrayMergeRecursiveDistinct($sources, static::arrayReplaceValueRecursive($config, $sourceName));
0 ignored issues
show
Bug introduced by
static::arrayReplaceValu...e($config, $sourceName) cannot be passed to arraymergerecursivedistinct() as the parameter $array2 expects a reference.
Loading history...
84
            }
85
        }
86
        return $sources;
87
    }
88
89
    /**
90
     * Get the configuration to be processed, and clear out the
91
     * 'unprocessed' list.
92
     *
93
     * @return array
94
     */
95
    protected function fetchUnprocessed()
96
    {
97
        $toBeProcessed = $this->unprocessedConfig;
98
        $this->unprocessedConfig = [];
99
        return $toBeProcessed;
100
    }
101
102
    /**
103
     * Use a map-reduce to evaluate the items to be processed,
104
     * and merge them into the processed array.
105
     *
106
     * @param array $processed
107
     * @param array $toBeProcessed
108
     * @return array
109
     */
110
    protected function process($processed, $toBeProcessed)
111
    {
112
        $toBeReduced = array_map([$this, 'preprocess'], $toBeProcessed);
113
        $reduced = array_reduce($toBeReduced, [$this, 'reduceOne'], $processed);
114
        return $this->evaluate($reduced);
115
    }
116
117
    /**
118
     * Process a single configuration file from the 'to be processed'
119
     * list. By default this is a no-op. Override this method to
120
     * provide any desired configuration preprocessing, e.g. dot-notation
121
     * expansion of the configuration keys, etc.
122
     *
123
     * @param array $config
124
     * @return array
125
     */
126
    protected function preprocess($config)
127
    {
128
        return $config;
129
    }
130
131
    /**
132
     * Evaluate one item in the 'to be evaluated' list, and then
133
     * merge it into the processed configuration (the 'carry').
134
     *
135
     * @param array $processed
136
     * @param array $config
137
     * @return array
138
     */
139
    protected function reduceOne($processed, $config)
140
    {
141
        return static::arrayMergeRecursiveDistinct($processed, $config);
142
    }
143
144
    /**
145
     * Evaluate one configuration item.
146
     *
147
     * @param array $processed
0 ignored issues
show
Bug introduced by
There is no parameter named $processed. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
148
     * @param array $config
149
     * @return array
150
     */
151
    protected function evaluate($config)
152
    {
153
        return Expander::expandArrayProperties(
154
            $config,
155
            []
156
        );
157
    }
158
159
    /**
160
     * Merges arrays recursively while preserving.
161
     *
162
     * @param array $array1
163
     * @param array $array2
164
     *
165
     * @return array
166
     *
167
     * @see http://php.net/manual/en/function.array-merge-recursive.php#92195
168
     * @see https://github.com/grasmash/bolt/blob/robo-rebase/src/Robo/Common/ArrayManipulator.php#L22
169
     */
170
    public static function arrayMergeRecursiveDistinct(
171
        array &$array1,
172
        array &$array2
173
    ) {
174
        $merged = $array1;
175
        foreach ($array2 as $key => &$value) {
176
            if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
177
                $merged[$key] = self::arrayMergeRecursiveDistinct($merged[$key], $value);
178
            } else {
179
                $merged[$key] = $value;
180
            }
181
        }
182
        return $merged;
183
    }
184
185
    /**
186
     * Replaces all of the leaf-node values of a nested array with the
187
     * provided replacement value.
188
     */
189
    public static function arrayReplaceValueRecursive(array $data, $replacement)
190
    {
191
        $result = [];
192
        foreach ($data as $key => $value) {
193
            if (is_array($value)) {
194
                $result[$key] = self::arrayReplaceValueRecursive($value, $replacement);
195
            } else {
196
                $result[$key] = $replacement;
197
            }
198
        }
199
        return $result;
200
    }
201
}
202