* @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 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; use phpbb\filesystem\helper as filesystem_helper; 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; /** * 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; } /** * Build and return a new Container respecting the current configuration * * @return \phpbb_cache_container|ContainerBuilder */ public function get_container() { try { $container_filename = $this->get_container_filename(); $config_cache = new ConfigCache($container_filename, defined('DEBUG')); if ($this->use_cache && $config_cache->isFresh()) { 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()); } require($config_cache->getPath()); $this->container = new \phpbb_cache_container(); } else { $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(); } $loader = new YamlFileLoader($this->container, new FileLocator(filesystem_helper::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->compile_container && $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. */ protected 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(); $config = $ext_container->get('config'); if (@is_file($this->phpbb_root_path . $config['exts_composer_vendor_dir'] . '/autoload.php')) { require_once($this->phpbb_root_path . $config['exts_composer_vendor_dir'] . '/autoload.php'); } $extensions = $ext_container->get('ext.manager')->all_enabled(); // Load each extension found $autoloaders = ' $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( array( '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->get_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()); } } } }