1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\HttpKernel; 13 14use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; 15use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; 16use Symfony\Component\DependencyInjection\ContainerInterface; 17use Symfony\Component\DependencyInjection\ContainerBuilder; 18use Symfony\Component\DependencyInjection\Dumper\PhpDumper; 19use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; 20use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; 21use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; 22use Symfony\Component\DependencyInjection\Loader\IniFileLoader; 23use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; 24use Symfony\Component\DependencyInjection\Loader\ClosureLoader; 25use Symfony\Component\HttpFoundation\Request; 26use Symfony\Component\HttpFoundation\Response; 27use Symfony\Component\HttpKernel\Bundle\BundleInterface; 28use Symfony\Component\HttpKernel\Config\EnvParametersResource; 29use Symfony\Component\HttpKernel\Config\FileLocator; 30use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; 31use Symfony\Component\HttpKernel\DependencyInjection\AddClassesToCachePass; 32use Symfony\Component\Config\Loader\LoaderResolver; 33use Symfony\Component\Config\Loader\DelegatingLoader; 34use Symfony\Component\Config\ConfigCache; 35use Symfony\Component\ClassLoader\ClassCollectionLoader; 36 37/** 38 * The Kernel is the heart of the Symfony system. 39 * 40 * It manages an environment made of bundles. 41 * 42 * @author Fabien Potencier <fabien@symfony.com> 43 * 44 * @api 45 */ 46abstract class Kernel implements KernelInterface, TerminableInterface 47{ 48 /** 49 * @var BundleInterface[] 50 */ 51 protected $bundles = array(); 52 53 protected $bundleMap; 54 protected $container; 55 protected $rootDir; 56 protected $environment; 57 protected $debug; 58 protected $booted = false; 59 protected $name; 60 protected $startTime; 61 protected $loadClassCache; 62 63 const VERSION = '2.6.13'; 64 const VERSION_ID = '20613'; 65 const MAJOR_VERSION = '2'; 66 const MINOR_VERSION = '6'; 67 const RELEASE_VERSION = '13'; 68 const EXTRA_VERSION = ''; 69 70 /** 71 * Constructor. 72 * 73 * @param string $environment The environment 74 * @param bool $debug Whether to enable debugging or not 75 * 76 * @api 77 */ 78 public function __construct($environment, $debug) 79 { 80 $this->environment = $environment; 81 $this->debug = (bool) $debug; 82 $this->rootDir = $this->getRootDir(); 83 $this->name = $this->getName(); 84 85 if ($this->debug) { 86 $this->startTime = microtime(true); 87 } 88 89 $this->init(); 90 } 91 92 /** 93 * @deprecated Deprecated since version 2.3, to be removed in 3.0. Move your logic in the constructor instead. 94 */ 95 public function init() 96 { 97 } 98 99 public function __clone() 100 { 101 if ($this->debug) { 102 $this->startTime = microtime(true); 103 } 104 105 $this->booted = false; 106 $this->container = null; 107 } 108 109 /** 110 * Boots the current kernel. 111 * 112 * @api 113 */ 114 public function boot() 115 { 116 if (true === $this->booted) { 117 return; 118 } 119 120 if ($this->loadClassCache) { 121 $this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]); 122 } 123 124 // init bundles 125 $this->initializeBundles(); 126 127 // init container 128 $this->initializeContainer(); 129 130 foreach ($this->getBundles() as $bundle) { 131 $bundle->setContainer($this->container); 132 $bundle->boot(); 133 } 134 135 $this->booted = true; 136 } 137 138 /** 139 * {@inheritdoc} 140 * 141 * @api 142 */ 143 public function terminate(Request $request, Response $response) 144 { 145 if (false === $this->booted) { 146 return; 147 } 148 149 if ($this->getHttpKernel() instanceof TerminableInterface) { 150 $this->getHttpKernel()->terminate($request, $response); 151 } 152 } 153 154 /** 155 * {@inheritdoc} 156 * 157 * @api 158 */ 159 public function shutdown() 160 { 161 if (false === $this->booted) { 162 return; 163 } 164 165 $this->booted = false; 166 167 foreach ($this->getBundles() as $bundle) { 168 $bundle->shutdown(); 169 $bundle->setContainer(null); 170 } 171 172 $this->container = null; 173 } 174 175 /** 176 * {@inheritdoc} 177 * 178 * @api 179 */ 180 public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) 181 { 182 if (false === $this->booted) { 183 $this->boot(); 184 } 185 186 return $this->getHttpKernel()->handle($request, $type, $catch); 187 } 188 189 /** 190 * Gets a HTTP kernel from the container. 191 * 192 * @return HttpKernel 193 */ 194 protected function getHttpKernel() 195 { 196 return $this->container->get('http_kernel'); 197 } 198 199 /** 200 * {@inheritdoc} 201 * 202 * @api 203 */ 204 public function getBundles() 205 { 206 return $this->bundles; 207 } 208 209 /** 210 * {@inheritdoc} 211 * 212 * @api 213 * 214 * @deprecated Deprecated since version 2.6, to be removed in 3.0. 215 */ 216 public function isClassInActiveBundle($class) 217 { 218 foreach ($this->getBundles() as $bundle) { 219 if (0 === strpos($class, $bundle->getNamespace())) { 220 return true; 221 } 222 } 223 224 return false; 225 } 226 227 /** 228 * {@inheritdoc} 229 * 230 * @api 231 */ 232 public function getBundle($name, $first = true) 233 { 234 if (!isset($this->bundleMap[$name])) { 235 throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the registerBundles() method of your %s.php file?', $name, get_class($this))); 236 } 237 238 if (true === $first) { 239 return $this->bundleMap[$name][0]; 240 } 241 242 return $this->bundleMap[$name]; 243 } 244 245 /** 246 * {@inheritdoc} 247 * 248 * @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle 249 */ 250 public function locateResource($name, $dir = null, $first = true) 251 { 252 if ('@' !== $name[0]) { 253 throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name)); 254 } 255 256 if (false !== strpos($name, '..')) { 257 throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $name)); 258 } 259 260 $bundleName = substr($name, 1); 261 $path = ''; 262 if (false !== strpos($bundleName, '/')) { 263 list($bundleName, $path) = explode('/', $bundleName, 2); 264 } 265 266 $isResource = 0 === strpos($path, 'Resources') && null !== $dir; 267 $overridePath = substr($path, 9); 268 $resourceBundle = null; 269 $bundles = $this->getBundle($bundleName, false); 270 $files = array(); 271 272 foreach ($bundles as $bundle) { 273 if ($isResource && file_exists($file = $dir.'/'.$bundle->getName().$overridePath)) { 274 if (null !== $resourceBundle) { 275 throw new \RuntimeException(sprintf('"%s" resource is hidden by a resource from the "%s" derived bundle. Create a "%s" file to override the bundle resource.', 276 $file, 277 $resourceBundle, 278 $dir.'/'.$bundles[0]->getName().$overridePath 279 )); 280 } 281 282 if ($first) { 283 return $file; 284 } 285 $files[] = $file; 286 } 287 288 if (file_exists($file = $bundle->getPath().'/'.$path)) { 289 if ($first && !$isResource) { 290 return $file; 291 } 292 $files[] = $file; 293 $resourceBundle = $bundle->getName(); 294 } 295 } 296 297 if (count($files) > 0) { 298 return $first && $isResource ? $files[0] : $files; 299 } 300 301 throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name)); 302 } 303 304 /** 305 * {@inheritdoc} 306 * 307 * @api 308 */ 309 public function getName() 310 { 311 if (null === $this->name) { 312 $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir)); 313 } 314 315 return $this->name; 316 } 317 318 /** 319 * {@inheritdoc} 320 * 321 * @api 322 */ 323 public function getEnvironment() 324 { 325 return $this->environment; 326 } 327 328 /** 329 * {@inheritdoc} 330 * 331 * @api 332 */ 333 public function isDebug() 334 { 335 return $this->debug; 336 } 337 338 /** 339 * {@inheritdoc} 340 * 341 * @api 342 */ 343 public function getRootDir() 344 { 345 if (null === $this->rootDir) { 346 $r = new \ReflectionObject($this); 347 $this->rootDir = str_replace('\\', '/', dirname($r->getFileName())); 348 } 349 350 return $this->rootDir; 351 } 352 353 /** 354 * {@inheritdoc} 355 * 356 * @api 357 */ 358 public function getContainer() 359 { 360 return $this->container; 361 } 362 363 /** 364 * Loads the PHP class cache. 365 * 366 * This methods only registers the fact that you want to load the cache classes. 367 * The cache will actually only be loaded when the Kernel is booted. 368 * 369 * That optimization is mainly useful when using the HttpCache class in which 370 * case the class cache is not loaded if the Response is in the cache. 371 * 372 * @param string $name The cache name prefix 373 * @param string $extension File extension of the resulting file 374 */ 375 public function loadClassCache($name = 'classes', $extension = '.php') 376 { 377 $this->loadClassCache = array($name, $extension); 378 } 379 380 /** 381 * Used internally. 382 */ 383 public function setClassCache(array $classes) 384 { 385 file_put_contents($this->getCacheDir().'/classes.map', sprintf('<?php return %s;', var_export($classes, true))); 386 } 387 388 /** 389 * {@inheritdoc} 390 * 391 * @api 392 */ 393 public function getStartTime() 394 { 395 return $this->debug ? $this->startTime : -INF; 396 } 397 398 /** 399 * {@inheritdoc} 400 * 401 * @api 402 */ 403 public function getCacheDir() 404 { 405 return $this->rootDir.'/cache/'.$this->environment; 406 } 407 408 /** 409 * {@inheritdoc} 410 * 411 * @api 412 */ 413 public function getLogDir() 414 { 415 return $this->rootDir.'/logs'; 416 } 417 418 /** 419 * {@inheritdoc} 420 * 421 * @api 422 */ 423 public function getCharset() 424 { 425 return 'UTF-8'; 426 } 427 428 protected function doLoadClassCache($name, $extension) 429 { 430 if (!$this->booted && is_file($this->getCacheDir().'/classes.map')) { 431 ClassCollectionLoader::load(include($this->getCacheDir().'/classes.map'), $this->getCacheDir(), $name, $this->debug, false, $extension); 432 } 433 } 434 435 /** 436 * Initializes the data structures related to the bundle management. 437 * 438 * - the bundles property maps a bundle name to the bundle instance, 439 * - the bundleMap property maps a bundle name to the bundle inheritance hierarchy (most derived bundle first). 440 * 441 * @throws \LogicException if two bundles share a common name 442 * @throws \LogicException if a bundle tries to extend a non-registered bundle 443 * @throws \LogicException if a bundle tries to extend itself 444 * @throws \LogicException if two bundles extend the same ancestor 445 */ 446 protected function initializeBundles() 447 { 448 // init bundles 449 $this->bundles = array(); 450 $topMostBundles = array(); 451 $directChildren = array(); 452 453 foreach ($this->registerBundles() as $bundle) { 454 $name = $bundle->getName(); 455 if (isset($this->bundles[$name])) { 456 throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s"', $name)); 457 } 458 $this->bundles[$name] = $bundle; 459 460 if ($parentName = $bundle->getParent()) { 461 if (isset($directChildren[$parentName])) { 462 throw new \LogicException(sprintf('Bundle "%s" is directly extended by two bundles "%s" and "%s".', $parentName, $name, $directChildren[$parentName])); 463 } 464 if ($parentName == $name) { 465 throw new \LogicException(sprintf('Bundle "%s" can not extend itself.', $name)); 466 } 467 $directChildren[$parentName] = $name; 468 } else { 469 $topMostBundles[$name] = $bundle; 470 } 471 } 472 473 // look for orphans 474 if (!empty($directChildren) && count($diff = array_diff_key($directChildren, $this->bundles))) { 475 $diff = array_keys($diff); 476 477 throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0])); 478 } 479 480 // inheritance 481 $this->bundleMap = array(); 482 foreach ($topMostBundles as $name => $bundle) { 483 $bundleMap = array($bundle); 484 $hierarchy = array($name); 485 486 while (isset($directChildren[$name])) { 487 $name = $directChildren[$name]; 488 array_unshift($bundleMap, $this->bundles[$name]); 489 $hierarchy[] = $name; 490 } 491 492 foreach ($hierarchy as $bundle) { 493 $this->bundleMap[$bundle] = $bundleMap; 494 array_pop($bundleMap); 495 } 496 } 497 } 498 499 /** 500 * Gets the container class. 501 * 502 * @return string The container class 503 */ 504 protected function getContainerClass() 505 { 506 return $this->name.ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer'; 507 } 508 509 /** 510 * Gets the container's base class. 511 * 512 * All names except Container must be fully qualified. 513 * 514 * @return string 515 */ 516 protected function getContainerBaseClass() 517 { 518 return 'Container'; 519 } 520 521 /** 522 * Initializes the service container. 523 * 524 * The cached version of the service container is used when fresh, otherwise the 525 * container is built. 526 */ 527 protected function initializeContainer() 528 { 529 $class = $this->getContainerClass(); 530 $cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug); 531 $fresh = true; 532 if (!$cache->isFresh()) { 533 $container = $this->buildContainer(); 534 $container->compile(); 535 $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); 536 537 $fresh = false; 538 } 539 540 require_once $cache; 541 542 $this->container = new $class(); 543 $this->container->set('kernel', $this); 544 545 if (!$fresh && $this->container->has('cache_warmer')) { 546 $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir')); 547 } 548 } 549 550 /** 551 * Returns the kernel parameters. 552 * 553 * @return array An array of kernel parameters 554 */ 555 protected function getKernelParameters() 556 { 557 $bundles = array(); 558 foreach ($this->bundles as $name => $bundle) { 559 $bundles[$name] = get_class($bundle); 560 } 561 562 return array_merge( 563 array( 564 'kernel.root_dir' => realpath($this->rootDir) ?: $this->rootDir, 565 'kernel.environment' => $this->environment, 566 'kernel.debug' => $this->debug, 567 'kernel.name' => $this->name, 568 'kernel.cache_dir' => realpath($this->getCacheDir()) ?: $this->getCacheDir(), 569 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), 570 'kernel.bundles' => $bundles, 571 'kernel.charset' => $this->getCharset(), 572 'kernel.container_class' => $this->getContainerClass(), 573 ), 574 $this->getEnvParameters() 575 ); 576 } 577 578 /** 579 * Gets the environment parameters. 580 * 581 * Only the parameters starting with "SYMFONY__" are considered. 582 * 583 * @return array An array of parameters 584 */ 585 protected function getEnvParameters() 586 { 587 $parameters = array(); 588 foreach ($_SERVER as $key => $value) { 589 if (0 === strpos($key, 'SYMFONY__')) { 590 $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value; 591 } 592 } 593 594 return $parameters; 595 } 596 597 /** 598 * Builds the service container. 599 * 600 * @return ContainerBuilder The compiled service container 601 * 602 * @throws \RuntimeException 603 */ 604 protected function buildContainer() 605 { 606 foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) { 607 if (!is_dir($dir)) { 608 if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { 609 throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir)); 610 } 611 } elseif (!is_writable($dir)) { 612 throw new \RuntimeException(sprintf("Unable to write in the %s directory (%s)\n", $name, $dir)); 613 } 614 } 615 616 $container = $this->getContainerBuilder(); 617 $container->addObjectResource($this); 618 $this->prepareContainer($container); 619 620 if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { 621 $container->merge($cont); 622 } 623 624 $container->addCompilerPass(new AddClassesToCachePass($this)); 625 $container->addResource(new EnvParametersResource('SYMFONY__')); 626 627 return $container; 628 } 629 630 /** 631 * Prepares the ContainerBuilder before it is compiled. 632 * 633 * @param ContainerBuilder $container A ContainerBuilder instance 634 */ 635 protected function prepareContainer(ContainerBuilder $container) 636 { 637 $extensions = array(); 638 foreach ($this->bundles as $bundle) { 639 if ($extension = $bundle->getContainerExtension()) { 640 $container->registerExtension($extension); 641 $extensions[] = $extension->getAlias(); 642 } 643 644 if ($this->debug) { 645 $container->addObjectResource($bundle); 646 } 647 } 648 foreach ($this->bundles as $bundle) { 649 $bundle->build($container); 650 } 651 652 // ensure these extensions are implicitly loaded 653 $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions)); 654 } 655 656 /** 657 * Gets a new ContainerBuilder instance used to build the service container. 658 * 659 * @return ContainerBuilder 660 */ 661 protected function getContainerBuilder() 662 { 663 $container = new ContainerBuilder(new ParameterBag($this->getKernelParameters())); 664 665 if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) { 666 $container->setProxyInstantiator(new RuntimeInstantiator()); 667 } 668 669 return $container; 670 } 671 672 /** 673 * Dumps the service container to PHP code in the cache. 674 * 675 * @param ConfigCache $cache The config cache 676 * @param ContainerBuilder $container The service container 677 * @param string $class The name of the class to generate 678 * @param string $baseClass The name of the container's base class 679 */ 680 protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, $class, $baseClass) 681 { 682 // cache the container 683 $dumper = new PhpDumper($container); 684 685 if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper')) { 686 $dumper->setProxyDumper(new ProxyDumper(md5((string) $cache))); 687 } 688 689 $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => (string) $cache)); 690 if (!$this->debug) { 691 $content = static::stripComments($content); 692 } 693 694 $cache->write($content, $container->getResources()); 695 } 696 697 /** 698 * Returns a loader for the container. 699 * 700 * @param ContainerInterface $container The service container 701 * 702 * @return DelegatingLoader The loader 703 */ 704 protected function getContainerLoader(ContainerInterface $container) 705 { 706 $locator = new FileLocator($this); 707 $resolver = new LoaderResolver(array( 708 new XmlFileLoader($container, $locator), 709 new YamlFileLoader($container, $locator), 710 new IniFileLoader($container, $locator), 711 new PhpFileLoader($container, $locator), 712 new ClosureLoader($container), 713 )); 714 715 return new DelegatingLoader($resolver); 716 } 717 718 /** 719 * Removes comments from a PHP source string. 720 * 721 * We don't use the PHP php_strip_whitespace() function 722 * as we want the content to be readable and well-formatted. 723 * 724 * @param string $source A PHP string 725 * 726 * @return string The PHP string with the comments removed 727 */ 728 public static function stripComments($source) 729 { 730 if (!function_exists('token_get_all')) { 731 return $source; 732 } 733 734 $rawChunk = ''; 735 $output = ''; 736 $tokens = token_get_all($source); 737 $ignoreSpace = false; 738 for (reset($tokens); false !== $token = current($tokens); next($tokens)) { 739 if (is_string($token)) { 740 $rawChunk .= $token; 741 } elseif (T_START_HEREDOC === $token[0]) { 742 $output .= $rawChunk.$token[1]; 743 do { 744 $token = next($tokens); 745 $output .= $token[1]; 746 } while ($token[0] !== T_END_HEREDOC); 747 $rawChunk = ''; 748 } elseif (T_WHITESPACE === $token[0]) { 749 if ($ignoreSpace) { 750 $ignoreSpace = false; 751 752 continue; 753 } 754 755 // replace multiple new lines with a single newline 756 $rawChunk .= preg_replace(array('/\n{2,}/S'), "\n", $token[1]); 757 } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { 758 $ignoreSpace = true; 759 } else { 760 $rawChunk .= $token[1]; 761 762 // The PHP-open tag already has a new-line 763 if (T_OPEN_TAG === $token[0]) { 764 $ignoreSpace = true; 765 } 766 } 767 } 768 769 $output .= $rawChunk; 770 771 return $output; 772 } 773 774 public function serialize() 775 { 776 return serialize(array($this->environment, $this->debug)); 777 } 778 779 public function unserialize($data) 780 { 781 list($environment, $debug) = unserialize($data); 782 783 $this->__construct($environment, $debug); 784 } 785} 786