|
1 <?php |
|
2 |
|
3 /* |
|
4 * This file is part of Mandango. |
|
5 * |
|
6 * (c) Pablo Díez <pablodip@gmail.com> |
|
7 * |
|
8 * This source file is subject to the MIT license that is bundled |
|
9 * with this source code in the file LICENSE. |
|
10 */ |
|
11 |
|
12 namespace Mandango\Mondator; |
|
13 |
|
14 use Mandango\Mondator\Definition\Method; |
|
15 use Mandango\Mondator\Definition\Property; |
|
16 |
|
17 /** |
|
18 * ClassExtension is the base class for class extensions. |
|
19 * |
|
20 * @author Pablo Díez <pablodip@gmail.com> |
|
21 * |
|
22 * @api |
|
23 */ |
|
24 abstract class ClassExtension |
|
25 { |
|
26 private $options; |
|
27 private $requiredOptions; |
|
28 |
|
29 protected $definitions; |
|
30 |
|
31 protected $class; |
|
32 protected $configClasses; |
|
33 protected $configClass; |
|
34 |
|
35 protected $newClassExtensions; |
|
36 protected $newConfigClasses; |
|
37 |
|
38 protected $twig; |
|
39 protected $twigTempDir; |
|
40 |
|
41 /** |
|
42 * Constructor. |
|
43 * |
|
44 * @param array $options An array of options. |
|
45 * |
|
46 * @api |
|
47 */ |
|
48 public function __construct(array $options = array()) |
|
49 { |
|
50 $this->options = array(); |
|
51 $this->requiredOptions = array(); |
|
52 |
|
53 $this->setUp(); |
|
54 |
|
55 foreach ($options as $name => $value) { |
|
56 $this->setOption($name, $value); |
|
57 } |
|
58 |
|
59 // required options |
|
60 if ($diff = array_diff($this->requiredOptions, array_keys($options))) { |
|
61 throw new \RuntimeException(sprintf('%s requires the options: "%s".', get_class($this), implode(', ', $diff))); |
|
62 } |
|
63 } |
|
64 |
|
65 /** |
|
66 * Set up the extension. |
|
67 * |
|
68 * @api |
|
69 */ |
|
70 protected function setUp() |
|
71 { |
|
72 } |
|
73 |
|
74 /** |
|
75 * Add an option. |
|
76 * |
|
77 * @param string $name The option name. |
|
78 * @param mixed $defaultValue The default value (optional, null by default). |
|
79 * |
|
80 * @api |
|
81 */ |
|
82 protected function addOption($name, $defaultValue = null) |
|
83 { |
|
84 $this->options[$name] = $defaultValue; |
|
85 } |
|
86 |
|
87 /** |
|
88 * Add options. |
|
89 * |
|
90 * @param array $options An array with options (name as key and default value as value). |
|
91 * |
|
92 * @api |
|
93 */ |
|
94 protected function addOptions(array $options) |
|
95 { |
|
96 foreach ($options as $name => $defaultValue) { |
|
97 $this->addOption($name, $defaultValue); |
|
98 } |
|
99 } |
|
100 |
|
101 /** |
|
102 * Add a required option. |
|
103 * |
|
104 * @param string $name The option name. |
|
105 * |
|
106 * @api |
|
107 */ |
|
108 protected function addRequiredOption($name) |
|
109 { |
|
110 $this->addOption($name); |
|
111 |
|
112 $this->requiredOptions[] = $name; |
|
113 } |
|
114 |
|
115 /** |
|
116 * Add required options. |
|
117 * |
|
118 * @param array $options An array with the name of the required option as value. |
|
119 * |
|
120 * @api |
|
121 */ |
|
122 protected function addRequiredOptions(array $options) |
|
123 { |
|
124 foreach ($options as $name) { |
|
125 $this->addRequiredOption($name); |
|
126 } |
|
127 } |
|
128 |
|
129 /** |
|
130 * Returns if exists an option. |
|
131 * |
|
132 * @param string $name The name. |
|
133 * |
|
134 * @return bool Returns true if the option exists, false otherwise. |
|
135 * |
|
136 * @api |
|
137 */ |
|
138 public function hasOption($name) |
|
139 { |
|
140 return array_key_exists($name, $this->options); |
|
141 } |
|
142 |
|
143 /** |
|
144 * Set an option. |
|
145 * |
|
146 * @param string $name The name. |
|
147 * @param mixed $value The value. |
|
148 * |
|
149 * @throws \InvalidArgumentException If the option does not exists. |
|
150 * |
|
151 * @api |
|
152 */ |
|
153 public function setOption($name, $value) |
|
154 { |
|
155 if (!$this->hasOption($name)) { |
|
156 throw new \InvalidArgumentException(sprintf('The option "%s" does not exists.', $name)); |
|
157 } |
|
158 |
|
159 $this->options[$name] = $value; |
|
160 } |
|
161 |
|
162 /** |
|
163 * Returns the options. |
|
164 * |
|
165 * @return array The options. |
|
166 * |
|
167 * @api |
|
168 */ |
|
169 public function getOptions() |
|
170 { |
|
171 return $this->options; |
|
172 } |
|
173 |
|
174 /** |
|
175 * Return an option. |
|
176 * |
|
177 * @param string $name The name. |
|
178 * |
|
179 * @return mixed The value of the option. |
|
180 * |
|
181 * @throws \InvalidArgumentException If the options does not exists. |
|
182 * |
|
183 * @api |
|
184 */ |
|
185 public function getOption($name) |
|
186 { |
|
187 if (!$this->hasOption($name)) { |
|
188 throw new \InvalidArgumentException(sprintf('The option "%s" does not exists.', $name)); |
|
189 } |
|
190 |
|
191 return $this->options[$name]; |
|
192 } |
|
193 |
|
194 /** |
|
195 * New class extensions process. |
|
196 * |
|
197 * @param string $class The class. |
|
198 * @param \ArrayObject $configClasses The config classes. |
|
199 * @param \ArrayObject $newClassExtensions The new class extensions. |
|
200 * |
|
201 * @api |
|
202 */ |
|
203 public function newClassExtensionsProcess($class, \ArrayObject $configClasses, \ArrayObject $newClassExtensions) |
|
204 { |
|
205 $this->class = $class; |
|
206 $this->configClasses = $configClasses; |
|
207 $this->configClass = $configClasses[$class]; |
|
208 $this->newClassExtensions = $newClassExtensions; |
|
209 |
|
210 $this->doNewClassExtensionsProcess(); |
|
211 |
|
212 $this->class = null; |
|
213 $this->configClasses = null; |
|
214 $this->configClass = null; |
|
215 $this->newClassExtensions = null; |
|
216 } |
|
217 |
|
218 /** |
|
219 * Do the new class extensions process. |
|
220 * |
|
221 * Here you can add new class extensions. |
|
222 * |
|
223 * @api |
|
224 */ |
|
225 protected function doNewClassExtensionsProcess() |
|
226 { |
|
227 } |
|
228 |
|
229 /** |
|
230 * New config classes process. |
|
231 * |
|
232 * @param string $class The class. |
|
233 * @param \ArrayObject $configClasses The config classes. |
|
234 * @param \ArrayObject $newConfigClasses The new config classes. |
|
235 * |
|
236 * @api |
|
237 */ |
|
238 public function newConfigClassesProcess($class, \ArrayObject $configClasses, \ArrayObject $newConfigClasses) |
|
239 { |
|
240 $this->class = $class; |
|
241 $this->configClasses = $configClasses; |
|
242 $this->configClass = $configClasses[$class]; |
|
243 $this->newConfigClasses = $newConfigClasses; |
|
244 |
|
245 $this->doNewConfigClassesProcess(); |
|
246 |
|
247 $this->class = null; |
|
248 $this->configClasses = null; |
|
249 $this->configClass = null; |
|
250 $this->newConfigClasses = null; |
|
251 } |
|
252 |
|
253 /** |
|
254 * Do the new config classes process. |
|
255 * |
|
256 * Here you can add new config classes, and change the config classes |
|
257 * if it is necessary to build the new config classes. |
|
258 * |
|
259 * @api |
|
260 */ |
|
261 protected function doNewConfigClassesProcess() |
|
262 { |
|
263 } |
|
264 |
|
265 /** |
|
266 * Process the config class. |
|
267 * |
|
268 * @param string $class The class. |
|
269 * @param \ArrayObject $configClasses The config classes. |
|
270 * |
|
271 * @api |
|
272 */ |
|
273 public function configClassProcess($class, \ArrayObject $configClasses) |
|
274 { |
|
275 $this->class = $class; |
|
276 $this->configClasses = $configClasses; |
|
277 $this->configClass = $configClasses[$class]; |
|
278 |
|
279 $this->doConfigClassProcess(); |
|
280 |
|
281 $this->class = null; |
|
282 $this->configClasses = null; |
|
283 $this->configClass = null; |
|
284 } |
|
285 |
|
286 /** |
|
287 * Do the config class process. |
|
288 * |
|
289 * Here you can modify the config class. |
|
290 * |
|
291 * @api |
|
292 */ |
|
293 protected function doConfigClassProcess() |
|
294 { |
|
295 } |
|
296 |
|
297 |
|
298 /** |
|
299 * Process the class. |
|
300 * |
|
301 * @param string $class The class. |
|
302 * @param \ArrayObject $configClasses The config classes. |
|
303 * @param Mandango\Mondator\Container $container The container. |
|
304 * |
|
305 * @api |
|
306 */ |
|
307 public function classProcess($class, \ArrayObject $configClasses, Container $container) |
|
308 { |
|
309 $this->class = $class; |
|
310 $this->configClasses = $configClasses; |
|
311 $this->configClass = $configClasses[$class]; |
|
312 $this->definitions = $container; |
|
313 |
|
314 $this->doClassProcess(); |
|
315 |
|
316 $this->class = null; |
|
317 $this->configClasses = null; |
|
318 $this->configClass = null; |
|
319 $this->definitions = null; |
|
320 } |
|
321 |
|
322 /** |
|
323 * Do the class process. |
|
324 * |
|
325 * @api |
|
326 */ |
|
327 protected function doClassProcess() |
|
328 { |
|
329 } |
|
330 |
|
331 /** |
|
332 * Twig. |
|
333 */ |
|
334 protected function processTemplate(Definition $definition, $name, array $variables = array()) |
|
335 { |
|
336 $twig = $this->getTwig(); |
|
337 |
|
338 $variables['options'] = $this->options; |
|
339 $variables['class'] = $this->class; |
|
340 $variables['config_class'] = $this->configClass; |
|
341 $variables['config_classes'] = $this->configClasses; |
|
342 |
|
343 $result = $twig->loadTemplate($name)->render($variables); |
|
344 |
|
345 // properties |
|
346 $expression = '/ |
|
347 (?P<docComment>\ \ \ \ \/\*\*\n[\s\S]*\ \ \ \ \ \*\/)?\n? |
|
348 \ \ \ \ (?P<static>static\ )? |
|
349 (?P<visibility>public|protected|private) |
|
350 \s |
|
351 \$ |
|
352 (?P<name>[a-zA-Z0-9_]+) |
|
353 ; |
|
354 /xU'; |
|
355 preg_match_all($expression, $result, $matches); |
|
356 |
|
357 for ($i = 0; $i <= count($matches[0]) - 1; $i++) { |
|
358 $property = new Property($matches['visibility'][$i], $matches['name'][$i], null); |
|
359 if ($matches['static'][$i]) { |
|
360 $property->setStatic(true); |
|
361 } |
|
362 if ($matches['docComment'][$i]) { |
|
363 $property->setDocComment($matches['docComment'][$i]); |
|
364 } |
|
365 $definition->addProperty($property); |
|
366 } |
|
367 |
|
368 // methods |
|
369 $expression = '/ |
|
370 (?P<docComment>\ \ \ \ \/\*\*\n[\s\S]*\ \ \ \ \ \*\/)?\n |
|
371 \ \ \ \ (?P<static>static\ )? |
|
372 (?P<visibility>public|protected|private) |
|
373 \s |
|
374 function |
|
375 \s |
|
376 (?P<name>[a-zA-Z0-9_]+) |
|
377 \((?P<arguments>[$a-zA-Z0-9_\\\=\(\), ]*)\) |
|
378 \n |
|
379 \ \ \ \ \{ |
|
380 (?P<code>[\s\S]*) |
|
381 \n\ \ \ \ \} |
|
382 /xU'; |
|
383 preg_match_all($expression, $result, $matches); |
|
384 |
|
385 for ($i = 0; $i <= count($matches[0]) - 1; $i++) { |
|
386 $code = trim($matches['code'][$i], "\n"); |
|
387 $method = new Method($matches['visibility'][$i], $matches['name'][$i], $matches['arguments'][$i], $code); |
|
388 if ($matches['static'][$i]) { |
|
389 $method->setStatic(true); |
|
390 } |
|
391 if ($matches['docComment'][$i]) { |
|
392 $method->setDocComment($matches['docComment'][$i]); |
|
393 } |
|
394 $definition->addMethod($method); |
|
395 } |
|
396 } |
|
397 |
|
398 public function getTwig() |
|
399 { |
|
400 if (null === $this->twig) { |
|
401 if (!class_exists('Twig_Environment')) { |
|
402 throw new \RuntimeException('Twig is required to use templates.'); |
|
403 } |
|
404 |
|
405 $loader = new \Twig_Loader_String(); |
|
406 $twig = new \Twig_Environment($loader, array( |
|
407 'autoescape' => false, |
|
408 'strict_variables' => true, |
|
409 'debug' => true, |
|
410 'cache' => $this->twigTempDir = sys_get_temp_dir().'Mondator/'.mt_rand(111111, 999999), |
|
411 )); |
|
412 |
|
413 $this->configureTwig($twig); |
|
414 |
|
415 $this->twig = $twig; |
|
416 } |
|
417 |
|
418 return $this->twig; |
|
419 } |
|
420 |
|
421 protected function configureTwig(\Twig_Environment $twig) |
|
422 { |
|
423 } |
|
424 |
|
425 /* |
|
426 * Tools. |
|
427 */ |
|
428 protected function createClassExtensionFromArray(array $data) |
|
429 { |
|
430 if (!isset($data['class'])) { |
|
431 throw new \InvalidArgumentException(sprintf('The extension does not have class.')); |
|
432 } |
|
433 |
|
434 return new $data['class'](isset($data['options']) ? $data['options'] : array()); |
|
435 } |
|
436 |
|
437 private function removeDir($target) |
|
438 { |
|
439 $fp = opendir($target); |
|
440 while (false !== $file = readdir($fp)) { |
|
441 if (in_array($file, array('.', '..'))) { |
|
442 continue; |
|
443 } |
|
444 |
|
445 if (is_dir($target.'/'.$file)) { |
|
446 self::removeDir($target.'/'.$file); |
|
447 } else { |
|
448 unlink($target.'/'.$file); |
|
449 } |
|
450 } |
|
451 closedir($fp); |
|
452 rmdir($target); |
|
453 } |
|
454 |
|
455 public function __destruct() |
|
456 { |
|
457 if ($this->twigTempDir && is_dir($this->twigTempDir)) { |
|
458 $this->removeDir($this->twigTempDir); |
|
459 } |
|
460 } |
|
461 } |