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