Skip to content

Commit

Permalink
API Refactor SapphireTest state management into SapphireTestState
Browse files Browse the repository at this point in the history
API Remove Injector::unregisterAllObjects()
API Remove FakeController
  • Loading branch information
Damian Mooyman committed Jun 20, 2017
1 parent f26ae75 commit c66d433
Show file tree
Hide file tree
Showing 23 changed files with 493 additions and 319 deletions.
9 changes: 9 additions & 0 deletions _config/tests.yml
@@ -0,0 +1,9 @@
---
Name: sapphiretest
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Dev\SapphireTestState:
properties:
States:
flushable: %$SilverStripe\Dev\FlushableTestState
requirements: %$SilverStripe\View\Dev\RequirementsTestState
11 changes: 5 additions & 6 deletions cli-script.php
Expand Up @@ -7,15 +7,14 @@
use SilverStripe\Control\HTTPRequest;

require __DIR__ . '/src/includes/cli.php';
$_SERVER['SCRIPT_FILENAME'] = __FILE__;
chdir(__DIR__);


require __DIR__ . '/src/includes/autoload.php';

// Default application
// Build request and detect flush
$request = HTTPRequest::createFromEnvironment();
$kernel = new AppKernel();
$flush = $request->getVar('flush') || strpos($request->getURL(), 'dev/build') === 0;

// Default application
$kernel = new AppKernel($flush);
$app = new HTTPApplication($kernel);
$app->addMiddleware(new OutputMiddleware());
$app->handle($request);
2 changes: 2 additions & 0 deletions docs/en/04_Changelogs/4.0.0.md
Expand Up @@ -1340,6 +1340,8 @@ After (`mysite/_config/config.yml`):
#### <a name="overview-general-removed"></a>General and Core Removed API

* `CMSMain::buildbrokenlinks()` action is removed.
* `Injector::unregisterAllObjects()` has been removed. Use `unregisterObjects` to unregister
groups of objects limited by type instead.
* `SS_Log` class has been removed. Use `Injector::inst()->get(LoggerInterface::class)` instead.
* Removed `CMSBatchAction_Delete`
* Removed `CMSBatchAction_DeleteFromLive`
Expand Down
7 changes: 5 additions & 2 deletions main.php
Expand Up @@ -8,9 +8,12 @@

require __DIR__ . '/src/includes/autoload.php';

// Default application
// Build request and detect flush
$request = HTTPRequest::createFromEnvironment();
$kernel = new AppKernel();
$flush = $request->getVar('flush') || strpos($request->getURL(), 'dev/build') === 0;

// Default application
$kernel = new AppKernel($flush);
$app = new HTTPApplication($kernel);
$app->addMiddleware(new OutputMiddleware());
$app->addMiddleware(new ErrorControlChainMiddleware($app, $request));
Expand Down
33 changes: 21 additions & 12 deletions src/Core/AppKernel.php
Expand Up @@ -27,8 +27,15 @@

class AppKernel extends CoreKernel
{
public function __construct()
/**
* @var bool
*/
protected $flush = false;

public function __construct($flush = false)
{
$this->flush = $flush;

// Initialise the dependency injector as soon as possible, as it is
// subsequently used by some of the following code
$injector = new Injector(array('locator' => SilverStripeServiceConfigurationLocator::class));
Expand Down Expand Up @@ -355,28 +362,30 @@ protected function buildManifestCacheFactory()
]);
}

/**
* @return bool
*/
protected function getIncludeTests()
{
return false;
}

