1 <?php
2
3 4 5 6 7 8 9 10
11
12 namespace ICanBoogie;
13
14 15 16
17 class Configs implements \ArrayAccess
18 {
19 protected $paths = [];
20 protected $constructors = [];
21 protected $configs = [];
22
23 public $cache_repository;
24
25 public function __construct($paths, $constructors)
26 {
27 foreach ($paths as $path)
28 {
29 $weight = 0;
30
31 if (is_array($path))
32 {
33 $weight = isset($path['weight']) ? $path['weight'] : 0;
34 $path = $path[0];
35 }
36
37 $this->add($path, $weight);
38 }
39
40 $this->constructors = $constructors;
41 }
42
43 public function offsetSet($offset, $value)
44 {
45 throw new OffsetNotWritable([ $offset, $this ]);
46 }
47
48 49 50 51 52
53 public function offsetExists($offset)
54 {
55 isset($this->configs[$offsets]);
56 }
57
58 59 60
61 public function offsetUnset($offset)
62 {
63 throw new OffsetNotWritable([ $offset, $this ]);
64 }
65
66 67 68 69 70
71 public function offsetGet($id)
72 {
73 if (isset($this->configs[$id]))
74 {
75 return $this->configs[$id];
76 }
77
78 if (empty($this->constructors[$id]))
79 {
80 throw new Exception('There is no constructor defined to build the %id config.', [ '%id' => $id ]);
81 }
82
83 list($constructor, $from) = $this->constructors[$id] + [ 1 => $id ];
84
85 return $this->synthesize($id, $constructor, $from);
86 }
87
88 89 90 91 92
93 protected function revoke_configs()
94 {
95 $this->configs = [];
96 }
97
98 99 100 101 102 103 104 105 106 107 108
109 public function add($path, $weight=0)
110 {
111 if (!$path)
112 {
113 throw new \InvalidArgumentException('$path is empty.');
114 }
115
116 $this->revoke_configs();
117
118 if (is_array($path))
119 {
120 $combined = array_combine($path, array_fill(0, count($path), $weight));
121
122 foreach ($combined as $path => $weight)
123 {
124 if (!file_exists($path))
125 {
126 trigger_error(format('Config path %path does not exists', [ 'path' => $path ]));
127 }
128 }
129
130 $this->paths += $combined;
131 }
132 else
133 {
134 $this->paths[$path] = $weight;
135 }
136
137 stable_sort($this->paths);
138 }
139
140 static private $require_cache = [];
141
142 static private function isolated_require($__file__, $path)
143 {
144 if (isset(self::$require_cache[$__file__]))
145 {
146 return self::$require_cache[$__file__];
147 }
148
149 return self::$require_cache[$__file__] = require $__file__;
150 }
151
152 153 154 155 156 157 158 159
160 public function get_fragments($name)
161 {
162 $fragments = [];
163 $filename = $name . '.php';
164
165 foreach ($this->paths as $path => $weight)
166 {
167 $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
168 $pathname = $path . $filename;
169
170 if (!file_exists($pathname))
171 {
172 continue;
173 }
174
175 $fragments[$path . $filename] = self::isolated_require($pathname, $path);
176 }
177
178 return $fragments;
179 }
180
181 static private $syntheses_cache;
182
183 184 185 186 187 188 189 190 191 192
193 public function synthesize($name, $constructor, $from=null)
194 {
195 if (isset($this->configs[$name]))
196 {
197 return $this->configs[$name];
198 }
199
200 if (!$from)
201 {
202 $from = $name;
203 }
204
205 $args = [ $from, $constructor ];
206
207 if ($this->cache_repository)
208 {
209 $cache = self::$syntheses_cache
210 ? self::$syntheses_cache
211 : self::$syntheses_cache = new FileCache
212 ([
213 FileCache::T_REPOSITORY => $this->cache_repository,
214 FileCache::T_SERIALIZE => true
215 ]);
216
217 $rc = $cache->load('config_' . normalize($name, '_'), [ $this, 'synthesize_constructor' ], $args);
218 }
219 else
220 {
221 $rc = $this->synthesize_constructor($args);
222 }
223
224 $this->configs[$name] = $rc;
225
226 return $rc;
227 }
228
229 public function synthesize_constructor(array $userdata)
230 {
231 list($name, $constructor) = $userdata;
232
233 $fragments = $this->get_fragments($name);
234
235 if (!$fragments)
236 {
237 return;
238 }
239
240 if ($constructor == 'merge')
241 {
242 $rc = call_user_func_array('array_merge', $fragments);
243 }
244 else if ($constructor == 'recursive merge')
245 {
246 $rc = call_user_func_array('ICanBoogie\array_merge_recursive', $fragments);
247 }
248 else
249 {
250 $rc = call_user_func($constructor, $fragments);
251 }
252
253 return $rc;
254 }
255 }