Passed
Push — master ( 8ad8be...f5c6b6 )
by Kenneth
01:43
created

GLConf   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 25
eloc 62
dl 0
loc 210
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A conformArray() 0 20 3
A get() 0 27 3
A getAll() 0 3 1
A fillPlaceHolders() 0 41 6
A processConfig() 0 17 5
A init() 0 35 6
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
        // Find the recursive self referenced placeholders and fill them.
129
        if ($data !== $value && preg_match('/@\[([a-zA-Z0-9_.-]*?)]/', $data)) {
130
            $data = $this->processConfig($data);
131
        }
132
133
        // Find the environment variable placeholders and fill them.
134
        $data = preg_replace_callback(
135
            '/\$\[([a-zA-Z0-9_.-]*?)]/',
136
            static function ($matches) {
137
                // If locally set environment variable (variable not set by a SAPI) found, replace with it's value.
138
                if (!empty(getenv($matches[1], true))) {
139
                    // Try local only environment variables first (variable not set by a SAPI)
140
                    $ret = getenv($matches[1], true);
141
                } else {
142
                    // Don't replace.
143
                    $ret = $matches[0];
144
                }
145
146
                return $ret;
147
            },
148
            $data
149
        );
150
151
        return $data ?? '';
152
    }
153
154
    /**
155
     * Run through the configuration and process the placeholders.
156
     *
157
     * @param mixed $data
158
     * @return mixed
159
     */
160
    private function processConfig($data)
161
    {
162
        if (is_array($data)) {
163
            // It's an array, so let's loop through it.
164
            foreach ($data as $k => $val) {
165
                if (is_string($val)) {
166
                    $data[$k] = $this->fillPlaceHolders($val);
167
                } else {
168
                    // Recursively replace placeholders.
169
                    $data[$k] = $this->processConfig($val);
170
                }
171
            }
172
        } elseif (is_string($data)) {
173
            $data = $this->fillPlaceHolders($data);
174
        }
175
176
        return $data;
177
    }
178
179
    /**
180
     * Initialize the configuration system.
181
     */
182
    public function init(): void
183
    {
184
        // Load main (top level) configuration and conform it (uppercase and changes spaces to underscores in keys).
185
        $this->configuration = $this->driver->parseConfigurationFile();
186
        $this->configuration = $this->conformArray($this->configuration);
187
        $config = [];
188
189
        // Load in the extra configuration via the CONF property.
190
        if (isset($this->configuration['CONF']) && is_array($this->configuration['CONF'])) {
191
            foreach ($this->configuration['CONF'] as $file) {
192
                // Load in the referenced configuration from the main configuration.
193
                $innerConfig = $this->driver->parseConfigurationFile($file);
194
195
                // Conform the configuration array.
196
                $innerConfig = $this->conformArray($innerConfig);
197
198
                // Strip out anything that wasn't in a section (non-array value at the top level).
199
                // We don't want the ability to overwrite stuff from main configuration file.
200
                foreach ($innerConfig as $k => $v) {
201
                    if (!is_array($v)) {
202
                        unset($innerConfig[$k]);
203
                    }
204
                }
205
206
                // Store conformed configuration into temporary array for merging later.
207
                $config[] = $innerConfig;
208
            }
209
210
            // Combine/Merge/Overwrite compiled configuration with current.
211
            // Uses the splat operator on the arrays stored in the temporary config.
212
            $this->configuration = array_replace_recursive($this->configuration, ...$config) ?? [];
213
        }
214
215
        // Fill in the placeholders.
216
        $this->configuration = $this->processConfig($this->configuration);
217
    }
218
}
219