/**
* Boot all manifests
*/
protected function bootManifests()
{
// Regenerate the manifest if ?flush is set, or if the database is being built.
// The coupling is a hack, but it removes an annoying bug where new classes
// referenced in _config.php files can be referenced during the build process.
$flush = isset($_GET['flush']) ||
trim($_GET['url'], '/') === trim(BASE_URL . '/dev/build', '/');

// Setup autoloader
$this->getClassLoader()->init(false, $flush);
$this->getClassLoader()->init($this->getIncludeTests(), $this->flush);

// Find modules
$this->getModuleLoader()->init(false, $flush);
$this->getModuleLoader()->init($this->getIncludeTests(), $this->flush);

// Flush config
if ($flush) {
if ($this->flush) {
$config = $this->getConfigLoader()->getManifest();
if ($config instanceof CachedConfigCollection) {
$config->setFlush($flush);
$config->setFlush(true);
}
}

Expand All @@ -386,7 +395,7 @@ protected function bootManifests()
// Find default templates
$defaultSet = $this->getThemeResourceLoader()->getSet('$default');
if ($defaultSet instanceof ThemeManifest) {
$defaultSet->init(false, $flush);
$defaultSet->init($this->getIncludeTests(), $this->flush);
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/Core/Extensible.php
Expand Up @@ -6,6 +6,7 @@
use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\DataObject;
use SilverStripe\View\ViewableData;

/**
Expand Down Expand Up @@ -271,8 +272,8 @@ public static function remove_extension($extension)
}
Config::modify()->set($class, 'extensions', $config);

// unset singletons to avoid side-effects
Injector::inst()->unregisterAllObjects();
// Unset singletons
Injector::inst()->unregisterObjects($class);

// unset some caches
$subclasses = ClassInfo::subclassesFor($class);
Expand Down
61 changes: 40 additions & 21 deletions src/Core/Injector/Injector.php
Expand Up @@ -2,14 +2,15 @@

namespace SilverStripe\Core\Injector;

use ArrayObject;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionMethod;
use ReflectionObject;
use ReflectionProperty;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use ReflectionProperty;
use ArrayObject;
use ReflectionObject;
use ReflectionMethod;
use Psr\Container\ContainerInterface;
use SilverStripe\Dev\Deprecation;

/**
Expand Down Expand Up @@ -231,16 +232,10 @@ public function __construct($config = null)
protected $nestedFrom = null;

/**
* If a user wants to use the injector as a static reference
*
* @param array $config
* @return Injector
*/
public static function inst($config = null)
public static function inst()
{
if (!self::$instance) {
self::$instance = new Injector($config);
}
return self::$instance;
}

Expand Down Expand Up @@ -404,7 +399,7 @@ public function load($config = array())

// make sure the class is set...
if (empty($class)) {
throw new \InvalidArgumentException('Missing spec class');
throw new InvalidArgumentException('Missing spec class');
}
$spec['class'] = $class;

Expand Down Expand Up @@ -651,21 +646,21 @@ public function inject($object, $asType = null)

// Format validation
if (!is_array($method) || !isset($method[0]) || isset($method[2])) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
"'calls' entries in service definition should be 1 or 2 element arrays."
);
}
if (!is_string($method[0])) {
throw new \InvalidArgumentException("1st element of a 'calls' entry should be a string");
throw new InvalidArgumentException("1st element of a 'calls' entry should be a string");
}
if (isset($method[1]) && !is_array($method[1])) {
throw new \InvalidArgumentException("2nd element of a 'calls' entry should an arguments array");
throw new InvalidArgumentException("2nd element of a 'calls' entry should an arguments array");
}

// Check that the method exists and is callable
$objectMethod = array($object, $method[0]);
if (!is_callable($objectMethod)) {
throw new \InvalidArgumentException("'$method[0]' in 'calls' entry is not a public method");
throw new InvalidArgumentException("'$method[0]' in 'calls' entry is not a public method");
}

// Call it
Expand Down Expand Up @@ -847,35 +842,59 @@ public function getServiceName($name)
* @param object $service The object to register
* @param string $replace The name of the object to replace (if different to the
* class name of the object to register)
* @return $this
*/
public function registerService($service, $replace = null)
{
$registerAt = get_class($service);
if ($replace != null) {
if ($replace !== null) {
$registerAt = $replace;
}

$this->specs[$registerAt] = array('class' => get_class($service));
$this->serviceCache[$registerAt] = $service;
return $this;
}

/**
* Removes a named object from the cached list of objects managed
* by the inject
*
* @param string $name The name to unregister
* @return $this
*/
public function unregisterNamedObject($name)
{
unset($this->serviceCache[$name]);
return $this;
}

/**
* Clear out all objects that are managed by the injetor.
* Clear out objects of one or more types that are managed by the injetor.
*
* @param array|string $types Base class of object (not service name) to remove
* @return $this
*/
public function unregisterAllObjects()
public function unregisterObjects($types)
{
$this->serviceCache = array('Injector' => $this);
if (!is_array($types)) {
$types = [ $types ];
}

// Filter all objects
foreach ($this->serviceCache as $key => $object) {
foreach ($types as $filterClass) {
// Prevent destructive flushing
if (strcasecmp($filterClass, 'object') === 0) {
throw new InvalidArgumentException("Global unregistration is not allowed");
}
if ($object instanceof $filterClass) {
unset($this->serviceCache[$key]);
break;
}
}
}
return $this;
}

/**
Expand Down
30 changes: 30 additions & 0 deletions src/Core/TestKernel.php
@@ -0,0 +1,30 @@
<?php

namespace SilverStripe\Core;

/**
* Kernel for running unit tests
*/
class TestKernel extends AppKernel
{
public function __construct($flush = true)
{
parent::__construct($flush);
$this->setEnvironment(self::DEV);
}

/**
* Reset kernel between tests.
* Note: this avoids resetting services (See TestState for service specific reset)
*/
public function reset()
{
$this->setEnvironment(self::DEV);
$this->bootPHP();
}

protected function getIncludeTests()
{
return true;
}
}
49 changes: 49 additions & 0 deletions src/Dev/FlushableTestState.php
@@ -0,0 +1,49 @@
<?php

namespace SilverStripe\Dev;

use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Flushable;
use SilverStripe\Core\Resettable;

/**
* Clears flushable / resettable objects
*/
class FlushableTestState implements TestState
{
/**
* @var bool
*/
protected $flushed = false;

public function setUp(SapphireTest $test)
{
// Reset all resettables
/** @var Resettable $resettable */
foreach (ClassInfo::implementorsOf(Resettable::class) as $resettable) {
$resettable::reset();
}
}

public function tearDown(SapphireTest $test)
{
}

public function setUpOnce($class)
{
if ($this->flushed) {
return;
}
$this->flushed = true;

// Flush all flushable records
/** @var Flushable $class */
foreach (ClassInfo::implementorsOf(Flushable::class) as $class) {
$class::flush();
}
}

public function tearDownOnce($class)
{
}
}

0 comments on commit c66d433

Please sign in to comment.