Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
69.23% covered (warning)
69.23%
18 / 26
CRAP
89.50% covered (warning)
89.50%
162 / 181
container_builder
0.00% covered (danger)
0.00%
0 / 1
69.23% covered (warning)
69.23%
18 / 26
65.30
89.50% covered (warning)
89.50%
162 / 181
 __construct
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 get_container
0.00% covered (danger)
0.00%
0 / 1
15
96.00% covered (success)
96.00%
48 / 50
 with_environment
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 with_extensions
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 2
 without_extensions
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 with_cache
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 2
 without_cache
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 with_cache_dir
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 with_compiled_container
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 2
 without_compiled_container
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 with_config_path
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 with_custom_parameters
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 with_config
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 get_config_path
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 get_cache_dir
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 load_extensions
0.00% covered (danger)
0.00%
0 / 1
5.01
92.31% covered (success)
92.31%
24 / 26
 dump_container
0.00% covered (danger)
0.00%
0 / 1
2.01
87.50% covered (warning)
87.50%
7 / 8
 create_container
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
8 / 8
 inject_custom_parameters
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 inject_dbal_driver
0.00% covered (danger)
0.00%
0 / 1
5.01
94.12% covered (success)
94.12%
16 / 17
 get_core_parameters
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
 get_env_parameters
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 get_container_filename
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 get_autoload_filename
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 get_environment
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 register_ext_compiler_pass
0.00% covered (danger)
0.00%
0 / 1
4.94
61.11% covered (warning)
61.11%
11 / 18
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\di;
use phpbb\filesystem\filesystem;
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
class container_builder
{
    /**
     * @var string The environment to use.
     */
    protected $environment;
    /**
     * @var string phpBB Root Path
     */
    protected $phpbb_root_path;
    /**
     * @var string php file extension
     */
    protected $php_ext;
    /**
     * The container under construction
     *
     * @var ContainerBuilder
     */
    protected $container;
    /**
     * @var \phpbb\db\driver\driver_interface
     */
    protected $dbal_connection = null;
    /**
     * Indicates whether extensions should be used (default to true).
     *
     * @var bool
     */
    protected $use_extensions = true;
    /**
     * Defines a custom path to find the configuration of the container (default to $this->phpbb_root_path . 'config')
     *
     * @var string
     */
    protected $config_path = null;
    /**
     * Indicates whether the container should be dumped to the filesystem (default to true).
     *
     * If DEBUG_CONTAINER is set this option is ignored and a new container is build.
     *
     * @var bool
     */
    protected $use_cache = true;
    /**
     * Indicates if the container should be compiled automatically (default to true).
     *
     * @var bool
     */
    protected $compile_container = true;
    /**
     * Custom parameters to inject into the container.
     *
     * Default to:
     *     array(
     *         'core.root_path', $this->phpbb_root_path,
     *         'core.php_ext', $this->php_ext,
     * );
     *
     * @var array
     */
    protected $custom_parameters = [];
    /**
     * @var \phpbb\config_php_file
     */
    protected $config_php_file;
    /**
     * @var string
     */
    protected $cache_dir;
    /**
     * @var array
     */
    private $container_extensions;
    /** @var \Exception */
    private $build_exception;
    /**
     * @var array
     */
    private $env_parameters = [];
    /**
     * Constructor
     *
     * @param string $phpbb_root_path Path to the phpbb includes directory.
     * @param string $php_ext php file extension
     */
    public function __construct($phpbb_root_path, $php_ext)
    {
        $this->phpbb_root_path    = $phpbb_root_path;
        $this->php_ext            = $php_ext;
        $this->env_parameters    = $this->get_env_parameters();
        if (isset($this->env_parameters['core.cache_dir']))
        {
            $this->with_cache_dir($this->env_parameters['core.cache_dir']);
        }
    }
    /**
     * Build and return a new Container respecting the current configuration
     *
     * @return \phpbb_cache_container|ContainerBuilder
     */
    public function get_container()
    {
        try
        {
            $build_container = true;
            if ($this->use_cache)
            {
                if ($this->use_extensions)
                {
                    $autoload_cache = new ConfigCache($this->get_autoload_filename(), defined('DEBUG'));
                    if (!$autoload_cache->isFresh())
                    {
                        // autoload cache should be refreshed
                        $this->load_extensions();
                    }
                    require($this->get_autoload_filename());
                }
                $container_filename = $this->get_container_filename();
                $config_cache = new ConfigCache($container_filename, defined('DEBUG'));
                if ($config_cache->isFresh())
                {
                    require($config_cache->getPath());
                    $this->container = new \phpbb_cache_container();
                    $build_container = false;
                }
            }
            if ($build_container)
            {
                $this->container_extensions = [
                    new extension\core($this->get_config_path()),
                ];
                if ($this->use_extensions)
                {
                    $this->load_extensions();
                }
                // Add tables extension after all extensions
                $this->container_extensions[] = new extension\tables();
                // Inject the config
                if ($this->config_php_file)
                {
                    $this->container_extensions[] = new extension\config($this->config_php_file);
                }
                $this->container = $this->create_container($this->container_extensions);
                // Easy collections through tags
                $this->container->addCompilerPass(new pass\collection_pass());
                // Event listeners "phpBB style"
                $this->container->addCompilerPass(new RegisterListenersPass('dispatcher', 'event.listener_listener', 'event.listener'));
                // Event listeners "Symfony style"
                $this->container->addCompilerPass(new RegisterListenersPass('dispatcher'));
                if ($this->use_extensions)
                {
                    $this->register_ext_compiler_pass();
                }
                $filesystem = new filesystem();
                $loader     = new YamlFileLoader($this->container, new FileLocator($filesystem->realpath($this->get_config_path())));
                $loader->load($this->container->getParameter('core.environment') . '/config.yml');
                $this->inject_custom_parameters();
                if ($this->compile_container)
                {
                    $this->container->compile();
                    if ($this->use_cache)
                    {
                        $this->dump_container($config_cache);
                    }
                }
            }
            if ($this->config_php_file)
            {
                $this->container->set('config.php', $this->config_php_file);
            }
            $this->inject_dbal_driver();
            return $this->container;
        }
        catch (\Exception $e)
        {
            // Don't try to recover if we are in the development environment
            if ($this->get_environment() === 'development')
            {
                throw $e;
            }
            if ($this->build_exception === null)
            {
                $this->build_exception = $e;
                return $this
                    ->without_extensions()
                    ->without_cache()
                    ->with_custom_parameters(array_merge($this->custom_parameters, [
                        'container_exception' => $e,
                    ]))
                    ->get_container();
            }
            else
            {
                // Rethrow the original exception if it's still failing
                throw $this->build_exception;
            }
        }
    }
    /**
     * Enable the extensions.
     *
     * @param string $environment The environment to use
     * @return $this
     */
    public function with_environment($environment)
    {
        $this->environment = $environment;
        return $this;
    }
    /**
     * Enable the extensions.
     *
     * @return $this
     */
    public function with_extensions()
    {
        $this->use_extensions = true;
        return $this;
    }
    /**
     * Disable the extensions.
     *
     * @return $this
     */
    public function without_extensions()
    {
        $this->use_extensions = false;
        return $this;
    }
    /**
     * Enable the caching of the container.
     *
     * If DEBUG_CONTAINER is set this option is ignored and a new container is build.
     *
     * @return $this
     */
    public function with_cache()
    {
        $this->use_cache = true;
        return $this;
    }
    /**
     * Disable the caching of the container.
     *
     * @return $this
     */
    public function without_cache()
    {
        $this->use_cache = false;
        return $this;
    }
    /**
     * Set the cache directory.
     *
     * @param string $cache_dir The cache directory.
     * @return $this
     */
    public function with_cache_dir($cache_dir)
    {
        $this->cache_dir = $cache_dir;
        return $this;
    }
    /**
     * Enable the compilation of the container.
     *
     * @return $this
     */
    public function with_compiled_container()
    {
        $this->compile_container = true;
        return $this;
    }
    /**
     * Disable the compilation of the container.
     *
     * @return $this
     */
    public function without_compiled_container()
    {
        $this->compile_container = false;
        return $this;
    }
    /**
     * Set a custom path to find the configuration of the container.
     *
     * @param string $config_path
     * @return $this
     */
    public function with_config_path($config_path)
    {
        $this->config_path = $config_path;
        return $this;
    }
    /**
     * Set custom parameters to inject into the container.
     *
     * @param array $custom_parameters
     * @return $this
     */
    public function with_custom_parameters($custom_parameters)
    {
        $this->custom_parameters = $custom_parameters;
        return $this;
    }
    /**
     * Set custom parameters to inject into the container.
     *
     * @param \phpbb\config_php_file $config_php_file
     * @return $this
     */
    public function with_config(\phpbb\config_php_file $config_php_file)
    {
        $this->config_php_file = $config_php_file;
        return $this;
    }
    /**
     * Returns the path to the container configuration (default: root_path/config)
     *
     * @return string
     */
    protected function get_config_path()
    {
        return $this->config_path ?: $this->phpbb_root_path . 'config';
    }
    /**
     * Returns the path to the cache directory (default: root_path/cache/environment).
     *
     * @return string Path to the cache directory.
     */
    public function get_cache_dir()
    {
        return $this->cache_dir ?: $this->phpbb_root_path . 'cache/' . $this->get_environment() . '/';
    }
    /**
     * Load the enabled extensions.
     */
    protected function load_extensions()
    {
        if ($this->config_php_file !== null)
        {
            // Build an intermediate container to load the ext list from the database
            $container_builder = new container_builder($this->phpbb_root_path, $this->php_ext);
            $ext_container = $container_builder
                ->without_cache()
                ->without_extensions()
                ->with_config($this->config_php_file)
                ->with_config_path($this->get_config_path())
                ->with_environment('production')
                ->without_compiled_container()
                ->get_container()
            ;
            $ext_container->register('cache.driver', '\\phpbb\\cache\\driver\\dummy');
            $ext_container->compile();
            $extensions = $ext_container->get('ext.manager')->all_enabled();
            // Load each extension found
            $autoloaders = '<?php
/**
 * Loads all extensions custom auto-loaders.
 *
 * This file has been auto-generated
 * by phpBB while loading the extensions.
 */
';
            foreach ($extensions as $ext_name => $path)
            {
                $extension_class = '\\' . str_replace('/', '\\', $ext_name) . '\\di\\extension';
                if (!class_exists($extension_class))
                {
                    $extension_class = '\\phpbb\\extension\\di\\extension_base';
                }
                $this->container_extensions[] = new $extension_class($ext_name, $path);
                // Load extension autoloader
                $filename = $path . 'vendor/autoload.php';
                if (file_exists($filename))
                {
                    $autoloaders .= "require('{$filename}');\n";
                }
            }
            $configCache = new ConfigCache($this->get_autoload_filename(), false);
            $configCache->write($autoloaders);
            require($this->get_autoload_filename());
        }
        else
        {
            // To load the extensions we need the database credentials.
            // Automatically disable the extensions if we don't have them.
            $this->use_extensions = false;
        }
    }
    /**
     * Dump the container to the disk.
     *
     * @param ConfigCache $cache The config cache
     */
    protected function dump_container($cache)
    {
        try
        {
            $dumper = new PhpDumper($this->container);
            $proxy_dumper = new ProxyDumper();
            $dumper->setProxyDumper($proxy_dumper);
            $cached_container_dump = $dumper->dump(array(
                'class'      => 'phpbb_cache_container',
                'base_class' => 'Symfony\\Component\\DependencyInjection\\Container',
            ));
            $cache->write($cached_container_dump, $this->container->getResources());
        }
        catch (IOException $e)
        {
            // Don't fail if the cache isn't writeable
        }
    }
    /**
     * Create the ContainerBuilder object
     *
     * @param array $extensions Array of Container extension objects
     * @return ContainerBuilder object
     */
    protected function create_container(array $extensions)
    {
        $container = new ContainerBuilder(new ParameterBag($this->get_core_parameters()));
        $container->setProxyInstantiator(new proxy_instantiator($this->get_cache_dir()));
        $extensions_alias = array();
        foreach ($extensions as $extension)
        {
            $container->registerExtension($extension);
            $extensions_alias[] = $extension->getAlias();
        }
        $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions_alias));
        return $container;
    }
    /**
     * Inject the customs parameters into the container
     */
    protected function inject_custom_parameters()
    {
        foreach ($this->custom_parameters as $key => $value)
        {
            $this->container->setParameter($key, $value);
        }
    }
    /**
     * Inject the dbal connection driver into container
     */
    protected function inject_dbal_driver()
    {
        if (empty($this->config_php_file))
        {
            return;
        }
        $config_data = $this->config_php_file->get_all();
        if (!empty($config_data))
        {
            if ($this->dbal_connection === null)
            {
                $dbal_driver_class = $this->config_php_file->convert_30_dbms_to_31($this->config_php_file->get('dbms'));
                /** @var \phpbb\db\driver\driver_interface $dbal_connection */
                $this->dbal_connection = new $dbal_driver_class();
                $this->dbal_connection->sql_connect(
                    $this->config_php_file->get('dbhost'),
                    $this->config_php_file->get('dbuser'),
                    $this->config_php_file->get('dbpasswd'),
                    $this->config_php_file->get('dbname'),
                    $this->config_php_file->get('dbport'),
                    false,
                    defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK
                );
            }
            $this->container->set('dbal.conn.driver', $this->dbal_connection);
        }
    }
    /**
     * Returns the core parameters.
     *
     * @return array An array of core parameters
     */
    protected function get_core_parameters()
    {
        return array_merge(
            [
                'core.root_path'     => $this->phpbb_root_path,
                'core.php_ext'       => $this->php_ext,
                'core.environment'   => $this->get_environment(),
                'core.debug'         => defined('DEBUG') ? DEBUG : false,
                'core.cache_dir'     => $this->get_cache_dir(),
            ],
            $this->env_parameters
        );
    }
    /**
     * Gets the environment parameters.
     *
     * Only the parameters starting with "PHPBB__" are considered.
     *
     * @return array An array of parameters
     */
    protected function get_env_parameters()
    {
        $parameters = array();
        foreach ($_SERVER as $key => $value)
        {
            if (0 === strpos($key, 'PHPBB__'))
            {
                $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value;
            }
        }
        return $parameters;
    }
    /**
     * Get the filename under which the dumped container will be stored.
     *
     * @return string Path for dumped container
     */
    protected function get_container_filename()
    {
        $container_params = [
            'phpbb_root_path' => $this->phpbb_root_path,
            'use_extensions' => $this->use_extensions,
            'config_path' => $this->config_path,
        ];
        return $this->get_cache_dir() . 'container_' . md5(implode(',', $container_params)) . '.' . $this->php_ext;
    }
    /**
     * Get the filename under which the dumped extensions autoloader will be stored.
     *
     * @return string Path for dumped extensions autoloader
     */
    protected function get_autoload_filename()
    {
        $container_params = [
            'phpbb_root_path' => $this->phpbb_root_path,
            'use_extensions' => $this->use_extensions,
            'config_path' => $this->config_path,
        ];
        return $this->get_cache_dir() . 'autoload_' . md5(implode(',', $container_params)) . '.' . $this->php_ext;
    }
    /**
     * Return the name of the current environment.
     *
     * @return string
     */
    protected function get_environment()
    {
        return $this->environment ?: PHPBB_ENVIRONMENT;
    }
    private function register_ext_compiler_pass()
    {
        $finder = new Finder();
        $finder
            ->name('*_pass.php')
            ->path('di/pass')
            ->files()
            ->ignoreDotFiles(true)
            ->ignoreUnreadableDirs(true)
            ->ignoreVCS(true)
            ->followLinks()
            ->in($this->phpbb_root_path . 'ext')
        ;
        /** @var \SplFileInfo $pass */
        foreach ($finder as $pass)
        {
            $filename = $pass->getPathname();
            $filename = substr($filename, 0, -strlen('.' . $pass->getExtension()));
            $filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename);
            $className = preg_replace('#^.*ext/#', '', $filename);
            $className = '\\' . str_replace('/', '\\', $className);
            if (class_exists($className) && in_array('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface', class_implements($className), true))
            {
                $this->container->addCompilerPass(new $className());
            }
        }
    }
}