1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette;
13:
14: use Nette,
15: Nette\Caching\Cache,
16: Nette\DI;
17:
18:
19:
20: 21: 22: 23: 24:
25: class Configurator extends Object
26: {
27: public static $instance;
28:
29:
30: public $defaultConfigFile = '%appDir%/config.neon';
31:
32:
33: private $container;
34:
35:
36:
37: public function __construct($containerClass = 'Nette\DI\Container')
38: {
39: self::$instance = $this;
40: $this->container = new $containerClass;
41: $this->container->addService('container', $this->container);
42:
43: foreach (get_class_methods($this) as $name) {
44: if (substr($name, 0, 13) === 'createService' ) {
45: $this->container->addService(strtolower($name[13]) . substr($name, 14), array(get_called_class(), $name));
46: }
47: }
48:
49: defined('WWW_DIR') && $this->container->params['wwwDir'] = realpath(WWW_DIR);
50: defined('APP_DIR') && $this->container->params['appDir'] = realpath(APP_DIR);
51: defined('LIBS_DIR') && $this->container->params['libsDir'] = realpath(LIBS_DIR);
52: defined('TEMP_DIR') && $this->container->params['tempDir'] = realpath(TEMP_DIR);
53: $this->container->params['productionMode'] = self::detectProductionMode();
54: $this->container->params['consoleMode'] = PHP_SAPI === 'cli';
55: }
56:
57:
58:
59: 60: 61: 62:
63: public function getContainer()
64: {
65: return $this->container;
66: }
67:
68:
69:
70: 71: 72: 73:
74: public function loadConfig($file, $section = NULL)
75: {
76: if ($file === NULL) {
77: $file = $this->defaultConfigFile;
78: }
79: $container = $this->container;
80: $file = $container->expand($file);
81: if (!is_file($file)) {
82: $file = preg_replace('#\.neon$#', '.ini', $file);
83: }
84: if ($section === NULL) {
85: if (PHP_SAPI === 'cli') {
86: $section = Environment::CONSOLE;
87: } else {
88: $section = $container->params['productionMode'] ? Environment::PRODUCTION : Environment::DEVELOPMENT;
89: }
90: }
91:
92: $cache = new Cache($container->templateCacheStorage, 'Nette.Configurator');
93: $cacheKey = array((array) $container->params, $file, $section);
94: $cached = $cache->load($cacheKey);
95: if ($cached) {
96: require $cached['file'];
97: fclose($cached['handle']);
98: return $this->container;
99: }
100:
101: $config = Nette\Config\Config::fromFile($file, $section);
102: $code = "<?php\n// source file $file\n\n";
103:
104:
105: foreach (array('service', 'variable') as $item) {
106: if (isset($config[$item])) {
107: trigger_error(basename($file) . ": Section '$item' is deprecated; use plural form '{$item}s' instead.", E_USER_WARNING);
108: $config[$item . 's'] = $config[$item];
109: unset($config[$item]);
110: }
111: }
112:
113:
114: if (isset($config['services'])) {
115: foreach ($config['services'] as $key => & $def) {
116: if (preg_match('#^Nette\\\\.*\\\\I?([a-zA-Z]+)$#', strtr($key, '-', '\\'), $m)) {
117: $m[1][0] = strtolower($m[1][0]);
118: trigger_error(basename($file) . ": service name '$key' has been renamed to '$m[1]'", E_USER_WARNING);
119: $key = $m[1];
120: }
121:
122: if (is_array($def)) {
123: if (method_exists(get_called_class(), "createService$key") && !isset($def['factory']) && !isset($def['class'])) {
124: $def['factory'] = array(get_called_class(), "createService$key");
125: }
126:
127: if (isset($def['option'])) {
128: $def['arguments'][] = $def['option'];
129: }
130:
131: if (!empty($def['run'])) {
132: $def['tags'] = array('run');
133: }
134: }
135: }
136: $builder = new DI\ContainerBuilder;
137: $code .= $builder->generateCode($config['services']);
138: unset($config['services']);
139: }
140:
141:
142: if (!isset($config['variables'])) {
143: $config['variables'] = array();
144: }
145: foreach ($config as $key => $value) {
146: if (!in_array($key, array('variables', 'services', 'php', 'const', 'mode'))) {
147: $config['variables'][$key] = $value;
148: }
149: }
150:
151:
152: $variables = $config['variables'];
153: array_walk_recursive($config, function(&$val) use ($variables) {
154: $val = Configurator::preExpand($val, $variables);
155: });
156:
157:
158: foreach ($config['variables'] as $key => $value) {
159: $code .= $this->generateCode('$container->params[?] = ?', $key, $value);
160: }
161:
162:
163: if (isset($config['php'])) {
164: foreach ($config['php'] as $key => $value) {
165: if (is_array($value)) {
166: foreach ($value as $k => $v) {
167: $code .= $this->configurePhp("$key.$k", $v);
168: }
169: } else {
170: $code .= $this->configurePhp($key, $value);
171: }
172: }
173: }
174:
175:
176: if (isset($config['const'])) {
177: foreach ($config['const'] as $key => $value) {
178: $code .= $this->generateCode('define', $key, $value);
179: }
180: }
181:
182:
183: if (isset($config['mode'])) {
184: foreach ($config['mode'] as $mode => $state) {
185: trigger_error(basename($file) . ": Section 'mode' is deprecated; use '{$mode}Mode' in section 'variables' instead.", E_USER_WARNING);
186: $code .= $this->generateCode('$container->params[?] = ?', $mode . 'Mode', (bool) $state);
187: }
188: }
189:
190:
191: $code .= self::preloadEnvironment($container);
192:
193:
194: $code .= 'foreach ($container->getServiceNamesByTag("run") as $name => $foo) { $container->getService($name); }' . "\n";
195:
196: $cache->save($cacheKey, $code, array(
197: Cache::FILES => $file,
198: ));
199:
200: Nette\Utils\LimitedScope::evaluate($code, array('container' => $container));
201: return $this->container;
202: }
203:
204:
205:
206:
207:
208:
209:
210: 211: 212: 213:
214: public static function detectProductionMode()
215: {
216: $addrs = array();
217: if (PHP_SAPI === 'cli') {
218: $addrs[] = getHostByName(php_uname('n'));
219: }
220: else {
221: if (!isset($_SERVER['SERVER_ADDR']) && !isset($_SERVER['LOCAL_ADDR'])) {
222: return TRUE;
223: }
224: if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
225: $addrs = preg_split('#,\s*#', $_SERVER['HTTP_X_FORWARDED_FOR']);
226: }
227: if (isset($_SERVER['REMOTE_ADDR'])) {
228: $addrs[] = $_SERVER['REMOTE_ADDR'];
229: }
230: $addrs[] = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : $_SERVER['LOCAL_ADDR'];
231: }
232: foreach ($addrs as $addr) {
233: $oct = explode('.', $addr);
234:
235:
236:
237:
238:
239: if ($addr !== '::1' && (count($oct) !== 4 || ($oct[0] !== '10' && $oct[0] !== '127' && ($oct[0] !== '172' || $oct[1] < 16 || $oct[1] > 31)
240: && ($oct[0] !== '169' || $oct[1] !== '254') && ($oct[0] !== '192' || $oct[1] !== '168')))
241: ) {
242: return TRUE;
243: }
244: }
245: return FALSE;
246: }
247:
248:
249:
250: public function configurePhp($name, $value)
251: {
252: if (!is_scalar($value)) {
253: throw new Nette\InvalidStateException("Configuration value for directive '$name' is not scalar.");
254: }
255:
256: switch ($name) {
257: case 'include_path':
258: return $this->generateCode('set_include_path', str_replace(';', PATH_SEPARATOR, $value));
259: case 'ignore_user_abort':
260: return $this->generateCode('ignore_user_abort', $value);
261: case 'max_execution_time':
262: return $this->generateCode('set_time_limit', $value);
263: case 'date.timezone':
264: return $this->generateCode('date_default_timezone_set', $value);
265: }
266:
267: if (function_exists('ini_set')) {
268: return $this->generateCode('ini_set', $name, $value);
269: } elseif (ini_get($name) != $value && !Framework::$iAmUsingBadHost) {
270: throw new Nette\NotSupportedException('Required function ini_set() is disabled.');
271: }
272: }
273:
274:
275:
276: private static function generateCode($statement)
277: {
278: $args = func_get_args();
279: unset($args[0]);
280: foreach ($args as &$arg) {
281: $arg = var_export($arg, TRUE);
282: $arg = preg_replace("#(?<!\\\)'%([\w-]+)%'#", '\$container->params[\'$1\']', $arg);
283: $arg = preg_replace("#(?<!\\\)'(?:[^'\\\]|\\\.)*%(?:[^'\\\]|\\\.)*'#", '\$container->expand($0)', $arg);
284: }
285: if (strpos($statement, '?') === FALSE) {
286: return $statement .= '(' . implode(', ', $args) . ");\n\n";
287: }
288: $a = strpos($statement, '?');
289: $i = 1;
290: while ($a !== FALSE) {
291: $statement = substr_replace($statement, $args[$i], $a, 1);
292: $a = strpos($statement, '?', $a + strlen($args[$i]));
293: $i++;
294: }
295: return $statement . ";\n\n";
296: }
297:
298:
299:
300: 301: 302: 303:
304: public static function preExpand($s, array $params, $check = array())
305: {
306: if (!is_string($s)) {
307: return $s;
308: }
309:
310: $parts = preg_split('#%([\w.-]*)%#i', $s, -1, PREG_SPLIT_DELIM_CAPTURE);
311: $res = '';
312: foreach ($parts as $n => $part) {
313: if ($n % 2 === 0) {
314: $res .= str_replace('%', '%%', $part);
315:
316: } elseif ($part === '') {
317: $res .= '%%';
318:
319: } elseif (isset($check[$part])) {
320: throw new Nette\InvalidArgumentException('Circular reference detected for variables: ' . implode(', ', array_keys($check)) . '.');
321:
322: } else {
323: try {
324: $val = Nette\Utils\Arrays::get($params, explode('.', $part));
325: } catch (Nette\InvalidArgumentException $e) {
326: $res .= "%$part%";
327: continue;
328: }
329: $val = self::preExpand($val, $params, $check + array($part => 1));
330: if (strlen($part) + 2 === strlen($s)) {
331: if (is_array($val)) {
332: array_walk_recursive($val, function(&$val) use ($params, $check, $part) {
333: $val = Configurator::preExpand($val, $params, $check + array($part => 1));
334: });
335: }
336: return $val;
337: }
338: if (!is_scalar($val)) {
339: throw new Nette\InvalidArgumentException("Unable to concatenate non-scalar parameter '$part' into '$s'.");
340: }
341: $res .= $val;
342: }
343: }
344: return $res;
345: }
346:
347:
348:
349:
350:
351:
352:
353: 354: 355:
356: public static function createServiceApplication(DI\Container $container, array $options = NULL)
357: {
358: $context = new DI\Container;
359: $context->addService('httpRequest', $container->httpRequest);
360: $context->addService('httpResponse', $container->httpResponse);
361: $context->addService('session', $container->session);
362: $context->addService('presenterFactory', $container->presenterFactory);
363: $context->addService('router', $container->router);
364:
365: Nette\Application\UI\Presenter::$invalidLinkMode = $container->params['productionMode']
366: ? Nette\Application\UI\Presenter::INVALID_LINK_SILENT
367: : Nette\Application\UI\Presenter::INVALID_LINK_WARNING;
368:
369: $class = isset($options['class']) ? $options['class'] : 'Nette\Application\Application';
370: $application = new $class($context);
371: $application->catchExceptions = $container->params['productionMode'];
372: if ($container->session->exists()) {
373: $application->onStartup[] = function() use ($container) {
374: $container->session->start();
375: };
376: }
377: return $application;
378: }
379:
380:
381:
382: 383: 384:
385: public static function createServicePresenterFactory(DI\Container $container)
386: {
387: return new Nette\Application\PresenterFactory(
388: isset($container->params['appDir']) ? $container->params['appDir'] : NULL,
389: $container
390: );
391: }
392:
393:
394:
395: 396: 397:
398: public static function createServiceRouter(DI\Container $container)
399: {
400: return new Nette\Application\Routers\RouteList;
401: }
402:
403:
404:
405: 406: 407:
408: public static function createServiceHttpRequest()
409: {
410: $factory = new Nette\Http\RequestFactory;
411: $factory->setEncoding('UTF-8');
412: return $factory->createHttpRequest();
413: }
414:
415:
416:
417: 418: 419:
420: public static function createServiceHttpResponse()
421: {
422: return new Nette\Http\Response;
423: }
424:
425:
426:
427: 428: 429:
430: public static function createServiceHttpContext(DI\Container $container)
431: {
432: return new Nette\Http\Context($container->httpRequest, $container->httpResponse);
433: }
434:
435:
436:
437: 438: 439:
440: public static function createServiceSession(DI\Container $container, array $options = NULL)
441: {
442: $session = new Nette\Http\Session($container->httpRequest, $container->httpResponse);
443: $session->setOptions((array) $options);
444: if (isset($options['expiration'])) {
445: $session->setExpiration($options['expiration']);
446: }
447: return $session;
448: }
449:
450:
451:
452: 453: 454:
455: public static function createServiceUser(DI\Container $container)
456: {
457: $context = new DI\Container;
458:
459: $context->addService('authenticator', function() use ($container) {
460: return $container->authenticator;
461: });
462: $context->addService('authorizator', function() use ($container) {
463: return $container->authorizator;
464: });
465: $context->addService('session', $container->session);
466: return new Nette\Http\User($context);
467: }
468:
469:
470:
471: 472: 473:
474: public static function createServiceCacheStorage(DI\Container $container)
475: {
476: if (!isset($container->params['tempDir'])) {
477: throw new Nette\InvalidStateException("Service cacheStorage requires that parameter 'tempDir' contains path to temporary directory.");
478: }
479: $dir = $container->expand('%tempDir%/cache');
480: umask(0000);
481: @mkdir($dir, 0777);
482: return new Nette\Caching\Storages\FileStorage($dir, $container->cacheJournal);
483: }
484:
485:
486:
487: 488: 489:
490: public static function createServiceTemplateCacheStorage(DI\Container $container)
491: {
492: if (!isset($container->params['tempDir'])) {
493: throw new Nette\InvalidStateException("Service templateCacheStorage requires that parameter 'tempDir' contains path to temporary directory.");
494: }
495: $dir = $container->expand('%tempDir%/cache');
496: umask(0000);
497: @mkdir($dir, 0777);
498: return new Nette\Caching\Storages\PhpFileStorage($dir);
499: }
500:
501:
502:
503: 504: 505:
506: public static function createServiceCacheJournal(DI\Container $container)
507: {
508: return new Nette\Caching\Storages\FileJournal($container->params['tempDir']);
509: }
510:
511:
512:
513: 514: 515:
516: public static function createServiceMailer(DI\Container $container, array $options = NULL)
517: {
518: if (empty($options['smtp'])) {
519: return new Nette\Mail\SendmailMailer;
520: } else {
521: return new Nette\Mail\SmtpMailer($options);
522: }
523: }
524:
525:
526:
527: 528: 529:
530: public static function createServiceRobotLoader(DI\Container $container, array $options = NULL)
531: {
532: $loader = new Nette\Loaders\RobotLoader;
533: $loader->autoRebuild = isset($options['autoRebuild']) ? $options['autoRebuild'] : !$container->params['productionMode'];
534: $loader->setCacheStorage($container->cacheStorage);
535: if (isset($options['directory'])) {
536: $loader->addDirectory($options['directory']);
537: } else {
538: foreach (array('appDir', 'libsDir') as $var) {
539: if (isset($container->params[$var])) {
540: $loader->addDirectory($container->params[$var]);
541: }
542: }
543: }
544: $loader->register();
545: return $loader;
546: }
547:
548:
549:
550: public static function preloadEnvironment(DI\Container $container)
551: {
552: $code = '';
553: $dir = $container->expand('%tempDir%/cache');
554: umask(0000);
555: @mkdir($dir, 0777);
556:
557:
558: $uniq = uniqid('_', TRUE);
559: umask(0000);
560: if (!@mkdir("$dir/$uniq", 0777)) {
561: throw new Nette\InvalidStateException("Unable to write to directory '$dir'. Make this directory writable.");
562: }
563:
564:
565: $useDirs = @file_put_contents("$dir/$uniq/_", '') !== FALSE;
566: @unlink("$dir/$uniq/_");
567: @rmdir("$dir/$uniq");
568:
569: $code .= self::generateCode('Nette\Caching\Storages\FileStorage::$useDirectories = ?', $useDirs);
570: return $code;
571: }
572:
573: }
574: