Passed
Branch master (29d251)
by Kenneth
02:15 queued 56s
created

GLConf::stringPlaceholderHandler()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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