Config::get()   B
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 3
nop 1
dl 0
loc 24
rs 8.5125
c 0
b 0
f 0
1
<?php
2
3
namespace Fwolf\Config;
4
5
use Fwolf\Config\Exception\KeyNotExist;
6
use Fwolf\Config\Exception\ReachLeafNode;
7
8
/**
9
 * Config class
10
 *
11
 *
12
 * Config keys can have hierarchy, split by separator.
13
 *
14
 * Eg: There are config with keys below:
15
 *  - foo.bar1 = 1
16
 *  - foo.bar2 = 2
17
 *  - foo.bar3 = 3
18
 *
19
 * While key foo.barN have their own value(1, 2 or 3), key foo can also be used
20
 * standalone, with associate array value {bar1 => 1, bar2 => 2, bar3 => 3}.
21
 *
22
 *
23
 * @copyright   Copyright 2013-2017 Fwolf
24
 * @license     https://opensource.org/licenses/MIT MIT
25
 */
26
class Config implements ConfigInterface
27
{
28
    /**
29
     * Separator for config key sections
30
     */
31
    const SEPARATOR = '.';
32
33
34
    /**
35
     * Config data array
36
     *
37
     * @var array
38
     */
39
    protected $configs = [];
40
41
42
    /**
43
     * {@inheritdoc}
44
     *
45
     * When delete a node with leaf, all leaves(sub tree) are deleted too.
46
     *
47
     * Even all leaf node deleted, empty parent node still exists.
48
     */
49
    public function delete($key)
50
    {
51
        if (false === strpos($key, static::SEPARATOR)) {
52
            unset($this->configs[$key]);
53
54
        } else {
55
            // Goto deepest parent node, then delete remain key
56
            $sections = explode(static::SEPARATOR, $key);
57
            $configPointer = &$this->configs;
58
59
            while (1 < count($sections)) {
60
                $currentKey = array_shift($sections);
61
62
                if (isset($configPointer[$currentKey])) {
63
                    $configPointer = &$configPointer[$currentKey];
64
65
                } else {
66
                    break;
67
                }
68
            }
69
70
            unset($configPointer[implode(static::SEPARATOR, $sections)]);
71
        }
72
73
        return $this;
74
    }
75
76
77
    /**
78
     * {@inheritdoc}
79
     *
80
     * Return array when get parent nodes.
81
     *
82
     * @throws  KeyNotExist
83
     */
84
    public function get($key)
85
    {
86
        if (false === strpos($key, static::SEPARATOR)) {
87
            if (key_exists($key, $this->configs)) {
88
                return $this->configs[$key];
89
            } else {
90
                throw new KeyNotExist("Key '$key' not found");
91
            }
92
93
        } else {
94
            // Check through each level
95
            $sections = explode(static::SEPARATOR, $key);
96
            $configPointer = $this->configs;
97
98
            // Loop match value, each loop go deeper in multi-dimension array
99
            foreach ($sections as $section) {
100
                if (key_exists($section, $configPointer)) {
101
                    $configPointer = $configPointer[$section];
102
                } else {
103
                    throw new KeyNotExist("Key '$key' not found");
104
                }
105
            }
106
107
            return ($configPointer);
108
        }
109
    }
110
111
112
    /**
113
     * {@inheritdoc}
114
     *
115
     * Result maybe multi dimension array.
116
     */
117
    public function getRaw()
118
    {
119
        return $this->configs;
120
    }
121
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function load(array $configData)
127
    {
128
        $this->configs = [];
129
130
        if (!empty($configData)) {
131
            $this->setMultiple($configData);
132
        }
133
134
        return $this;
135
    }
136
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function offsetExists($offset)
142
    {
143
        try {
144
            $this->get($offset);
145
        } catch (KeyNotExist $e) {
146
            return false;
147
        }
148
149
        return true;
150
    }
151
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function offsetGet($offset)
157
    {
158
        return $this->get($offset);
159
    }
160
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function offsetSet($offset, $value)
166
    {
167
        $this->set($offset, $value);
168
    }
169
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function offsetUnset($offset)
175
    {
176
        $this->delete($offset);
177
    }
178
179
180
    /**
181
     * {@inheritdoc}
182
     *
183
     * If $key include separator, will convert to array by it recurrently.
184
     *
185
     * Eg: system.format.time => $this->config['system']['format']['time']
186
     *
187
     * Notice: This is extend of config key, do not confuse with config value
188
     * which is already array, although they may have same result.
189
     */
190
    public function set($key, $value)
191
    {
192
        if (false === strpos($key, static::SEPARATOR)) {
193
            $this->configs[$key] = $value;
194
195
        } else {
196
            // Recognize separator
197
            $sections = explode(static::SEPARATOR, $key);
198
            $parentLevels = count($sections) - 1;
199
            $configPointer = &$this->configs;
200
201
            // Check and create middle level for multi-dimension array
202
            // Pointer change every loop, goes deeper to sub array
203
            for ($i = 0; $i < $parentLevels; $i++) {
204
                $currentKey = $sections[$i];
205
206
                // 'a.b.c', if b is not set, create it as an empty array
207
                if (!key_exists($currentKey, $configPointer)) {
208
                    $configPointer[$currentKey] = [];
209
                }
210
211
                // Go down to next level
212
                $configPointer = &$configPointer[$currentKey];
213
214
                if (!is_array($configPointer)) {
215
                    $scannedKeys = array_slice($sections, 0, $i + 1);
216
                    $scannedKeyStr = implode(static::SEPARATOR, $scannedKeys);
217
                    throw new ReachLeafNode(
218
                        "Key '$scannedKeyStr' has assigned single value, " .
219
                        "did not accept array/multiple value assign"
220
                    );
221
                }
222
            }
223
224
            // At last level, set the value
225
            $configPointer[$sections[$parentLevels]] = $value;
226
        }
227
228
        return $this;
229
    }
230
231
232
    /**
233
     * {@inheritdoc}
234
     */
235
    public function setMultiple(array $configData)
236
    {
237
        foreach ($configData as $key => $value) {
238
            $this->set($key, $value);
239
        }
240
241
        return $this;
242
    }
243
}
244