-
-
Save tractorcow/2bb76d12074b885b9b2923dafa07ae88 to your computer and use it in GitHub Desktop.
app-rfc-v4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
Suggested AC: | |
- A kernel object exists, which can be easily overridden in user code, to control the boot process of any | |
silverstripe application. | |
- An application object exists, which can be easily overridden in user code, to control the execution of any | |
request in a silverstripe application. | |
- All components which make up the state of the Kernel object are available via Injector service, even | |
if these components are initialised pre-config (including the Kernel itself). | |
- Kernel has no static members. | |
- The Kernel object can be nested (and un-nested), which internally nests all core services, as well as injector / config. | |
However, this nesting is performed per-instance and not via static accessors as with config / injector. | |
- Config::inst() and Injector::inst() functionality remains unaltered. | |
- HTTPRequest is not a part of kernel, nor are any request-specific services. | |
- Session::inst() is removed and replaced with of the HTTPRequest::getSession() | |
- Development experience should not be greatly adversely affected for classes which use the main core traits. | |
Critical AC: | |
- If a non-current instance of Injector / Config is ever invoked, it must raise a logic error to enforce application consistency. | |
*/ | |
namespace SilverStripe\Core; | |
// Begin: main.php | |
$kernel = new AppKernel(); | |
$request = HTTPRequest::createFromEnvironment(); | |
$app = new HTTPApplication($kernel); | |
$app->handle($request); | |
// End: of main.php | |
// Begin:: cli-script.php | |
$kernel = new AppKernel(); | |
$app = new CLIApplication($kernel); | |
$app->handle(); | |
// based on https://github.com/symfony/symfony/blob/3.4/src/Symfony/Component/HttpKernel/KernelInterface.php | |
interface Kernel | |
{ | |
public function boot(); | |
/** | |
* @return Injector | |
*/ | |
public function getContainer(); | |
public function setContainer(Injector $injector); | |
/** | |
* @return string | |
*/ | |
public function getEnvironment(); | |
/** | |
* @return static | |
*/ | |
public function nest(); | |
} | |
/** | |
* Basic container for Kernel. Can be used or subclassed by user-code to set a custom Kernel | |
*/ | |
class CoreKernel implements Kernel | |
{ | |
// basic getters / setters | |
protected $injector = null; | |
protected $environment = 'live'; | |
public function boot() | |
{ | |
// Does heavy work, e.g. start enumerating all classes, ensure config is cached, module list is enumerated | |
} | |
public function getContainer() | |
{ | |
return $this->injector; | |
} | |
public function setContainer(Injector $injector) | |
{ | |
// Ensure injector / kernel are mutually consistent | |
$this->injector = $injector; | |
$injector->registerService($this, Kernel::class); | |
return $this; | |
} | |
public function getEnvironment() | |
{ | |
return $this->environment; | |
} | |
public function nest() | |
{ | |
$new = clone $this; | |
// Note: nest config / injector here automatically. | |
// Further nesting of config / injector can be done within this appstate level | |
// Implementation here omitted for brevity | |
// TODO: Nest config / injector | |
$new->nestedFrom = $this; | |
return $new; | |
} | |
public function unnest() | |
{ | |
// TODO: Unnest config / injector | |
return $this->nestedFrom; | |
} | |
} | |
/** | |
* SilverStripe specific code. | |
* Essentially the factory that replaces Core.php | |
*/ | |
class AppKernel extends CoreKernel | |
{ | |
public function __construct() | |
{ | |
// Initialises all services from core, but does not trigger any of them to boot | |
// See CoreKernel::boot(); | |
// Triggers mutual registration | |
$this->setContainer(new Injector()); | |
} | |
} | |
/** | |
* Core application interface. Does nothing (intentionally doesn't declare execution entry point, | |
* as each implementation will differ). | |
*/ | |
interface Application | |
{ | |
// Empty | |
} | |
/** | |
* App for HTTP requests | |
*/ | |
class HTTPApplication implements Application | |
{ | |
protected $kernel = null; | |
public function __construct($kernel) | |
{ | |
$this->kernel = $kernel; | |
} | |
public function handle(HTTPRequest $request) | |
{ | |
// Copy errorcontrailchain handling logic out of main.php | |
// Note: Finally have a way to create an errorcontrolchain-less application :) | |
$chain = new ErrorControlChain(); | |
$chain->then(function() use ($request) { | |
// Perform actual booting of kernel (heavy lifting) inside errorcontrolchain | |
$this->kernel->boot(); | |
$result = $this->routeRequest($this->kernel, $request); | |
$result->output(); | |
}); | |
} | |
/** | |
* Replacement of Director::test() | |
* Note: Example implementation only; We may create a new Application instead of using the current application. | |
* | |
* @param HTTPRequest $request | |
* @return HTTPResponse | |
*/ | |
public function test(HTTPRequest $request) | |
{ | |
// @See Kernel::nest() | |
$kernel = $this->kernel->nest(); | |
try { | |
return $this->routeRequest($kernel, $request); | |
} finally { | |
// Safe unnest kernel | |
$kernel->unnest(); | |
} | |
} | |
/** | |
* Replacement for Director::direct() | |
* | |
* @param HTTPrequest $request | |
* @return HTTPResponse | |
*/ | |
protected function routeRequest($kernel, $request) | |
{ | |
// Refactor Director::direct() into here, return response | |
} | |
} | |
class Injector { | |
/** | |
* Ensure only active instance is accessed. | |
* Consistency enforced by both Injector::nest() / Kernel::nest() | |
* | |
* Same logic applied to Config (omitted for brevity) | |
*/ | |
protected function checkInst() | |
{ | |
if (static::$inst !== $this) { | |
throw new LogicException("Accessing incorrect container context"); | |
} | |
} | |
public function get($class) | |
{ | |
// Add consistency check when invoking injector | |
$this->checkInst(); | |
// ... | |
} | |
} | |
/** | |
* Move HTTP specific state into request | |
* | |
* Note: HTTPRequest will eventually be immutable (psr-7), but probably not by 4.0 | |
*/ | |
class HTTPRequest | |
{ | |
public static function createFromEnvironment() | |
{ | |
// Copied logic from main.php | |
return new static(); | |
} | |
/** | |
* @return Session | |
*/ | |
public function getSession() | |
{ | |
return $this->session; | |
} | |
} | |
/** | |
* Example injectable object | |
*/ | |
class SomeObject | |
{ | |
// Example for how an object must explicitly request a kernel to access it's methods | |
// Injectable / Configurable provide getContainer() and getConfig() for free, but other | |
// services can be injected here. | |
// Can also be done via yaml, but just inline here as an example | |
private static $dependencies = [ | |
'Kernel' => '%$'.Kernel::class | |
]; | |
protected $kernel; | |
public function getKernel() | |
{ | |
return $this->kernel; | |
} | |
public function setKernel(Kernel $kernel) | |
{ | |
$this->kernel = $kernel; | |
return $this; | |
} | |
/** | |
* Example user-code | |
* | |
* @param HTTPRequest $request | |
* @return HTTPResponse | |
*/ | |
public function handleRequest(HTTPRequest $request) | |
{ | |
// Example request handling | |
// Note how $request is simple passed, and not saved in state anywhere | |
// Any object which requires access to Session will need to be passed this object now, | |
// instead of relying on global statics | |
$subController = OtherObject::create(); | |
$result = $subController->handleRequest($request); | |
// Replaces Session::set() | |
$request->getSession()->set('OK', 1); | |
// Cookies is unchanged | |
Cookies::set('OK', 1); | |
// Replace Director::get_environment_type() | |
if ($this->getKernel()->getEnvironment() === 'dev') { | |
Debug::message('dev message'); | |
} | |
return $result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment