sfConfigCache.class.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
  5. * (c) 2004-2006 Sean Kerr <sean@code-box.org>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * sfConfigCache allows you to customize the format of a configuration file to
  12. * make it easy-to-use, yet still provide a PHP formatted result for direct
  13. * inclusion into your modules.
  14. *
  15. * @package symfony
  16. * @subpackage config
  17. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  18. * @author Sean Kerr <sean@code-box.org>
  19. * @version SVN: $Id: sfConfigCache.class.php 17858 2009-05-01 21:22:50Z FabianLange $
  20. */
  21. class sfConfigCache
  22. {
  23. protected
  24. $configuration = null,
  25. $handlers = array(),
  26. $userHandlers = array();
  27. /**
  28. * Constructor
  29. *
  30. * @param sfApplicationConfiguration $configuration A sfApplicationConfiguration instance
  31. */
  32. public function __construct(sfApplicationConfiguration $configuration)
  33. {
  34. $this->configuration = $configuration;
  35. }
  36. /**
  37. * Loads a configuration handler.
  38. *
  39. * @param string $handler The handler to use when parsing a configuration file
  40. * @param array $configs An array of absolute filesystem paths to configuration files
  41. * @param string $cache An absolute filesystem path to the cache file that will be written
  42. *
  43. * @throws <b>sfConfigurationException</b> If a requested configuration file does not have an associated configuration handler
  44. */
  45. protected function callHandler($handler, $configs, $cache)
  46. {
  47. if (count($this->handlers) == 0)
  48. {
  49. // we need to load the handlers first
  50. $this->loadConfigHandlers();
  51. }
  52. if (count($this->userHandlers) != 0)
  53. {
  54. // we load user defined handlers
  55. $this->mergeUserConfigHandlers();
  56. }
  57. // handler key to call for this configuration file
  58. $handlerKey = null;
  59. $handler = str_replace(DIRECTORY_SEPARATOR, '/', $handler);
  60. // grab the base name of the handler
  61. $basename = basename($handler);
  62. if (isset($this->handlers[$handler]))
  63. {
  64. // we have a handler associated with the full configuration path
  65. $handlerKey = $handler;
  66. }
  67. else if (isset($this->handlers[$basename]))
  68. {
  69. // we have a handler associated with the configuration base name
  70. $handlerKey = $basename;
  71. }
  72. else
  73. {
  74. // let's see if we have any wildcard handlers registered that match this basename
  75. foreach (array_keys($this->handlers) as $key)
  76. {
  77. // replace wildcard chars in the configuration
  78. $pattern = strtr($key, array('.' => '\.', '*' => '.*?'));
  79. // create pattern from config
  80. if (preg_match('#'.$pattern.'$#', $handler))
  81. {
  82. $handlerKey = $key;
  83. break;
  84. }
  85. }
  86. }
  87. if (!$handlerKey)
  88. {
  89. // we do not have a registered handler for this file
  90. throw new sfConfigurationException(sprintf('Configuration file "%s" does not have a registered handler.', implode(', ', $configs)));
  91. }
  92. // call the handler and retrieve the cache data
  93. $data = $this->getHandler($handlerKey)->execute($configs);
  94. $this->writeCacheFile($handler, $cache, $data);
  95. }
  96. /**
  97. * Returns the config handler configured for the given name
  98. *
  99. * @param string $name The config handler name
  100. *
  101. * @return sfConfigHandler A sfConfigHandler instance
  102. */
  103. protected function getHandler($name)
  104. {
  105. if (is_array($this->handlers[$name]))
  106. {
  107. $class = $this->handlers[$name][0];
  108. $this->handlers[$name] = new $class($this->handlers[$name][1]);
  109. }
  110. return $this->handlers[$name];
  111. }
  112. /**
  113. * Checks to see if a configuration file has been modified and if so
  114. * recompile the cache file associated with it.
  115. *
  116. * The recompilation only occurs in a non debug environment.
  117. *
  118. * If the configuration file path is relative, symfony will look in directories
  119. * defined in the sfConfiguration::getConfigPaths() method.
  120. *
  121. * @param string $configPath A filesystem path to a configuration file
  122. * @param boolean $optional If true, config path does not need to exist
  123. *
  124. * @return string An absolute filesystem path to the cache filename associated with this specified configuration file
  125. *
  126. * @throws <b>sfConfigurationException</b> If a requested configuration file does not exist
  127. *
  128. * @see sfConfiguration::getConfigPaths()
  129. */
  130. public function checkConfig($configPath, $optional = false)
  131. {
  132. if (sfConfig::get('sf_debug') && sfConfig::get('sf_logging_enabled'))
  133. {
  134. $timer = sfTimerManager::getTimer('Configuration');
  135. }
  136. // the cache filename we'll be using
  137. $cache = $this->getCacheName($configPath);
  138. if (!sfConfig::get('sf_debug') && !sfConfig::get('sf_test') && is_readable($cache))
  139. {
  140. return $cache;
  141. }
  142. if (!sfToolkit::isPathAbsolute($configPath))
  143. {
  144. $files = $this->configuration->getConfigPaths($configPath);
  145. }
  146. else
  147. {
  148. $files = is_readable($configPath) ? array($configPath) : array();
  149. }
  150. if (!isset($files[0]))
  151. {
  152. if ($optional)
  153. {
  154. return null;
  155. }
  156. // configuration does not exist
  157. throw new sfConfigurationException(sprintf('Configuration "%s" does not exist or is unreadable.', $configPath));
  158. }
  159. // find the more recent configuration file last modification time
  160. $mtime = 0;
  161. foreach ($files as $file)
  162. {
  163. if (filemtime($file) > $mtime)
  164. {
  165. $mtime = filemtime($file);
  166. }
  167. }
  168. if (!is_readable($cache) || $mtime > filemtime($cache))
  169. {
  170. // configuration has changed so we need to reparse it
  171. $this->callHandler($configPath, $files, $cache);
  172. }
  173. if (sfConfig::get('sf_debug') && sfConfig::get('sf_logging_enabled'))
  174. {
  175. $timer->addTime();
  176. }
  177. return $cache;
  178. }
  179. /**
  180. * Clears all configuration cache files.
  181. */
  182. public function clear()
  183. {
  184. sfToolkit::clearDirectory(sfConfig::get('sf_config_cache_dir'));
  185. }
  186. /**
  187. * Converts a normal filename into a cache filename.
  188. *
  189. * @param string $config A normal filename
  190. *
  191. * @return string An absolute filesystem path to a cache filename
  192. */
  193. public function getCacheName($config)
  194. {
  195. if (strlen($config) > 3 && ctype_alpha($config[0]) && $config[1] == ':' && ($config[2] == '\\' || $config[2] == '/'))
  196. {
  197. // file is a windows absolute path, strip off the drive letter
  198. $config = substr($config, 3);
  199. }
  200. // replace unfriendly filename characters with an underscore
  201. $config = str_replace(array('\\', '/', ' '), '_', $config);
  202. $config .= '.php';
  203. return sfConfig::get('sf_config_cache_dir').'/'.$config;
  204. }
  205. /**
  206. * Imports a configuration file.
  207. *
  208. * @param string $config A filesystem path to a configuration file
  209. * @param bool $once Only allow this configuration file to be included once per request?
  210. * @param bool $optional Only include if true
  211. *
  212. * @see checkConfig()
  213. */
  214. public function import($config, $once = true, $optional = false)
  215. {
  216. $cache = $this->checkConfig($config, $optional);
  217. if ($optional && !$cache)
  218. {
  219. return;
  220. }
  221. // include cache file
  222. if ($once)
  223. {
  224. include_once($cache);
  225. }
  226. else
  227. {
  228. include($cache);
  229. }
  230. }
  231. /**
  232. * Loads all configuration application and module level handlers.
  233. *
  234. * @throws <b>sfConfigurationException</b> If a configuration related error occurs.
  235. */
  236. protected function loadConfigHandlers()
  237. {
  238. // manually create our config_handlers.yml handler
  239. $this->handlers['config_handlers.yml'] = new sfRootConfigHandler();
  240. // application configuration handlers
  241. require $this->checkConfig('config/config_handlers.yml');
  242. // module level configuration handlers
  243. // checks modules directory exists
  244. if (!is_readable($sf_app_modules_dir = sfConfig::get('sf_app_modules_dir')))
  245. {
  246. return;
  247. }
  248. // ignore names
  249. $ignore = array('.', '..', 'CVS', '.svn');
  250. // create a file pointer to the module dir
  251. $fp = opendir($sf_app_modules_dir);
  252. // loop through the directory and grab the modules
  253. while (($directory = readdir($fp)) !== false)
  254. {
  255. if (in_array($directory, $ignore))
  256. {
  257. continue;
  258. }
  259. $configPath = $sf_app_modules_dir.'/'.$directory.'/config/config_handlers.yml';
  260. if (is_readable($configPath))
  261. {
  262. // initialize the root configuration handler with this module name
  263. $params = array('module_level' => true, 'module_name' => $directory);
  264. $this->handlers['config_handlers.yml']->initialize($params);
  265. // replace module dir path with a special keyword that
  266. // checkConfig knows how to use
  267. $configPath = 'modules/'.$directory.'/config/config_handlers.yml';
  268. require $this->checkConfig($configPath);
  269. }
  270. }
  271. // close file pointer
  272. closedir($fp);
  273. }
  274. /**
  275. * Writes a cache file.
  276. *
  277. * @param string $config An absolute filesystem path to a configuration file
  278. * @param string $cache An absolute filesystem path to the cache file that will be written
  279. * @param string $data Data to be written to the cache file
  280. *
  281. * @throws sfCacheException If the cache file cannot be written
  282. */
  283. protected function writeCacheFile($config, $cache, $data)
  284. {
  285. $current_umask = umask(0000);
  286. if (!is_dir(dirname($cache)))
  287. {
  288. if (false === @mkdir(dirname($cache), 0777, true))
  289. {
  290. throw new sfCacheException(sprintf('Failed to make cache directory "%s" while generating cache for configuration file "%s".', dirname($cache), $config));
  291. }
  292. }
  293. $tmpFile = tempnam(dirname($cache), basename($cache));
  294. if (!$fp = @fopen($tmpFile, 'wb'))
  295. {
  296. throw new sfCacheException(sprintf('Failed to write cache file "%s" generated from configuration file "%s".', $tmpFile, $config));
  297. }
  298. @fwrite($fp, $data);
  299. @fclose($fp);
  300. // Hack from Agavi (http://trac.agavi.org/changeset/3979)
  301. // With php < 5.2.6 on win32, renaming to an already existing file doesn't work, but copy does,
  302. // so we simply assume that when rename() fails that we are on win32 and try to use copy()
  303. if (!@rename($tmpFile, $cache))
  304. {
  305. if (copy($tmpFile, $cache))
  306. {
  307. unlink($tmpFile);
  308. }
  309. }
  310. chmod($cache, 0666);
  311. umask($current_umask);
  312. }
  313. /**
  314. * Registers a configuration handler.
  315. *
  316. * @param string $handler The handler to use when parsing a configuration file
  317. * @param class $class A configuration handler class
  318. * @param string $params An array of options for the handler class initialization
  319. */
  320. public function registerConfigHandler($handler, $class, $params = array())
  321. {
  322. class_exists($class);
  323. $this->userHandlers[$handler] = new $class($params);
  324. }
  325. /**
  326. * Merges configuration handlers from the config_handlers.yml
  327. * and the ones defined with registerConfigHandler()
  328. *
  329. */
  330. protected function mergeUserConfigHandlers()
  331. {
  332. // user defined configuration handlers
  333. $this->handlers = array_merge($this->handlers, $this->userHandlers);
  334. $this->userHandlers = array();
  335. }
  336. }