Kernel.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <?php
  2. namespace Symfony\Framework;
  3. use Symfony\Components\DependencyInjection\ContainerInterface;
  4. use Symfony\Components\DependencyInjection\ContainerBuilder;
  5. use Symfony\Components\DependencyInjection\Dumper\PhpDumper;
  6. use Symfony\Components\DependencyInjection\Resource\FileResource;
  7. use Symfony\Components\DependencyInjection\ParameterBag\ParameterBag;
  8. use Symfony\Components\DependencyInjection\Loader\DelegatingLoader;
  9. use Symfony\Components\DependencyInjection\Loader\LoaderResolver;
  10. use Symfony\Components\DependencyInjection\Loader\LoaderInterface;
  11. use Symfony\Components\DependencyInjection\Loader\XmlFileLoader;
  12. use Symfony\Components\DependencyInjection\Loader\YamlFileLoader;
  13. use Symfony\Components\DependencyInjection\Loader\IniFileLoader;
  14. use Symfony\Components\DependencyInjection\Loader\PhpFileLoader;
  15. use Symfony\Components\DependencyInjection\Loader\ClosureLoader;
  16. use Symfony\Components\HttpFoundation\Request;
  17. use Symfony\Components\HttpKernel\HttpKernelInterface;
  18. use Symfony\Framework\ClassCollectionLoader;
  19. /*
  20. * This file is part of the Symfony package.
  21. *
  22. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  23. *
  24. * For the full copyright and license information, please view the LICENSE
  25. * file that was distributed with this source code.
  26. */
  27. /**
  28. * The Kernel is the heart of the Symfony system. It manages an environment
  29. * that can host bundles.
  30. *
  31. * @author Fabien Potencier <fabien.potencier@symfony-project.org>
  32. */
  33. abstract class Kernel implements HttpKernelInterface, \Serializable
  34. {
  35. static protected $loaded;
  36. protected $bundles;
  37. protected $bundleDirs;
  38. protected $container;
  39. protected $rootDir;
  40. protected $environment;
  41. protected $debug;
  42. protected $booted;
  43. protected $name;
  44. protected $startTime;
  45. protected $request;
  46. const VERSION = '2.0.0-DEV';
  47. /**
  48. * Constructor.
  49. *
  50. * @param string $environment The environment
  51. * @param Boolean $debug Whether to enable debugging or not
  52. */
  53. public function __construct($environment, $debug)
  54. {
  55. $this->environment = $environment;
  56. $this->debug = (Boolean) $debug;
  57. $this->booted = false;
  58. $this->rootDir = realpath($this->registerRootDir());
  59. $this->name = basename($this->rootDir);
  60. if ($this->debug) {
  61. ini_set('display_errors', 1);
  62. error_reporting(-1);
  63. $this->startTime = microtime(true);
  64. } else {
  65. ini_set('display_errors', 0);
  66. }
  67. }
  68. public function __clone()
  69. {
  70. if ($this->debug) {
  71. $this->startTime = microtime(true);
  72. }
  73. $this->booted = false;
  74. $this->container = null;
  75. $this->request = null;
  76. }
  77. abstract public function registerRootDir();
  78. abstract public function registerBundles();
  79. abstract public function registerBundleDirs();
  80. abstract public function registerContainerConfiguration(LoaderInterface $loader);
  81. /**
  82. * Checks whether the current kernel has been booted or not.
  83. *
  84. * @return boolean $booted
  85. */
  86. public function isBooted()
  87. {
  88. return $this->booted;
  89. }
  90. /**
  91. * Boots the current kernel.
  92. *
  93. * This method boots the bundles, which MUST set
  94. * the DI container.
  95. *
  96. * @throws \LogicException When the Kernel is already booted
  97. */
  98. public function boot()
  99. {
  100. if (true === $this->booted) {
  101. throw new \LogicException('The kernel is already booted.');
  102. }
  103. if (!$this->isDebug()) {
  104. require_once __DIR__.'/bootstrap.php';
  105. }
  106. $this->bundles = $this->registerBundles();
  107. $this->bundleDirs = $this->registerBundleDirs();
  108. $this->container = $this->initializeContainer();
  109. // load core classes
  110. // can only be loaded once (for all Kernel in the same process)
  111. if (!self::$loaded) {
  112. self::$loaded = true;
  113. ClassCollectionLoader::load(
  114. $this->container->getParameter('kernel.compiled_classes'),
  115. $this->container->getParameter('kernel.cache_dir'),
  116. 'classes',
  117. $this->container->getParameter('kernel.debug')
  118. );
  119. }
  120. foreach ($this->bundles as $bundle) {
  121. $bundle->setContainer($this->container);
  122. $bundle->boot();
  123. }
  124. $this->booted = true;
  125. }
  126. /**
  127. * Shutdowns the kernel.
  128. *
  129. * This method is mainly useful when doing functional testing.
  130. */
  131. public function shutdown()
  132. {
  133. $this->booted = false;
  134. foreach ($this->bundles as $bundle) {
  135. $bundle->shutdown();
  136. $bundle->setContainer(null);
  137. }
  138. $this->container = null;
  139. }
  140. /**
  141. * Reboots the kernel.
  142. *
  143. * This method is mainly useful when doing functional testing.
  144. *
  145. * It is a shortcut for the call to shutdown() and boot().
  146. */
  147. public function reboot()
  148. {
  149. $this->shutdown();
  150. $this->boot();
  151. }
  152. /**
  153. * Gets the Request instance associated with the master request.
  154. *
  155. * @return Request A Request instance
  156. */
  157. public function getRequest()
  158. {
  159. return $this->request;
  160. }
  161. /**
  162. * Handles a request to convert it to a response by calling the HttpKernel service.
  163. *
  164. * @param Request $request A Request instance
  165. * @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST, HttpKernelInterface::FORWARDED_REQUEST, or HttpKernelInterface::EMBEDDED_REQUEST)
  166. * @param Boolean $raw Whether to catch exceptions or not
  167. *
  168. * @return Response $response A Response instance
  169. */
  170. public function handle(Request $request = null, $type = HttpKernelInterface::MASTER_REQUEST, $raw = false)
  171. {
  172. if (false === $this->booted) {
  173. $this->boot();
  174. }
  175. if (null === $request) {
  176. $request = $this->container->get('request');
  177. } else {
  178. $this->container->set('request', $request);
  179. }
  180. if (HttpKernelInterface::MASTER_REQUEST === $type) {
  181. $this->request = $request;
  182. }
  183. $response = $this->container->getHttpKernelService()->handle($request, $type, $raw);
  184. $this->container->set('request', $this->request);
  185. return $response;
  186. }
  187. /**
  188. * Gets the directories where bundles can be stored.
  189. *
  190. * @return array An array of directories where bundles can be stored
  191. */
  192. public function getBundleDirs()
  193. {
  194. return $this->bundleDirs;
  195. }
  196. /**
  197. * Gets the registered bundle names.
  198. *
  199. * @return array An array of registered bundle names
  200. */
  201. public function getBundles()
  202. {
  203. return $this->bundles;
  204. }
  205. /**
  206. * Checks if a given class name belongs to an active bundle.
  207. *
  208. * @param string $class A class name
  209. *
  210. * @return Boolean true if the class belongs to an active bundle, false otherwise
  211. */
  212. public function isClassInActiveBundle($class)
  213. {
  214. foreach ($this->bundles as $bundle) {
  215. $bundleClass = get_class($bundle);
  216. if (0 === strpos($class, substr($bundleClass, 0, strrpos($bundleClass, '\\')))) {
  217. return true;
  218. }
  219. }
  220. return false;
  221. }
  222. public function getName()
  223. {
  224. return $this->name;
  225. }
  226. public function getSafeName()
  227. {
  228. return preg_replace('/[^a-zA-Z0-9_]+/', '', $this->name);
  229. }
  230. public function getEnvironment()
  231. {
  232. return $this->environment;
  233. }
  234. public function isDebug()
  235. {
  236. return $this->debug;
  237. }
  238. public function getRootDir()
  239. {
  240. return $this->rootDir;
  241. }
  242. public function getContainer()
  243. {
  244. return $this->container;
  245. }
  246. public function getStartTime()
  247. {
  248. return $this->debug ? $this->startTime : -INF;
  249. }
  250. public function getCacheDir()
  251. {
  252. return $this->rootDir.'/cache/'.$this->environment;
  253. }
  254. public function getLogDir()
  255. {
  256. return $this->rootDir.'/logs';
  257. }
  258. protected function initializeContainer()
  259. {
  260. $class = $this->getSafeName().ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer';
  261. $location = $this->getCacheDir().'/'.$class;
  262. $reload = $this->debug ? $this->needsReload($class, $location) : false;
  263. if ($reload || !file_exists($location.'.php')) {
  264. $this->buildContainer($class, $location.'.php');
  265. }
  266. require_once $location.'.php';
  267. $container = new $class();
  268. $container->set('kernel', $this);
  269. return $container;
  270. }
  271. public function getKernelParameters()
  272. {
  273. $bundles = array();
  274. foreach ($this->bundles as $bundle) {
  275. $bundles[] = get_class($bundle);
  276. }
  277. return array_merge(
  278. array(
  279. 'kernel.root_dir' => $this->rootDir,
  280. 'kernel.environment' => $this->environment,
  281. 'kernel.debug' => $this->debug,
  282. 'kernel.name' => $this->name,
  283. 'kernel.cache_dir' => $this->getCacheDir(),
  284. 'kernel.logs_dir' => $this->getLogDir(),
  285. 'kernel.bundle_dirs' => $this->bundleDirs,
  286. 'kernel.bundles' => $bundles,
  287. 'kernel.charset' => 'UTF-8',
  288. 'kernel.compiled_classes' => array(),
  289. ),
  290. $this->getEnvParameters()
  291. );
  292. }
  293. protected function getEnvParameters()
  294. {
  295. $parameters = array();
  296. foreach ($_SERVER as $key => $value) {
  297. if ('SYMFONY__' === substr($key, 0, 9)) {
  298. $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value;
  299. }
  300. }
  301. return $parameters;
  302. }
  303. protected function needsReload($class, $location)
  304. {
  305. if (!file_exists($location.'.meta') || !file_exists($location.'.php')) {
  306. return true;
  307. }
  308. $meta = unserialize(file_get_contents($location.'.meta'));
  309. $time = filemtime($location.'.php');
  310. foreach ($meta as $resource) {
  311. if (!$resource->isUptodate($time)) {
  312. return true;
  313. }
  314. }
  315. return false;
  316. }
  317. protected function buildContainer($class, $file)
  318. {
  319. $parameterBag = new ParameterBag($this->getKernelParameters());
  320. $container = new ContainerBuilder($parameterBag);
  321. foreach ($this->bundles as $bundle) {
  322. $bundle->registerExtensions($container);
  323. if ($this->debug) {
  324. $container->addObjectResource($bundle);
  325. }
  326. }
  327. if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) {
  328. $container->merge($cont);
  329. }
  330. $container->freeze();
  331. foreach (array('cache', 'logs') as $name) {
  332. $dir = $container->getParameter(sprintf('kernel.%s_dir', $name));
  333. if (!is_dir($dir)) {
  334. if (false === @mkdir($dir, 0777, true)) {
  335. die(sprintf('Unable to create the %s directory (%s)', $name, dirname($dir)));
  336. }
  337. } elseif (!is_writable($dir)) {
  338. die(sprintf('Unable to write in the %s directory (%s)', $name, $dir));
  339. }
  340. }
  341. // cache the container
  342. $dumper = new PhpDumper($container);
  343. $content = $dumper->dump(array('class' => $class));
  344. if (!$this->debug) {
  345. $content = self::stripComments($content);
  346. }
  347. $this->writeCacheFile($file, $content);
  348. if ($this->debug) {
  349. $container->addObjectResource($this);
  350. // save the resources
  351. $this->writeCacheFile($this->getCacheDir().'/'.$class.'.meta', serialize($container->getResources()));
  352. }
  353. }
  354. protected function getContainerLoader(ContainerInterface $container)
  355. {
  356. $resolver = new LoaderResolver(array(
  357. new XmlFileLoader($container, $this->getBundleDirs()),
  358. new YamlFileLoader($container, $this->getBundleDirs()),
  359. new IniFileLoader($container, $this->getBundleDirs()),
  360. new PhpFileLoader($container, $this->getBundleDirs()),
  361. new ClosureLoader($container),
  362. ));
  363. return new DelegatingLoader($resolver);
  364. }
  365. /**
  366. * Removes comments from a PHP source string.
  367. *
  368. * We don't use the PHP php_strip_whitespace() function
  369. * as we want the content to be readable and well-formatted.
  370. *
  371. * @param string $source A PHP string
  372. *
  373. * @return string The PHP string with the comments removed
  374. */
  375. static public function stripComments($source)
  376. {
  377. if (!function_exists('token_get_all')) {
  378. return $source;
  379. }
  380. $output = '';
  381. foreach (token_get_all($source) as $token) {
  382. if (is_string($token)) {
  383. $output .= $token;
  384. } elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
  385. $output .= $token[1];
  386. }
  387. }
  388. // replace multiple new lines with a single newline
  389. $output = preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $output);
  390. // reformat {} "a la python"
  391. $output = preg_replace(array('/\n\s*\{/', '/\n\s*\}/'), array(' {', ' }'), $output);
  392. return $output;
  393. }
  394. protected function writeCacheFile($file, $content)
  395. {
  396. $tmpFile = tempnam(dirname($file), basename($file));
  397. if (!$fp = @fopen($tmpFile, 'wb')) {
  398. die(sprintf('Failed to write cache file "%s".', $tmpFile));
  399. }
  400. @fwrite($fp, $content);
  401. @fclose($fp);
  402. if ($content != file_get_contents($tmpFile)) {
  403. die(sprintf('Failed to write cache file "%s" (cache corrupted).', $tmpFile));
  404. }
  405. if (!@rename($tmpFile, $file)) {
  406. throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file));
  407. }
  408. chmod($file, 0644);
  409. }
  410. public function serialize()
  411. {
  412. return serialize(array($this->environment, $this->debug));
  413. }
  414. public function unserialize($data)
  415. {
  416. list($environment, $debug) = unserialize($data);
  417. $this->__construct($environment, $debug);
  418. }
  419. }