API Implement kernel nesting

This commit is contained in:
Damian Mooyman 2017-06-13 17:23:21 +12:00
parent fc7188da7d
commit 2f6336a15b
13 changed files with 265 additions and 76 deletions

View File

@ -188,7 +188,6 @@ class Director implements TemplateGlobalProvider
$cookies = array(),
&$request = null
) {
Config::nest();
Injector::nest();

View File

@ -13,6 +13,7 @@ use SilverStripe\Core\Cache\ManifestCacheFactory;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Config\CoreConfigFactory;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Injector\InjectorLoader;
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ClassManifest;
@ -38,9 +39,10 @@ class AppKernel extends CoreKernel
// Initialise the dependency injector as soon as possible, as it is
// subsequently used by some of the following code
$injectorLoader = InjectorLoader::inst();
$injector = new Injector(array('locator' => SilverStripeServiceConfigurationLocator::class));
$this->setContainer($injector);
Injector::set_inst($injector);
$injectorLoader->pushManifest($injector);
$this->setInjectorLoader($injectorLoader);
// Manifest cache factory
$manifestCacheFactory = $this->buildManifestCacheFactory();

View File

@ -87,7 +87,7 @@ abstract class Config
{
// Unnest unless we would be left at 0 manifests
$loader = ConfigLoader::inst();
if ($loader->countManifests() < 2) {
if ($loader->countManifests() <= 1) {
user_error(
"Unable to unnest root Config, please make sure you don't have mis-matched nest/unnest",
E_USER_WARNING

View File

@ -82,14 +82,31 @@ class ConfigLoader
}
/**
* Nest the current manifest
* Nest the config loader and activates it
*
* @return ConfigCollectionInterface
* @return static
*/
public function nest()
{
$manifest = $this->getManifest()->nest();
$this->pushManifest($manifest);
return $manifest;
// Nest config
$manifest = clone $this->getManifest();
// Create new blank loader with new stack (top level nesting)
$newLoader = new self;
$newLoader->pushManifest($manifest);
// Activate new loader
return $newLoader->activate();
}
/**
* Mark this instance as the current instance
*
* @return $this
*/
public function activate()
{
static::$instance = $this;
return $this;
}
}

View File

@ -5,6 +5,7 @@ namespace SilverStripe\Core;
use InvalidArgumentException;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Injector\InjectorLoader;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\View\ThemeResourceLoader;
@ -14,6 +15,11 @@ use SilverStripe\View\ThemeResourceLoader;
*/
class CoreKernel implements Kernel
{
/**
* @var Kernel
*/
protected $nestedFrom = null;
/**
* @var Injector
*/
@ -39,6 +45,11 @@ class CoreKernel implements Kernel
*/
protected $configLoader = null;
/**
* @var InjectorLoader
*/
protected $injectorLoader = null;
/**
* @var ThemeResourceLoader
*/
@ -54,21 +65,44 @@ class CoreKernel implements Kernel
public function nest()
{
// TODO: Implement nest() method.
// Clone this kernel, nesting config / injector manifest containers
$kernel = clone $this;
$kernel->setConfigLoader($this->configLoader->nest());
$kernel->setInjectorLoader($this->injectorLoader->nest());
return $kernel;
}
public function activate()
{
$this->configLoader->activate();
$this->injectorLoader->activate();
return $this;
}
public function getNestedFrom()
{
return $this->nestedFrom;
}
public function getContainer()
{
return $this->container;
return $this->getInjectorLoader()->getManifest();
}
public function setContainer(Injector $container)
public function setInjectorLoader(InjectorLoader $injectorLoader)
{
$this->container = $container;
$container->registerService($this, Kernel::class);
$this->injectorLoader = $injectorLoader;
$injectorLoader
->getManifest()
->registerService($this, Kernel::class);
return $this;
}
public function getInjectorLoader()
{
return $this->injectorLoader;
}
public function getClassLoader()
{
return $this->classLoader;

View File

@ -106,10 +106,14 @@ class HTTPApplication implements Application
*/
public function execute(callable $callback)
{
return $this->callMiddleware(function () use ($callback) {
// Pre-request boot
$this->getKernel()->boot();
return call_user_func($callback);
});
try {
return $this->callMiddleware(function () use ($callback) {
// Pre-request boot
$this->getKernel()->boot();
return call_user_func($callback);
});
} finally {
$this->getKernel()->shutdown();
}
}
}

View File

@ -236,18 +236,7 @@ class Injector implements ContainerInterface
*/
public static function inst()
{
return self::$instance;
}
/**
* Sets the default global injector instance.
*
* @param Injector $instance
* @return Injector Reference to new active Injector instance
*/
public static function set_inst(Injector $instance)
{
return self::$instance = $instance;
return InjectorLoader::inst()->getManifest();
}
/**
@ -262,11 +251,10 @@ class Injector implements ContainerInterface
*/
public static function nest()
{
$current = self::$instance;
$new = clone $current;
$new->nestedFrom = $current;
return self::set_inst($new);
// Clone current injector and nest
$new = clone self::inst();
InjectorLoader::inst()->pushManifest($new);
return $new;
}
/**
@ -277,15 +265,17 @@ class Injector implements ContainerInterface
*/
public static function unnest()
{
if (self::inst()->nestedFrom) {
self::set_inst(self::inst()->nestedFrom);
} else {
// Unnest unless we would be left at 0 manifests
$loader = InjectorLoader::inst();
if ($loader->countManifests() <= 1) {
user_error(
"Unable to unnest root Injector, please make sure you don't have mis-matched nest/unnest",
E_USER_WARNING
);
} else {
$loader->popManifest();
}
return self::inst();
return static::inst();
}
/**

View File

@ -0,0 +1,109 @@
<?php
namespace SilverStripe\Core\Injector;
use BadMethodCallException;
/**
* Registers chained injectors
*/
class InjectorLoader
{
/**
* @internal
* @var self
*/
private static $instance;
/**
* @var Injector[] map of injector instances
*/
protected $manifests = array();
/**
* @return self
*/
public static function inst()
{
return self::$instance ? self::$instance : self::$instance = new self();
}
/**
* Returns the currently active class manifest instance that is used for
* loading classes.
*
* @return Injector
*/
public function getManifest()
{
if (empty($this->manifests)) {
throw new BadMethodCallException("No injector manifests available");
}
return $this->manifests[count($this->manifests) - 1];
}
/**
* Returns true if this class loader has a manifest.
*
* @return bool
*/
public function hasManifest()
{
return (bool)$this->manifests;
}
/**
* Pushes a class manifest instance onto the top of the stack.
*
* @param Injector $manifest
*/
public function pushManifest(Injector $manifest)
{
$this->manifests[] = $manifest;
}
/**
* @return Injector
*/
public function popManifest()
{
return array_pop($this->manifests);
}
/**
* Check number of manifests
*
* @return int
*/
public function countManifests()
{
return count($this->manifests);
}
/**
* Nest the config loader
*
* @return static
*/
public function nest()
{
// Nest config
$manifest = $this->getManifest()->nest();
// Create new blank loader with new stack (top level nesting)
$newLoader = new self;
$newLoader->pushManifest($manifest);
// Activate new loader
$newLoader->activate();
return $newLoader;
}
/**
* Mark this instance as the current instance
*/
public function activate()
{
static::$instance = $this;
}
}

View File

@ -4,6 +4,7 @@ namespace SilverStripe\Core;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Injector\InjectorLoader;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\View\ThemeResourceLoader;
@ -42,23 +43,22 @@ interface Kernel
/**
* Nests this kernel, all components, and returns the nested value.
*
* @return static
* @return Kernel
*/
public function nest();
/**
* Ensures this kernel is the active kernel after or during nesting
*
* @return $this
*/
public function activate();
/**
* @return Injector
*/
public function getContainer();
/**
* Sets injector
*
* @param Injector $container
* @return $this
*/
public function setContainer(Injector $container);
/**
* @return ClassLoader
*/
@ -70,6 +70,19 @@ interface Kernel
*/
public function setClassLoader(ClassLoader $classLoader);
/**
* Get loader for injector instance
*
* @return InjectorLoader
*/
public function getInjectorLoader();
/**
* @param InjectorLoader $injectorLoader
* @return $this
*/
public function setInjectorLoader(InjectorLoader $injectorLoader);
/**
* @return ModuleLoader
*/

View File

@ -35,4 +35,10 @@ class TestKernel extends AppKernel
{
return true;
}
protected function bootErrorHandling()
{
// Leave phpunit to capture errors
restore_error_handler();
}
}

View File

@ -37,7 +37,9 @@ class ExtensionTestState implements TestState
public function setUpOnce($class)
{
$isAltered = false;
// May be altered by another class
$isAltered = $this->extensionsToReapply || $this->extensionsToRemove;
/** @var string|SapphireTest $class */
/** @var string|DataObject $dataClass */
// Remove any illegal extensions that are present
@ -84,18 +86,19 @@ class ExtensionTestState implements TestState
}
}
// If we have made changes to the extensions present, then migrate the database schema.
if ($isAltered || $this->extensionsToReapply || $this->extensionsToRemove || $class::getExtraDataObjects()) {
// clear singletons, they're caching old extension info
// which is used in DatabaseAdmin->doBuild()
Injector::inst()->unregisterObjects(DataObject::class);
// If we have altered the schema, but SapphireTest::setUpBeforeClass() would not otherwise
// reset the schema (if there were extra objects) then force a reset
if ($isAltered && empty($class::getExtraDataObjects())) {
DataObject::reset();
if (!SapphireTest::using_temp_db()) {
SapphireTest::create_temp_db();
}
SapphireTest::resetDBSchema(true);
$class::resetDBSchema(true);
}
// clear singletons, they're caching old extension info
// which is used in DatabaseAdmin->doBuild()
Injector::inst()->unregisterObjects(DataObject::class);
}
public function tearDownOnce($class)

View File

@ -203,11 +203,11 @@ class SapphireTest extends PHPUnit_Framework_TestCase
*/
protected function setUp()
{
// Reset state
self::$kernel->reset();
//nest config and injector for each test so they are effectively sandboxed per test
Config::nest();
Injector::nest();
// Nest
self::$kernel->nest();
// Call state helpers
static::$state->setUp($this);
@ -299,12 +299,20 @@ class SapphireTest extends PHPUnit_Framework_TestCase
// Reset kernel
static::$kernel->reset();
//nest config and injector for each suite so they are effectively sandboxed
Config::nest();
Injector::nest();
// Nest kernel
static::$kernel->nest();
// Call state helpers
static::$state->setUpOnce(static::class);
// Build DB if we have objects
if (static::getExtraDataObjects()) {
DataObject::reset();
if (!self::using_temp_db()) {
self::create_temp_db();
}
static::resetDBSchema(true);
}
}
/**
@ -322,14 +330,14 @@ class SapphireTest extends PHPUnit_Framework_TestCase
// Call state helpers
static::$state->tearDownOnce(static::class);
//unnest injector / config now that the test suite is over
// this will reset all the extensions on the object too (see setUpBeforeClass)
Injector::unnest();
Config::unnest();
static::resetDBSchema();
// Unnest
static::$kernel->activate();
// Reset PHP state
static::$kernel->reset();
// Reset DB schema
static::resetDBSchema();
}
/**
@ -485,11 +493,10 @@ class SapphireTest extends PHPUnit_Framework_TestCase
}
// Call state helpers
static::$state->setUp($this);
static::$state->tearDown($this);
//unnest injector / config now that tests are over
Injector::unnest();
Config::unnest();
// Unnest
self::$kernel->activate();
// Reset state
self::$kernel->reset();
@ -992,6 +999,11 @@ class SapphireTest extends PHPUnit_Framework_TestCase
}
}
/**
* Create temp DB without creating extra objects
*
* @return string
*/
public static function create_temp_db()
{
// Disable PHPUnit error handling

View File

@ -53,10 +53,10 @@ class TestSession
public function __construct()
{
$this->session = Injector::inst()->create('SilverStripe\\Control\\Session', array());
$this->cookies = Injector::inst()->create('SilverStripe\\Control\\Cookie_Backend');
$this->session = Injector::inst()->create(Session::class, array());
$this->cookies = Injector::inst()->create(Cookie_Backend::class);
$this->controller = new Controller();
$this->controller->setSession($this->session);
// @todo - Ensure $this->session is set on all requests
$this->controller->pushCurrent();
}