Passed
Push — master ( ca00df...8560bb )
by Kenneth
02:04
created

GLConf::fillPlaceHolders()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 33
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 33
rs 9.4555
c 0
b 0
f 0
cc 5
nc 2
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 mixed $value
108
     * @return mixed
109
     */
110
    private function fillPlaceHolders($value)
111
    {
112
        // Certain recursive stuff, like @[SelfReferencedPlaceholder.@[SomeStuff.a]] is what triggers this part.
113
        // Find the self referenced placeholders and fill them.
114
        $data = preg_replace_callback('/@\[([a-zA-Z0-9_.-]*?)]/',
115
            function ($matches) {
116
                // Does this key exist, is so fill this match, if not, just return the match intact.
117
                return $this->get($matches[1]) ?: $matches[0];
118
            },
119
            $value);
120
121
        // Find the recursive self referenced placeholders and fill them.
122
        if ($data !== $value && preg_match('/@\[([a-zA-Z0-9_.-]*?)]/', $data)) {
123
            $data = $this->processConfig($data);
124
        }
125
126
        // Find the environment variable placeholders and fill them.
127
        $data = preg_replace_callback('/\$\[([a-zA-Z0-9_.-]*?)]/',
128
            static function ($matches) {
129
                // If locally set environment variable (variable not set by a SAPI) found, replace with it's value.
130
                if (!empty(getenv($matches[1], true))) {
131
                    // Try local only environment variables first (variable not set by a SAPI)
132
                    $ret = getenv($matches[1], true);
133
                } else {
134
                    // Don't replace.
135
                    $ret = $matches[0];
136
                }
137
138
                return $ret;
139
            },
140
            $data);
141
142
        return $data;
143
    }
144
145
    /**
146
     * Run through the configuration and process the placeholders.
147
     *
148
     * @param mixed $data
149
     * @return mixed
150
     */
151
    private function processConfig($data)
152
    {
153
        if (is_array($data)) {
154
            // It's an array, so let's loop through it.
155
            foreach ($data as $k => $val) {
156
                if (!is_array($val)) {
157
                    $data[$k] = $this->fillPlaceHolders($val);
158
                } else {
159
                    // Recursively replace placeholders.
160
                    $data[$k] = $this->processConfig($val);
161
                }
162
            }
163
        } elseif (is_string($data)) {
164
            $data = $this->fillPlaceHolders($data);
165
        }
166
167
        return $data;
168
    }
169
170
    /**
171
     * Initialize the configuration system.
172
     */
173
    public function init(): void
174
    {
175
        // Load main (top level) configuration and conform it (uppercase and changes spaces to underscores in keys).
176
        $this->configuration = $this->driver->parseConfigurationFile();
177
        $this->configuration = $this->conformArray($this->configuration);
178
        $config = [];
179
180
        // Load in the extra configuration via the CONF property.
181
        if (isset($this->configuration['CONF']) && is_array($this->configuration['CONF'])) {
182
            foreach ($this->configuration['CONF'] as $file) {
183
                // Load in the referenced configuration from the main configuration.
184
                $innerConfig = $this->driver->parseConfigurationFile($file);
185
186
                // Conform the configuration array.
187
                $innerConfig = $this->conformArray($innerConfig);
188
189
                // Strip out anything that wasn't in a section (non-array value at the top level).
190
                // We don't want the ability to overwrite stuff from main configuration file.
191
                foreach ($innerConfig as $k => $v) {
192
                    if (!is_array($v)) {
193
                        unset($innerConfig[$k]);
194
                    }
195
                }
196
197
                // Store conformed configuration into temporary array for merging later.
198
                $config[] = $innerConfig;
199
            }
200
201
            // Combine/Merge/Overwrite compiled configuration with current.
202
            // Uses the splat operator on the arrays stored in the temporary config.
203
            $this->configuration = array_replace_recursive($this->configuration, ...$config) ?? [];
204
        }
205
206
        // Fill in the placeholders.
207
        $this->configuration = $this->processConfig($this->configuration);
208
    }
209
}
210