X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=Civi%2FCore%2FContainer.php;h=fa7fe74994879e346d770ab0aec30db7a323b137;hb=50a2375554b2a33cacebbe3822ff3c84f60fb9e9;hp=627f1a18c90d1c7c25e1c0e83cfeb8204f2c1a2d;hpb=839ce23ccd4a801e330876d92cccff7a29c666d5;p=civicrm-core.git diff --git a/Civi/Core/Container.php b/Civi/Core/Container.php index 627f1a18c9..fa7fe74994 100644 --- a/Civi/Core/Container.php +++ b/Civi/Core/Container.php @@ -9,10 +9,14 @@ use Doctrine\Common\Cache\FilesystemCache; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Tools\Setup; +use Symfony\Component\Config\ConfigCache; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; // TODO use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; @@ -24,33 +28,84 @@ class Container { const SELF = 'civi_container_factory'; - /** - * @var ContainerBuilder - */ - private static $singleton; - /** * @param bool $reset * Whether to forcibly rebuild the entire container. * @return \Symfony\Component\DependencyInjection\TaggedContainerInterface */ public static function singleton($reset = FALSE) { - if ($reset || self::$singleton === NULL) { - $c = new self(); - self::$singleton = $c->createContainer(); + if ($reset || !isset(\Civi::$statics[__CLASS__]['container'])) { + self::boot(TRUE); } - return self::$singleton; + return \Civi::$statics[__CLASS__]['container']; } /** + * Find a cached container definition or construct a new one. + * + * There are many weird contexts in which Civi initializes (eg different + * variations of multitenancy and different permutations of CMS/CRM bootstrap), + * and hook_container may fire a bit differently in each context. To mitigate + * risk of leaks between environments, we compute a unique envID + * (md5(DB_NAME, HTTP_HOST, SCRIPT_FILENAME, etc)) and use separate caches for + * each (eg "templates_c/CachedCiviContainer.$ENVID.php"). + * + * Constants: + * - CIVICRM_CONTAINER_CACHE -- 'always' [default], 'never', 'auto' + * - CIVICRM_DSN + * - CIVICRM_DOMAIN_ID + * - CIVICRM_TEMPLATE_COMPILEDIR + * + * @return ContainerInterface + */ + public function loadContainer() { + // Note: The container's raison d'etre is to manage construction of other + // services. Consequently, we assume a minimal service available -- the classloader + // has been setup, and civicrm.settings.php is loaded, but nothing else works. + + $cacheMode = defined('CIVICRM_CONTAINER_CACHE') ? CIVICRM_CONTAINER_CACHE : 'always'; + + // In pre-installation environments, don't bother with caching. + if (!defined('CIVICRM_TEMPLATE_COMPILEDIR') || !defined('CIVICRM_DSN') || $cacheMode === 'never' || \CRM_Utils_System::isInUpgradeMode()) { + return $this->createContainer(); + } + + $envId = \CRM_Core_Config_Runtime::getId(); + $file = CIVICRM_TEMPLATE_COMPILEDIR . "/CachedCiviContainer.{$envId}.php"; + $containerConfigCache = new ConfigCache($file, $cacheMode === 'auto'); + if (!$containerConfigCache->isFresh()) { + $containerBuilder = $this->createContainer(); + $containerBuilder->compile(); + $dumper = new PhpDumper($containerBuilder); + $containerConfigCache->write( + $dumper->dump(array('class' => 'CachedCiviContainer')), + $containerBuilder->getResources() + ); + } + + require_once $file; + $c = new \CachedCiviContainer(); + $c->set('service_container', $c); + return $c; + } + + /** + * Construct a new container. + * * @var ContainerBuilder * @return \Symfony\Component\DependencyInjection\ContainerBuilder */ public function createContainer() { $civicrm_base_path = dirname(dirname(__DIR__)); $container = new ContainerBuilder(); + $container->addCompilerPass(new RegisterListenersPass('dispatcher')); + $container->addObjectResource($this); $container->setParameter('civicrm_base_path', $civicrm_base_path); - $container->set(self::SELF, $this); + //$container->set(self::SELF, $this); + $container->setDefinition(self::SELF, new Definition( + 'Civi\Core\Container', + array() + )); // TODO Move configuration to an external file; define caching structure // if (empty($configDirectories)) { @@ -68,45 +123,67 @@ class Container { // } // } - $container->setDefinition('lockManager', new Definition( - '\Civi\Core\Lock\LockManager', - array() - )) - ->setFactoryService(self::SELF)->setFactoryMethod('createLockManager'); - $container->setDefinition('angular', new Definition( - '\Civi\Angular\Manager', + 'Civi\Angular\Manager', array() )) ->setFactoryService(self::SELF)->setFactoryMethod('createAngularManager'); $container->setDefinition('dispatcher', new Definition( - '\Symfony\Component\EventDispatcher\EventDispatcher', - array() + 'Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher', + array(new Reference('service_container')) )) ->setFactoryService(self::SELF)->setFactoryMethod('createEventDispatcher'); $container->setDefinition('magic_function_provider', new Definition( - '\Civi\API\Provider\MagicFunctionProvider', + 'Civi\API\Provider\MagicFunctionProvider', array() )); $container->setDefinition('civi_api_kernel', new Definition( - '\Civi\API\Kernel', + 'Civi\API\Kernel', array(new Reference('dispatcher'), new Reference('magic_function_provider')) )) ->setFactoryService(self::SELF)->setFactoryMethod('createApiKernel'); $container->setDefinition('cxn_reg_client', new Definition( - '\Civi\Cxn\Rpc\RegistrationClient', + 'Civi\Cxn\Rpc\RegistrationClient', array() )) ->setFactoryClass('CRM_Cxn_BAO_Cxn')->setFactoryMethod('createRegistrationClient'); + $container->setDefinition('psr_log', new Definition('CRM_Core_Error_Log', array())); + + foreach (array('js_strings', 'community_messages') as $cacheName) { + $container->setDefinition("cache.{$cacheName}", new Definition( + 'CRM_Utils_Cache_Interface', + array( + array( + 'name' => $cacheName, + 'type' => array('*memory*', 'SqlGroup', 'ArrayCache'), + ), + ) + ))->setFactoryClass('CRM_Utils_Cache')->setFactoryMethod('create'); + } + + $container->setDefinition('pear_mail', new Definition('Mail')) + ->setFactoryClass('CRM_Utils_Mail')->setFactoryMethod('createMailer'); + + if (empty(\Civi::$statics[__CLASS__]['boot'])) { + throw new \RuntimeException("Cannot initialize container. Boot services are undefined."); + } + foreach (\Civi::$statics[__CLASS__]['boot'] as $bootService => $def) { + $container->setDefinition($bootService, new Definition($def['class'], array($bootService))) + ->setFactoryClass(__CLASS__) + ->setFactoryMethod('getBootService'); + } + // Expose legacy singletons as services in the container. $singletons = array( 'resources' => 'CRM_Core_Resources', 'httpClient' => 'CRM_Utils_HttpClient', + 'cache.default' => 'CRM_Utils_Cache', + 'i18n' => 'CRM_Core_I18n', // Maybe? 'config' => 'CRM_Core_Config', // Maybe? 'smarty' => 'CRM_Core_Smarty', ); @@ -117,6 +194,18 @@ class Container { ->setFactoryClass($class)->setFactoryMethod('singleton'); } + $container->setDefinition('civi_token_compat', new Definition( + 'Civi\Token\TokenCompatSubscriber', + array() + ))->addTag('kernel.event_subscriber'); + + $container->setDefinition('actionscheduletmp', new Definition( + 'CRM_Core_ActionScheduleTmp', + array() + ))->addTag('kernel.event_subscriber'); + + \CRM_Utils_Hook::container($container); + return $container; } @@ -128,10 +217,11 @@ class Container { } /** - * @return \Symfony\Component\EventDispatcher\EventDispatcher + * @param ContainerInterface $container + * @return \Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher */ - public function createEventDispatcher() { - $dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher(); + public function createEventDispatcher($container) { + $dispatcher = new ContainerAwareEventDispatcher($container); $dispatcher->addListener('hook_civicrm_post::Activity', array('\Civi\CCase\Events', 'fireCaseChange')); $dispatcher->addListener('hook_civicrm_post::Case', array('\Civi\CCase\Events', 'fireCaseChange')); $dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\Events', 'delegateToXmlListeners')); @@ -149,7 +239,7 @@ class Container { /** * @return LockManager */ - public function createLockManager() { + public static function createLockManager() { // Ideally, downstream implementers could override any definitions in // the container. For now, we'll make-do with some define()s. $lm = new LockManager(); @@ -218,4 +308,75 @@ class Container { return $kernel; } + /** + * Get a list of boot services. + * + * These are services which must be setup *before* the container can operate. + * + * @param bool $loadFromDB + * @throws \CRM_Core_Exception + */ + public static function boot($loadFromDB) { + $bootServices = array(); + \Civi::$statics[__CLASS__]['boot'] = &$bootServices; + + $bootServices['runtime'] = array( + 'class' => 'CRM_Core_Config_Runtime', + 'obj' => ($runtime = new \CRM_Core_Config_Runtime()), + ); + $runtime->initialize($loadFromDB); + + if ($loadFromDB && $runtime->dsn) { + \CRM_Core_DAO::init($runtime->dsn); + } + + $bootServices['paths'] = array( + 'class' => 'Civi\Core\Paths', + 'obj' => new \Civi\Core\Paths(), + ); + + $class = $runtime->userFrameworkClass; + $bootServices['userSystem'] = array( + 'class' => 'CRM_Utils_Cache_Interface', + 'obj' => ($userSystem = new $class()), + ); + $userSystem->initialize(); + + $userPermissionClass = 'CRM_Core_Permission_' . $runtime->userFramework; + $bootServices['userPermissionClass'] = array( + // Ugh, silly name. + 'class' => 'CRM_Core_Permission_Base', + 'obj' => new $userPermissionClass(), + ); + + $bootServices['cache.settings'] = array( + 'class' => 'CRM_Utils_Cache_Interface', + 'obj' => \CRM_Utils_Cache::create(array( + 'name' => 'settings', + 'type' => array('*memory*', 'SqlGroup', 'ArrayCache'), + )), + ); + + $bootServices['settings_manager'] = array( + 'class' => 'Civi\Core\SettingsManager', + 'obj' => new \Civi\Core\SettingsManager($bootServices['cache.settings']['obj']), + ); + + $bootServices['lockManager'] = array( + 'class' => 'Civi\Core\Lock\LockManager', + 'obj' => self::createLockManager(), + ); + + if ($loadFromDB && $runtime->dsn) { + \CRM_Extension_System::singleton(TRUE); + + $c = new self(); + \Civi::$statics[__CLASS__]['container'] = $c->loadContainer(); + } + } + + public static function getBootService($name) { + return \Civi::$statics[__CLASS__]['boot'][$name]['obj']; + } + }