Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.80% covered (warning)
89.80%
176 / 196
69.23% covered (warning)
69.23%
18 / 26
CRAP
0.00% covered (danger)
0.00%
0 / 1
container_builder
89.80% covered (warning)
89.80%
176 / 196
69.23% covered (warning)
69.23%
18 / 26
66.08
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 get_container
96.23% covered (success)
96.23%
51 / 53
0.00% covered (danger)
0.00%
0 / 1
15
 with_environment
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 with_extensions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 without_extensions
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 with_cache
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 without_cache
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 with_cache_dir
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 with_compiled_container
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 without_compiled_container
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 with_config_path
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 with_custom_parameters
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 with_config
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_config_path
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 get_cache_dir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 load_extensions
90.32% covered (success)
90.32%
28 / 31
0.00% covered (danger)
0.00%
0 / 1
6.03
 dump_container
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 create_container
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 inject_custom_parameters
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 inject_dbal_driver
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
5.01
 get_core_parameters
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 get_env_parameters
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 get_container_filename
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 get_autoload_filename
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 get_environment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 register_ext_compiler_pass
63.16% covered (warning)
63.16%
12 / 19
0.00% covered (danger)
0.00%
0 / 1
4.80
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb\di;
15
16use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
17use Symfony\Component\Config\ConfigCache;
18use Symfony\Component\Config\FileLocator;
19use Symfony\Component\DependencyInjection\ContainerBuilder;
20use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
21use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
22use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
23use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
24use Symfony\Component\Filesystem\Exception\IOException;
25use Symfony\Component\Finder\Finder;
26use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
27use phpbb\filesystem\helper as filesystem_helper;
28
29class container_builder
30{
31    /**
32     * @var string The environment to use.
33     */
34    protected $environment;
35
36    /**
37     * @var string phpBB Root Path
38     */
39    protected $phpbb_root_path;
40
41    /**
42     * @var string php file extension
43     */
44    protected $php_ext;
45
46    /**
47     * The container under construction
48     *
49     * @var \phpbb_cache_container|ContainerBuilder
50     */
51    protected $container;
52
53    /**
54     * Indicates whether extensions should be used (default to true).
55     *
56     * @var bool
57     */
58    protected $use_extensions = true;
59
60    /**
61     * Defines a custom path to find the configuration of the container (default to $this->phpbb_root_path . 'config')
62     *
63     * @var string
64     */
65    protected $config_path = null;
66
67    /**
68     * Indicates whether the container should be dumped to the filesystem (default to true).
69     *
70     * If DEBUG_CONTAINER is set this option is ignored and a new container is build.
71     *
72     * @var bool
73     */
74    protected $use_cache = true;
75
76    /**
77     * Indicates if the container should be compiled automatically (default to true).
78     *
79     * @var bool
80     */
81    protected $compile_container = true;
82
83    /**
84     * Custom parameters to inject into the container.
85     *
86     * Default to:
87     *     array(
88     *         'core.root_path', $this->phpbb_root_path,
89     *         'core.php_ext', $this->php_ext,
90     * );
91     *
92     * @var array
93     */
94    protected $custom_parameters = [];
95
96    /**
97     * @var \phpbb\config_php_file
98     */
99    protected $config_php_file;
100
101    /**
102     * @var string
103     */
104    protected $cache_dir;
105
106    /**
107     * @var array
108     */
109    private $container_extensions;
110
111    /** @var \Exception */
112    private $build_exception;
113
114    /**
115     * @var array
116     */
117    private $env_parameters = [];
118
119    /**
120     * @var \phpbb\db\driver\driver_interface
121     */
122    protected $dbal_connection = null;
123
124    /**
125     * Constructor
126     *
127     * @param string $phpbb_root_path Path to the phpbb includes directory.
128     * @param string $php_ext php file extension
129     */
130    public function __construct($phpbb_root_path, $php_ext)
131    {
132        $this->phpbb_root_path    = $phpbb_root_path;
133        $this->php_ext            = $php_ext;
134        $this->env_parameters    = $this->get_env_parameters();
135
136        if (isset($this->env_parameters['core.cache_dir']))
137        {
138            $this->with_cache_dir($this->env_parameters['core.cache_dir']);
139        }
140    }
141
142    /**
143     * Build and return a new Container respecting the current configuration
144     *
145     * @return \phpbb_cache_container|ContainerBuilder
146     * @throws \Exception
147     */
148    public function get_container()
149    {
150        try
151        {
152            $build_container = true;
153
154            if ($this->use_cache)
155            {
156                if ($this->use_extensions)
157                {
158                    $autoload_cache = new ConfigCache($this->get_autoload_filename(), defined('DEBUG'));
159
160                    if (!$autoload_cache->isFresh())
161                    {
162                        // autoload cache should be refreshed
163                        $this->load_extensions();
164                    }
165
166                    require($this->get_autoload_filename());
167                }
168
169                $container_filename = $this->get_container_filename();
170                $config_cache = new ConfigCache($container_filename, defined('DEBUG'));
171
172                if ($config_cache->isFresh())
173                {
174                    require($config_cache->getPath());
175                    $this->container = new \phpbb_cache_container();
176                    $build_container = false;
177                }
178            }
179
180            if ($build_container)
181            {
182                $this->container_extensions = [
183                    new extension\core($this->get_config_path()),
184                ];
185
186                if ($this->use_extensions)
187                {
188                    $this->load_extensions();
189                }
190
191                // Add tables extension after all extensions
192                $this->container_extensions[] = new extension\tables();
193
194                // Inject the config
195                if ($this->config_php_file)
196                {
197                    $this->container_extensions[] = new extension\config($this->config_php_file);
198                }
199
200                $this->container = $this->create_container($this->container_extensions);
201
202                // Easy collections through tags
203                $this->container->addCompilerPass(new pass\collection_pass());
204
205                // Mark all services public
206                $this->container->addCompilerPass(new pass\markpublic_pass());
207
208                // Convert old event dispatcher syntax
209                $this->container->addCompilerPass(new pass\convert_events());
210
211                // Event listeners
212                $this->container->addCompilerPass(new RegisterListenersPass());
213
214                if ($this->use_extensions)
215                {
216                    $this->register_ext_compiler_pass();
217                }
218
219                $loader     = new YamlFileLoader($this->container, new FileLocator(filesystem_helper::realpath($this->get_config_path())));
220                $loader->load($this->container->getParameter('core.environment') . '/config.yml');
221
222                $this->inject_custom_parameters();
223
224                if ($this->compile_container)
225                {
226                    $this->container->compile();
227
228                    if ($this->use_cache)
229                    {
230                        $this->dump_container($config_cache);
231                    }
232                }
233            }
234
235            if ($this->config_php_file)
236            {
237                $this->container->set('config.php', $this->config_php_file);
238            }
239
240            $this->inject_dbal_driver();
241
242            return $this->container;
243        }
244        catch (\Exception $e)
245        {
246            // Don't try to recover if we are in the development environment
247            if ($this->get_environment() === 'development')
248            {
249                throw $e;
250            }
251
252            if ($this->build_exception === null)
253            {
254                $this->build_exception = $e;
255
256                return $this
257                    ->without_extensions()
258                    ->without_cache()
259                    ->with_custom_parameters(array_merge($this->custom_parameters, [
260                        'container_exception' => $e,
261                    ]))
262                    ->get_container();
263            }
264            else
265            {
266                // Rethrow the original exception if it's still failing
267                throw $this->build_exception;
268            }
269        }
270    }
271
272    /**
273     * Enable the extensions.
274     *
275     * @param string $environment The environment to use
276     * @return $this
277     */
278    public function with_environment($environment)
279    {
280        $this->environment = $environment;
281
282        return $this;
283    }
284
285    /**
286     * Enable the extensions.
287     *
288     * @return $this
289     */
290    public function with_extensions()
291    {
292        $this->use_extensions = true;
293
294        return $this;
295    }
296
297    /**
298     * Disable the extensions.
299     *
300     * @return $this
301     */
302    public function without_extensions()
303    {
304        $this->use_extensions = false;
305
306        return $this;
307    }
308
309    /**
310     * Enable the caching of the container.
311     *
312     * If DEBUG_CONTAINER is set this option is ignored and a new container is build.
313     *
314     * @return $this
315     */
316    public function with_cache()
317    {
318        $this->use_cache = true;
319
320        return $this;
321    }
322
323    /**
324     * Disable the caching of the container.
325     *
326     * @return $this
327     */
328    public function without_cache()
329    {
330        $this->use_cache = false;
331
332        return $this;
333    }
334
335    /**
336     * Set the cache directory.
337     *
338     * @param string $cache_dir The cache directory.
339     * @return $this
340     */
341    public function with_cache_dir($cache_dir)
342    {
343        $this->cache_dir = $cache_dir;
344
345        return $this;
346    }
347
348    /**
349     * Enable the compilation of the container.
350     *
351     * @return $this
352     */
353    public function with_compiled_container()
354    {
355        $this->compile_container = true;
356
357        return $this;
358    }
359
360    /**
361     * Disable the compilation of the container.
362     *
363     * @return $this
364     */
365    public function without_compiled_container()
366    {
367        $this->compile_container = false;
368
369        return $this;
370    }
371
372    /**
373     * Set a custom path to find the configuration of the container.
374     *
375     * @param string $config_path
376     * @return $this
377     */
378    public function with_config_path($config_path)
379    {
380        $this->config_path = $config_path;
381
382        return $this;
383    }
384
385    /**
386     * Set custom parameters to inject into the container.
387     *
388     * @param array $custom_parameters
389     * @return $this
390     */
391    public function with_custom_parameters($custom_parameters)
392    {
393        $this->custom_parameters = $custom_parameters;
394
395        return $this;
396    }
397
398    /**
399     * Set custom parameters to inject into the container.
400     *
401     * @param \phpbb\config_php_file $config_php_file
402     * @return $this
403     */
404    public function with_config(\phpbb\config_php_file $config_php_file)
405    {
406        $this->config_php_file = $config_php_file;
407
408        return $this;
409    }
410
411    /**
412     * Returns the path to the container configuration (default: root_path/config)
413     *
414     * @return string
415     */
416    protected function get_config_path()
417    {
418        return $this->config_path ?: $this->phpbb_root_path . 'config';
419    }
420
421    /**
422     * Returns the path to the cache directory (default: root_path/cache/environment).
423     *
424     * @return string Path to the cache directory.
425     */
426    public function get_cache_dir()
427    {
428        return $this->cache_dir ?: $this->phpbb_root_path . 'cache/' . $this->get_environment() . '/';
429    }
430
431    /**
432     * Load the enabled extensions.
433     */
434    protected function load_extensions()
435    {
436        if ($this->config_php_file !== null)
437        {
438            // Build an intermediate container to load the ext list from the database
439            $container_builder = new container_builder($this->phpbb_root_path, $this->php_ext);
440            $ext_container = $container_builder
441                ->without_cache()
442                ->without_extensions()
443                ->with_config($this->config_php_file)
444                ->with_config_path($this->get_config_path())
445                ->with_environment('production')
446                ->without_compiled_container()
447                ->get_container()
448            ;
449
450            $ext_container->register('cache.driver', '\\phpbb\\cache\\driver\\dummy');
451            $ext_container->compile();
452
453            /** @var \phpbb\config\config $config */
454            $config = $ext_container->get('config');
455            if (@is_file($this->phpbb_root_path . $config['exts_composer_vendor_dir'] . '/autoload.php'))
456            {
457                require_once($this->phpbb_root_path . $config['exts_composer_vendor_dir'] . '/autoload.php');
458            }
459
460            $extensions = $ext_container->get('ext.manager')->all_enabled();
461
462            // Load each extension found
463            $autoloaders = '<?php
464/**
465 * Loads all extensions custom auto-loaders.
466 *
467 * This file has been auto-generated
468 * by phpBB while loading the extensions.
469 */
470
471';
472            foreach ($extensions as $ext_name => $path)
473            {
474                $extension_class = '\\' . str_replace('/', '\\', $ext_name) . '\\di\\extension';
475
476                if (!class_exists($extension_class))
477                {
478                    $extension_class = '\\phpbb\\extension\\di\\extension_base';
479                }
480
481                $this->container_extensions[] = new $extension_class($ext_name, $path);
482
483                // Load extension autoloader
484                $filename = $path . 'vendor/autoload.php';
485                if (file_exists($filename))
486                {
487                    $autoloaders .= "require('{$filename}');\n";
488                }
489            }
490
491            $configCache = new ConfigCache($this->get_autoload_filename(), false);
492            $configCache->write($autoloaders);
493
494            require($this->get_autoload_filename());
495        }
496        else
497        {
498            // To load the extensions we need the database credentials.
499            // Automatically disable the extensions if we don't have them.
500            $this->use_extensions = false;
501        }
502    }
503
504    /**
505     * Dump the container to the disk.
506     *
507     * @param ConfigCache $cache The config cache
508     */
509    protected function dump_container($cache)
510    {
511        try
512        {
513            $dumper = new PhpDumper($this->container);
514            $proxy_dumper = new ProxyDumper();
515            $dumper->setProxyDumper($proxy_dumper);
516
517            $cached_container_dump = $dumper->dump(array(
518                'class'      => 'phpbb_cache_container',
519                'base_class' => 'Symfony\\Component\\DependencyInjection\\Container',
520            ));
521
522            $cache->write($cached_container_dump, $this->container->getResources());
523        }
524        catch (IOException $e)
525        {
526            // Don't fail if the cache isn't writeable
527        }
528    }
529
530    /**
531     * Create the ContainerBuilder object
532     *
533     * @param array $extensions Array of Container extension objects
534     * @return ContainerBuilder object
535     */
536    protected function create_container(array $extensions)
537    {
538        $container = new ContainerBuilder(new ParameterBag($this->get_core_parameters()));
539        $container->setProxyInstantiator(new proxy_instantiator($this->get_cache_dir()));
540
541        $extensions_alias = array();
542
543        foreach ($extensions as $extension)
544        {
545            $container->registerExtension($extension);
546            $extensions_alias[] = $extension->getAlias();
547        }
548
549        $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions_alias));
550
551        return $container;
552    }
553
554    /**
555     * Inject the customs parameters into the container
556     */
557    protected function inject_custom_parameters()
558    {
559        foreach ($this->custom_parameters as $key => $value)
560        {
561            $this->container->setParameter($key, $value);
562        }
563    }
564
565    /**
566     * Inject the dbal connection driver into container
567     */
568    protected function inject_dbal_driver()
569    {
570        if (empty($this->config_php_file))
571        {
572            return;
573        }
574
575        $config_data = $this->config_php_file->get_all();
576        if (!empty($config_data))
577        {
578            if ($this->dbal_connection === null)
579            {
580                $dbal_driver_class = \phpbb\config_php_file::convert_30_dbms_to_31($this->config_php_file->get('dbms'));
581                /** @var \phpbb\db\driver\driver_interface $dbal_connection */
582                $this->dbal_connection = new $dbal_driver_class();
583                $this->dbal_connection->sql_connect(
584                    $this->config_php_file->get('dbhost'),
585                    $this->config_php_file->get('dbuser'),
586                    $this->config_php_file->get('dbpasswd'),
587                    $this->config_php_file->get('dbname'),
588                    $this->config_php_file->get('dbport'),
589                    false,
590                    defined('PHPBB_DB_NEW_LINK') ? PHPBB_DB_NEW_LINK : false
591                );
592            }
593            $this->container->set('dbal.conn.driver', $this->dbal_connection);
594        }
595    }
596
597    /**
598     * Returns the core parameters.
599     *
600     * @return array An array of core parameters
601     */
602    protected function get_core_parameters()
603    {
604        return array_merge(
605            [
606                'core.root_path'     => $this->phpbb_root_path,
607                'core.php_ext'       => $this->php_ext,
608                'core.environment'   => $this->get_environment(),
609                'core.debug'         => defined('DEBUG') ? DEBUG : false,
610                'core.cache_dir'     => $this->get_cache_dir(),
611            ],
612            $this->env_parameters
613        );
614    }
615
616    /**
617     * Gets the environment parameters.
618     *
619     * Only the parameters starting with "PHPBB__" are considered.
620     *
621     * @return array An array of parameters
622     */
623    protected function get_env_parameters()
624    {
625        $parameters = array();
626        foreach ($_SERVER as $key => $value)
627        {
628            if (0 === strpos($key, 'PHPBB__'))
629            {
630                $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value;
631            }
632        }
633
634        return $parameters;
635    }
636
637    /**
638     * Get the filename under which the dumped container will be stored.
639     *
640     * @return string Path for dumped container
641     */
642    protected function get_container_filename()
643    {
644        $container_params = [
645            'phpbb_root_path' => $this->phpbb_root_path,
646            'use_extensions' => $this->use_extensions,
647            'config_path' => $this->config_path,
648        ];
649
650        return $this->get_cache_dir() . 'container_' . md5(implode(',', $container_params)) . '.' . $this->php_ext;
651    }
652
653    /**
654     * Get the filename under which the dumped extensions autoloader will be stored.
655     *
656     * @return string Path for dumped extensions autoloader
657     */
658    protected function get_autoload_filename()
659    {
660        $container_params = [
661            'phpbb_root_path' => $this->phpbb_root_path,
662            'use_extensions' => $this->use_extensions,
663            'config_path' => $this->config_path,
664        ];
665
666        return $this->get_cache_dir() . 'autoload_' . md5(implode(',', $container_params)) . '.' . $this->php_ext;
667    }
668
669    /**
670     * Return the name of the current environment.
671     *
672     * @return string
673     */
674    protected function get_environment()
675    {
676        return $this->environment ?: PHPBB_ENVIRONMENT;
677    }
678
679    private function register_ext_compiler_pass()
680    {
681        $finder = new Finder();
682        $finder
683            ->name('*_pass.php')
684            ->path('di/pass')
685            ->files()
686            ->ignoreDotFiles(true)
687            ->ignoreUnreadableDirs(true)
688            ->ignoreVCS(true)
689            ->followLinks()
690            ->in($this->phpbb_root_path . 'ext')
691        ;
692
693        /** @var \SplFileInfo $pass */
694        foreach ($finder as $pass)
695        {
696            $filename = $pass->getPathname();
697            $filename = substr($filename, 0, -strlen('.' . $pass->getExtension()));
698            $filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename);
699            $className = preg_replace('#^.*ext/#', '', $filename);
700            $className = '\\' . str_replace('/', '\\', $className);
701
702            if (class_exists($className) && in_array('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface', class_implements($className), true))
703            {
704                $this->container->addCompilerPass(new $className());
705            }
706        }
707    }
708}