Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.89% covered (warning)
77.89%
155 / 199
69.23% covered (warning)
69.23%
18 / 26
CRAP
0.00% covered (danger)
0.00%
0 / 1
container_builder
77.89% covered (warning)
77.89%
155 / 199
69.23% covered (warning)
69.23%
18 / 26
108.28
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
75.47% covered (warning)
75.47%
40 / 53
0.00% covered (danger)
0.00%
0 / 1
18.32
 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
91.18% covered (success)
91.18%
31 / 34
0.00% covered (danger)
0.00%
0 / 1
7.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%
7 / 7
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
22.22% covered (danger)
22.22%
4 / 18
0.00% covered (danger)
0.00%
0 / 1
22.94
 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\Component\Config\ConfigCache;
17use Symfony\Component\Config\FileLocator;
18use Symfony\Component\DependencyInjection\ContainerBuilder;
19use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
20use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
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            $resources = [];
462
463            // Load each extension found
464            $autoloaders = '<?php
465/**
466 * Loads all extensions custom auto-loaders.
467 *
468 * This file has been auto-generated
469 * by phpBB while loading the extensions.
470 */
471
472';
473            foreach ($extensions as $ext_name => $path)
474            {
475                $extension_class = '\\' . str_replace('/', '\\', $ext_name) . '\\di\\extension';
476
477                if (!class_exists($extension_class))
478                {
479                    $extension_class = '\\phpbb\\extension\\di\\extension_base';
480                }
481
482                $this->container_extensions[] = new $extension_class($ext_name, $path);
483
484                if (is_dir($path))
485                {
486                    $resources[] = new \Symfony\Component\Config\Resource\DirectoryResource($path);
487                }
488
489                // Load extension autoloader
490                $filename = $path . 'vendor/autoload.php';
491                if (file_exists($filename))
492                {
493                    $autoloaders .= "require('{$filename}');\n";
494                }
495            }
496
497            $configCache = new ConfigCache($this->get_autoload_filename(), false);
498            $configCache->write($autoloaders, $resources);
499
500            require($this->get_autoload_filename());
501        }
502        else
503        {
504            // To load the extensions we need the database credentials.
505            // Automatically disable the extensions if we don't have them.
506            $this->use_extensions = false;
507        }
508    }
509
510    /**
511     * Dump the container to the disk.
512     *
513     * @param ConfigCache $cache The config cache
514     */
515    protected function dump_container($cache)
516    {
517        try
518        {
519            $dumper = new PhpDumper($this->container);
520            $proxy_dumper = new LazyServiceDumper();
521            $dumper->setProxyDumper($proxy_dumper);
522
523            $cached_container_dump = $dumper->dump(array(
524                'class'      => 'phpbb_cache_container',
525                'base_class' => 'Symfony\\Component\\DependencyInjection\\Container',
526            ));
527
528            $cache->write($cached_container_dump, $this->container->getResources());
529        }
530        catch (IOException $e)
531        {
532            // Don't fail if the cache isn't writeable
533        }
534    }
535
536    /**
537     * Create the ContainerBuilder object
538     *
539     * @param array $extensions Array of Container extension objects
540     * @return ContainerBuilder object
541     */
542    protected function create_container(array $extensions)
543    {
544        $container = new ContainerBuilder(new ParameterBag($this->get_core_parameters()));
545
546        $extensions_alias = array();
547
548        foreach ($extensions as $extension)
549        {
550            $container->registerExtension($extension);
551            $extensions_alias[] = $extension->getAlias();
552        }
553
554        $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions_alias));
555
556        return $container;
557    }
558
559    /**
560     * Inject the customs parameters into the container
561     */
562    protected function inject_custom_parameters()
563    {
564        foreach ($this->custom_parameters as $key => $value)
565        {
566            $this->container->setParameter($key, $value);
567        }
568    }
569
570    /**
571     * Inject the dbal connection driver into container
572     */
573    protected function inject_dbal_driver()
574    {
575        if (empty($this->config_php_file))
576        {
577            return;
578        }
579
580        $config_data = $this->config_php_file->get_all();
581        if (!empty($config_data) && !empty($config_data['dbms']))
582        {
583            if ($this->dbal_connection === null)
584            {
585                $dbal_driver_class = \phpbb\config_php_file::convert_30_dbms_to_31($this->config_php_file->get('dbms'));
586                /** @var \phpbb\db\driver\driver_interface $dbal_connection */
587                $this->dbal_connection = new $dbal_driver_class();
588                $this->dbal_connection->sql_connect(
589                    $this->config_php_file->get('dbhost'),
590                    $this->config_php_file->get('dbuser'),
591                    $this->config_php_file->get('dbpasswd'),
592                    $this->config_php_file->get('dbname'),
593                    $this->config_php_file->get('dbport'),
594                    false,
595                    defined('PHPBB_DB_NEW_LINK') ? PHPBB_DB_NEW_LINK : false
596                );
597            }
598            $this->container->set('dbal.conn.driver', $this->dbal_connection);
599        }
600        else
601        {
602            return;
603        }
604    }
605
606    /**
607     * Returns the core parameters.
608     *
609     * @return array An array of core parameters
610     */
611    protected function get_core_parameters()
612    {
613        return array_merge(
614            [
615                'core.root_path'     => $this->phpbb_root_path,
616                'core.php_ext'       => $this->php_ext,
617                'core.environment'   => $this->get_environment(),
618                'core.debug'         => defined('DEBUG') ? DEBUG : false,
619                'core.cache_dir'     => $this->get_cache_dir(),
620            ],
621            $this->env_parameters
622        );
623    }
624
625    /**
626     * Gets the environment parameters.
627     *
628     * Only the parameters starting with "PHPBB__" are considered.
629     *
630     * @return array An array of parameters
631     */
632    protected function get_env_parameters()
633    {
634        $parameters = array();
635        foreach ($_SERVER as $key => $value)
636        {
637            if (0 === strpos($key, 'PHPBB__'))
638            {
639                $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value;
640            }
641        }
642
643        return $parameters;
644    }
645
646    /**
647     * Get the filename under which the dumped container will be stored.
648     *
649     * @return string Path for dumped container
650     */
651    protected function get_container_filename()
652    {
653        $container_params = [
654            'phpbb_root_path' => $this->phpbb_root_path,
655            'use_extensions' => $this->use_extensions,
656            'config_path' => $this->config_path,
657        ];
658
659        return $this->get_cache_dir() . 'container_' . md5(implode(',', $container_params)) . '.' . $this->php_ext;
660    }
661
662    /**
663     * Get the filename under which the dumped extensions autoloader will be stored.
664     *
665     * @return string Path for dumped extensions autoloader
666     */
667    protected function get_autoload_filename()
668    {
669        $container_params = [
670            'phpbb_root_path' => $this->phpbb_root_path,
671            'use_extensions' => $this->use_extensions,
672            'config_path' => $this->config_path,
673        ];
674
675        return $this->get_cache_dir() . 'autoload_' . md5(implode(',', $container_params)) . '.' . $this->php_ext;
676    }
677
678    /**
679     * Return the name of the current environment.
680     *
681     * @return string
682     */
683    protected function get_environment()
684    {
685        return $this->environment ?: PHPBB_ENVIRONMENT;
686    }
687
688    private function register_ext_compiler_pass()
689    {
690        $finder = new Finder();
691        $finder
692            ->name('*_pass.php')
693            ->path('di/pass')
694            ->files()
695            ->ignoreDotFiles(true)
696            ->ignoreUnreadableDirs(true)
697            ->ignoreVCS(true)
698            ->followLinks()
699            ->in($this->phpbb_root_path . 'ext')
700        ;
701
702        /** @var \SplFileInfo $pass */
703        foreach ($finder as $pass)
704        {
705            $filename = $pass->getPathname();
706            $filename = substr($filename, 0, -strlen('.' . $pass->getExtension()));
707            $filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename);
708            $className = preg_replace('#^.*ext/#', '', $filename);
709            $className = '\\' . str_replace('/', '\\', $className);
710
711            if (class_exists($className) && in_array('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface', class_implements($className), true))
712            {
713                $this->container->addCompilerPass(new $className());
714            }
715        }
716    }
717}