Passed
Push — master ( ab3d25...c39df5 )
by Kenneth
01:40
created

GLConf::fillPlaceHolders()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 49
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 49
rs 8.4444
c 0
b 0
f 0
cc 8
nc 9
nop 1
1
<?php
2
3
namespace GeekLab\Conf;
4
5
use GeekLab\Conf\Driver\ConfDriverInterface;
6
7
final class GLConf
8
{
9
    /** @var ConfDriverInterface $driver */
10
    private $driver;
11
12
    /** @var array $configuration The compiled configuration. */
13
    protected $configuration = [];
14
15
    /**
16
     * GLConf constructor.
17
     * Inject our driver (strategy) here.
18
     *
19
     * @param ConfDriverInterface $driver
20
     */
21
    public function __construct(ConfDriverInterface $driver)
22
    {
23
        $this->driver = $driver;
24
    }
25
26
    /**
27
     * Return the compiled configuration.
28
     *
29
     * @return array
30
     */
31
    public function getAll(): array
32
    {
33
        return $this->configuration;
34
    }
35
36
    /**
37
     * Get data by dot notation.
38
     * Stolen from: https://stackoverflow.com/a/14706302/344028
39
     *
40
     * @param string $key dot notated array key accessor.
41
     *
42
     * @return mixed
43
     */
44
    public function get(string $key)
45
    {
46
        /** @var string $key Convert key to upper case. */
47
        $key = strtoupper($key);
48
49
        /** @var mixed $config Save configuration for local scope modification. */
50
        $config = $this->configuration;
51
52
        /** @var bool|array $token Tokenize the key to do iterations over the config with. */
53
        $token = strtok($key, '.');
54
55
        // Loop until we are out of tokens.
56
        while ($token !== false) {
57
            if (!isset($config[$token])) {
58
                // Array key of $token wasn't found.
59
                return false;
60
            }
61
62
            // Save the data found.
63
            $config = $config[$token];
64
65
            // Advanced to the next token, or set token to false if nothing else if left..
66
            $token = strtok('.');
67
        }
68
69
        // Return the valid found by the previous loop.
70
        return $config;
71
    }
72
73
    /**
74
     * Make the array conform to some sort of standard.
75
     * -  Convert key names to uppercase.
76
     * -  Convert spaces and periods to underscores.
77
     *
78
     * @param array $arr
79
     *
80
     * @return array
81
     */
82
    protected function conformArray(array $arr): array
83
    {
84
        // Store our conformed array for returning.
85
        $fixed = [];
86
87
        // Convert keys to uppercase.
88
        $arr = array_change_key_case($arr, CASE_UPPER);
89
90
        foreach ($arr as $k => $v) {
91
            // Recursively conform the inner arrays.
92
            if (is_array($v)) {
93
                $v = $this->conformArray($v);
94
            }
95
96
            // Replace spaces and periods with underscores.
97
            $fixed[preg_replace('/\s+|\.+/', '_', $k)] = $v;
98
        }
99
100
        // Return the conformed array.
101
        return $fixed;
102
    }
103
104
    /**
105
     * Self referenced and environment variable placeholder replacement.
106
     *
107
     * @param string $value
108
     *
109
     * @return string
110
     */
111
    private function fillPlaceHolders(string $value): string
112
    {
113
        // Certain recursive stuff, like @[SelfReferencedPlaceholder.@[SomeStuff.a]] is what triggers this part.
114
        // Find the self referenced placeholders and fill them.
115
        $data = preg_replace_callback(
116
            '/@\[([a-zA-Z0-9_.-]*?)]/',
117
            function ($matches) {
118
                // Does this key exist, is so fill this match, if not, just return the match intact.
119
                return $this->get($matches[1]) ?: $matches[0];
120
            },
121
            $value
122
        );
123
124
        if ($data === null) {
125
            return '';
126
        }
127
128
        if (is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always false.
Loading history...
129
            $data = $this->fillPlaceHolders($this->processConfig($data));
130
        }
131
132
        // Find the recursive self referenced placeholders and fill them.
133
        if ($data !== $value && preg_match('/@\[([a-zA-Z0-9_.-]*?)]/', $data)) {
134
            $data = $this->processConfig($data);
135
        }
136
137
        // Find the environment variable placeholders and fill them.
138
        $data = preg_replace_callback(
139
            '/\$\[([a-zA-Z0-9_.-]*?)]/',
140
            static function ($matches) {
141
                // If locally set environment variable (variable not set by a SAPI) found, replace with it's value.
142
                if (!empty(getenv($matches[1], true))) {
143
                    // Try local only environment variables first (variable not set by a SAPI)
144
                    $ret = getenv($matches[1], true);
145
                } else {
146
                    // Don't replace.
147
                    $ret = $matches[0];
148
                }
149
150
                return $ret;
151
            },
152
            $data
153
        );
154
155
        if (is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always false.
Loading history...
156
            $data = $this->fillPlaceHolders($this->processConfig($data));
157
        }
158
159
        return $data ?? '';
160
    }
161
162
    /**
163
     * Run through the configuration and process the placeholders.
164
     *
165
     * @param mixed $data
166
     * @return mixed
167
     */
168
    private function processConfig($data)
169
    {
170
        if (is_array($data)) {
171
            // It's an array, so let's loop through it.
172
            foreach ($data as $k => $val) {
173
                if (is_string($val)) {
174
                    $data[$k] = $this->fillPlaceHolders($val);
175
                } else {
176
                    // Recursively replace placeholders.
177
                    $data[$k] = $this->processConfig($val);
178
                }
179
            }
180
        } elseif (is_string($data)) {
181
            $data = $this->fillPlaceHolders($data);
182
        }
183
184
        return $data;
185
    }
186
187
    /**
188
     * Initialize the configuration system.
189
     */
190
    public function init(): void
191
    {
192
        // Load main (top level) configuration and conform it (uppercase and changes spaces to underscores in keys).
193
        $this->configuration = $this->driver->parseConfigurationFile();
194
        $this->configuration = $this->conformArray($this->configuration);
195
        $config = [];
196
197
        // Load in the extra configuration via the CONF property.
198
        if (isset($this->configuration['CONF']) && is_array($this->configuration['CONF'])) {
199
            foreach ($this->configuration['CONF'] as $file) {
200
                // Load in the referenced configuration from the main configuration.
201
                $innerConfig = $this->driver->parseConfigurationFile($file);
202
203
                // Conform the configuration array.
204
                $innerConfig = $this->conformArray($innerConfig);
205
206
                // Strip out anything that wasn't in a section (non-array value at the top level).
207
                // We don't want the ability to overwrite stuff from main configuration file.
208
                foreach ($innerConfig as $k => $v) {
209
                    if (!is_array($v)) {
210
                        unset($innerConfig[$k]);
211
                    }
212
                }
213
214
                // Store conformed configuration into temporary array for merging later.
215
                $config[] = $innerConfig;
216
            }
217
218
            // Combine/Merge/Overwrite compiled configuration with current.
219
            // Uses the splat operator on the arrays stored in the temporary config.
220
            $this->configuration = array_replace_recursive($this->configuration, ...$config) ?? [];
221
        }
222
223
        // Fill in the placeholders.
224
        $this->configuration = $this->processConfig($this->configuration);
225
    }
226
}
227