Commit 40787e18 authored by totten's avatar totten

CRM-13244 - Civi\Core\Container - Allow hooks to modify container. Cache it.

Conflicts:
	composer.json
parent 1a574627
......@@ -1894,6 +1894,31 @@ abstract class CRM_Utils_Hook {
);
}
/**
* Modify the CiviCRM container - add new services, parameters, extensions, etc.
*
* @code
* use Symfony\Component\Config\Resource\FileResource;
* use Symfony\Component\DependencyInjection\Definition;
*
* function mymodule_civicrm_container($container) {
* $container->addResource(new FileResource(__FILE__));
* $container->setDefinition('mysvc', new Definition('My\Class', array()));
* }
* @endcode
*
* Tip: The container configuration will be compiled/cached. The default cache
* behavior is aggressive. When you first implement the hook, be sure to
* flush the cache. Additionally, you should relax caching during development.
* In `civicrm.settings.php`, set define('CIVICRM_CONTAINER_CACHE', 'auto').
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @see http://symfony.com/doc/current/components/dependency_injection/index.html
*/
public static function container(\Symfony\Component\DependencyInjection\ContainerBuilder $container) {
self::singleton()->invoke(1, $container, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, 'civicrm_container');
}
/**
* @param array <CRM_Core_FileSearchInterface> $fileSearches
* @return mixed
......
......@@ -1771,7 +1771,7 @@ class CRM_Utils_System {
* @return bool
*/
public static function isInUpgradeMode() {
$args = explode('/', $_GET['q']);
$args = explode('/', CRM_Utils_Array::value('q', $_GET));
$upgradeInProcess = CRM_Core_Session::singleton()->get('isUpgradePending');
if ((isset($args[1]) && $args[1] == 'upgrade') || $upgradeInProcess) {
return TRUE;
......
......@@ -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;
......@@ -37,20 +41,85 @@ class Container {
public static function singleton($reset = FALSE) {
if ($reset || self::$singleton === NULL) {
$c = new self();
self::$singleton = $c->createContainer();
self::$singleton = $c->loadContainer();
}
return self::$singleton;
}
/**
* 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 = md5(implode(\CRM_Core_DAO::VALUE_SEPARATOR, array(
defined('CIVICRM_DOMAIN_ID') ? CIVICRM_DOMAIN_ID : 1, // e.g. one database, multi URL
parse_url(CIVICRM_DSN, PHP_URL_PATH), // e.g. one codebase, multi database
\CRM_Utils_Array::value('SCRIPT_FILENAME', $_SERVER, ''), // e.g. CMS vs extern vs installer
\CRM_Utils_Array::value('HTTP_HOST', $_SERVER, ''), // e.g. name-based vhosts
\CRM_Utils_Array::value('SERVER_PORT', $_SERVER, ''), // e.g. port-based vhosts
// Depending on deployment arch, these signals *could* be redundant, but who cares?
)));
$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)) {
......@@ -69,36 +138,36 @@ class Container {
// }
$container->setDefinition('lockManager', new Definition(
'\Civi\Core\Lock\LockManager',
'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');
......@@ -135,6 +204,8 @@ class Container {
->setFactoryClass($class)->setFactoryMethod('singleton');
}
\CRM_Utils_Hook::container($container);
return $container;
}
......@@ -146,10 +217,11 @@ class Container {
}
/**
* @param ContainerInterface $container
* @return \Symfony\Component\EventDispatcher\EventDispatcher
*/
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'));
......
This diff is collapsed.
<?php
define('CIVICRM_TEST', 1);
define('CIVICRM_CONTAINER_CACHE', 'auto');
// This file is loaded on all systems running tests. To override settings on
// your local system, please create "civicrm.settings.local.php" and put
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment