a00c976f11c506042c804f39a67c1388c7a224c8
[m6w6/merry] / lib / merry / Config.php
1 <?php
2
3 /**
4 * merry\Config
5 *
6 * @author Michael Wallner <mike@php.net>
7 */
8 namespace merry;
9
10 /**
11 * A merry config container
12 *
13 * @see https://github.com/m6w6/merry
14 * @package merry\Config
15 */
16 class Config implements \ArrayAccess, \RecursiveIterator
17 {
18 /**
19 * Index for a numerically indexed array
20 * @internal
21 * @var int
22 */
23 private $index = 0;
24
25 /**
26 * Container
27 * @internal
28 * @var stdClass
29 */
30 private $props;
31
32 /**
33 * State for the RecursiveIterator
34 * @internal
35 * @var array
36 */
37 private $riter;
38
39 /**
40 * Create a new configuration container
41 * @param array $array the configuration array
42 * @param string $section the section to use (i.e. first level key)
43 * @param string $section_sep a separator for section extension
44 * @param string $key_sep a separator for key traversal
45 */
46 public function __construct(array $array = null, $section = null, $section_sep = ":", $key_sep = ".") {
47 $this->props = new \stdClass;
48
49 if (isset($section) && strlen($section_sep)) {
50 $array = $this->combine($array, $section_sep)[$section];
51 }
52 if ($array) {
53 $config = array();
54
55 if (strlen($key_sep)) {
56 foreach ($array as $key => $val) {
57 $this->walk($config, $key, $val, $key_sep);
58 }
59 }
60
61 foreach ($config as $property => $value) {
62 $this->__set($property, $value);
63 }
64 }
65 }
66
67 /**
68 * Combine individual sections with their parent section
69 * @param array $array the config array
70 * @param string $section_sep the section extension separator
71 * @return array merged sections
72 */
73 protected function combine($array, $section_sep) {
74 foreach ($array as $section_spec => $settings) {
75 $section_spec = array_map("trim", explode($section_sep, $section_spec));
76 if (count($section_spec) > 1) {
77 $sections[$section_spec[0]] = array_merge(
78 $sections[$section_spec[1]],
79 $settings
80 );
81 } else {
82 $sections[$section_spec[0]] = $settings;
83 }
84 }
85 return $sections;
86 }
87
88 /**
89 * Walk a key split by the key separator into an array up and set the
90 * respective value on the leaf
91 * @param mixed $ptr current leaf pointer in the array
92 * @param string $key the array key
93 * @param mixed $val the value to set
94 * @param string $key_sep the key separator for traversal
95 */
96 protected function walk(&$ptr, $key, $val, $key_sep) {
97 foreach (explode($key_sep, $key) as $sub) {
98 $ptr = &$ptr[$sub];
99 }
100 $ptr = $val;
101 }
102
103 /**
104 * Recursively turn a Config instance and its childs into an array
105 * @param \merry\Config $o the Config instance to convert to an array
106 * @return array
107 */
108 protected function arrayify(Config $o) {
109 $a = [];
110
111 foreach ($o->props as $k => $v) {
112 if ($v instanceof Config) {
113 $a[$k] = $this->arrayify($v);
114 } else {
115 $a[$k] = $v;
116 }
117 }
118
119 return $a;
120 }
121
122 /**
123 * Apply one or mor modifier callbacks
124 * @param mixed $modifier
125 * @return \merry\Config
126 */
127 public function apply($modifier) {
128 if (is_callable($modifier)) {
129 foreach ($this->props as $prop => $value) {
130 $this->__set($prop, $modifier($value, $prop));
131 }
132 } else {
133 foreach ($modifier as $key => $mod) {
134 if (is_callable($mod)) {
135 $this->props->$key = $mod(isset($this->props->$key) ? $this->props->$key : null, $key);
136 } elseif (is_array($mod)) {
137 $this->props->$key->apply($mod);
138 } else {
139 /* */
140 }
141 }
142 }
143 return $this;
144 }
145
146 /**
147 * Return the complete config as array
148 * @return array
149 */
150 function toArray() {
151 return $this->arrayify($this);
152 }
153
154 /**
155 * @ignore
156 */
157 function __get($prop) {
158 return $this->props->$prop;
159 }
160
161 /**
162 * @ignore
163 */
164 function __set($prop, $value) {
165 if (isset($value) && !is_scalar($value) && !($value instanceof Config)) {
166 $value = new static((array) $value);
167 }
168 if (!strlen($prop)) {
169 $prop = $this->index++;
170 } elseif (is_numeric($prop) && !strcmp($prop, (int) $prop)) {
171 /* update internal index */
172 if ($prop >= $this->index) {
173 $this->index = $prop + 1;
174 }
175 }
176
177 $this->props->$prop = $value;
178 }
179
180 /**
181 * @ignore
182 */
183 function __isset($prop) {
184 return isset($this->props->$prop);
185 }
186
187 /**
188 * @ignore
189 */
190 function __unset($prop) {
191 unset($this->props->$prop);
192 }
193
194 /**
195 * @ignore
196 */
197 function offsetGet($o) {
198 return $this->props->$o;
199 }
200
201 /**
202 * @ignore
203 */
204 function offsetSet($o, $v) {
205 $this->__set($o, $v);
206 }
207
208 /**
209 * @ignore
210 */
211 function offsetExists($o) {
212 return isset($this->props->$o);
213 }
214
215 /**
216 * @ignore
217 */
218 function offsetUnset($o) {
219 unset($this->props->$o);
220 }
221
222 /**
223 * @ignore
224 */
225 function rewind() {
226 $this->riter = (array) $this->props;
227 reset($this->riter);
228 }
229
230 /**
231 * @ignore
232 */
233 function valid() {
234 return NULL !== key($this->riter);
235 }
236
237 /**
238 * @ignore
239 */
240 function next() {
241 next($this->riter);
242 }
243
244 /**
245 * @ignore
246 */
247 function key() {
248 return key($this->riter);
249 }
250
251 /**
252 * @ignore
253 */
254 function current() {
255 return current($this->riter);
256 }
257
258 /**
259 * @ignore
260 */
261 function hasChildren() {
262 return current($this->riter) instanceof Config;
263 }
264
265 /**
266 * @ignore
267 */
268 function getChildren() {
269 return current($this->riter);
270 }
271 }