NEW NullDatabase (#10016)

* NEW DatabaselessKernel to support operation without DB

This is required for GraphQL code generation in CI (without a working runtime database/webserver environment).
Context: https://github.com/silverstripe/silverstripe-graphql/issues/388

* New --no-database option for sake

* Refactor to abstract class

* Apply feedback peer review

Co-authored-by: Aaron Carlino <unclecheese@leftandmain.com>
Co-authored-by: Maxime Rainville <maxime@silverstripe.com>
This commit is contained in:
Ingo Schommer 2022-02-04 10:07:27 +13:00 committed by GitHub
parent 3b5c72f3fd
commit d8499a24d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 949 additions and 493 deletions

View File

@ -4,6 +4,9 @@
use SilverStripe\Control\CLIRequestBuilder;
use SilverStripe\Control\HTTPApplication;
use SilverStripe\Core\CoreKernel;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\Connect\NullDatabase;
use SilverStripe\Core\DatabaselessKernel;
require __DIR__ . '/src/includes/autoload.php';
@ -16,8 +19,17 @@ if (!in_array(PHP_SAPI, ["cli", "cgi", "cgi-fcgi"])) {
// Build request and detect flush
$request = CLIRequestBuilder::createFromEnvironment();
$skipDatabase = in_array('--no-database', $argv);
if ($skipDatabase) {
DB::set_conn(new NullDatabase());
}
// Default application
$kernel = new CoreKernel(BASE_PATH);
$kernel = $skipDatabase
? new DatabaselessKernel(BASE_PATH)
: new CoreKernel(BASE_PATH);
$app = new HTTPApplication($kernel);
$response = $app->handle($request);
$response->output();

507
src/Core/BaseKernel.php Normal file
View File

@ -0,0 +1,507 @@
<?php
namespace SilverStripe\Core;
use InvalidArgumentException;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use SilverStripe\Config\Collections\CachedConfigCollection;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
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;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Core\Manifest\ModuleManifest;
use SilverStripe\Dev\DebugView;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Logging\ErrorHandler;
use SilverStripe\View\PublicThemes;
use SilverStripe\View\SSViewer;
use SilverStripe\View\ThemeManifest;
use SilverStripe\View\ThemeResourceLoader;
use Exception;
/**
* Simple Kernel container
*/
abstract class BaseKernel implements Kernel
{
/**
* @var Kernel
*/
protected $nestedFrom = null;
/**
* @var Injector
*/
protected $container = null;
/**
* @var string
*/
protected $enviroment = null;
/**
* @var ClassLoader
*/
protected $classLoader = null;
/**
* @var ModuleLoader
*/
protected $moduleLoader = null;
/**
* @var ConfigLoader
*/
protected $configLoader = null;
/**
* @var InjectorLoader
*/
protected $injectorLoader = null;
/**
* @var ThemeResourceLoader
*/
protected $themeResourceLoader = null;
protected $basePath = null;
/**
* Indicates whether the Kernel has been booted already
*
* @var bool
*/
private $booted = false;
/**
* Create a new kernel for this application
*
* @param string $basePath Path to base dir for this application
*/
public function __construct($basePath)
{
$this->basePath = $basePath;
// 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(['locator' => SilverStripeServiceConfigurationLocator::class]);
$injectorLoader->pushManifest($injector);
$this->setInjectorLoader($injectorLoader);
// Manifest cache factory
$manifestCacheFactory = $this->buildManifestCacheFactory();
// Class loader
$classLoader = ClassLoader::inst();
$classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory));
$this->setClassLoader($classLoader);
// Module loader
$moduleLoader = ModuleLoader::inst();
$moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory);
$moduleLoader->pushManifest($moduleManifest);
$this->setModuleLoader($moduleLoader);
// Config loader
// @todo refactor CoreConfigFactory
$configFactory = new CoreConfigFactory($manifestCacheFactory);
$configManifest = $configFactory->createRoot();
$configLoader = ConfigLoader::inst();
$configLoader->pushManifest($configManifest);
$this->setConfigLoader($configLoader);
// Load template manifest
$themeResourceLoader = ThemeResourceLoader::inst();
$themeResourceLoader->addSet(SSViewer::PUBLIC_THEME, new PublicThemes());
$themeResourceLoader->addSet(SSViewer::DEFAULT_THEME, new ThemeManifest(
$basePath,
null, // project is defined in config, and this argument is deprecated
$manifestCacheFactory
));
$this->setThemeResourceLoader($themeResourceLoader);
}
/**
* Initialise PHP with default variables
*/
protected function bootPHP()
{
if ($this->getEnvironment() === self::LIVE) {
// limited to fatal errors and warnings in live mode
error_reporting(E_ALL & ~(E_DEPRECATED | E_STRICT | E_NOTICE));
} else {
// Report all errors in dev / test mode
error_reporting(E_ALL | E_STRICT);
}
/**
* Ensure we have enough memory
*/
Environment::increaseMemoryLimitTo('64M');
// Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit
if (function_exists('xdebug_enable')) {
$current = ini_get('xdebug.max_nesting_level');
if ((int)$current < 200) {
ini_set('xdebug.max_nesting_level', 200);
}
}
/**
* Set default encoding
*/
mb_http_output('UTF-8');
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');
/**
* Enable better garbage collection
*/
gc_enable();
}
/**
* Boot all manifests
*
* @param bool $flush
*/
protected function bootManifests($flush)
{
// Setup autoloader
$this->getClassLoader()->init(
$this->getIncludeTests(),
$flush,
$this->getIgnoredCIConfigs()
);
// Find modules
$this->getModuleLoader()->init(
$this->getIncludeTests(),
$flush,
$this->getIgnoredCIConfigs()
);
// Flush config
if ($flush) {
$config = $this->getConfigLoader()->getManifest();
if ($config instanceof CachedConfigCollection) {
$config->setFlush(true);
}
}
// tell modules to sort, now that config is available
$this->getModuleLoader()->getManifest()->sort();
// Find default templates
$defaultSet = $this->getThemeResourceLoader()->getSet('$default');
if ($defaultSet instanceof ThemeManifest) {
$defaultSet->setProject(
ModuleManifest::config()->get('project')
);
$defaultSet->init(
$this->getIncludeTests(),
$flush,
$this->getIgnoredCIConfigs()
);
}
}
/**
* Include all _config.php files
*/
protected function bootConfigs()
{
global $project;
$projectBefore = $project;
$config = ModuleManifest::config();
// After loading all other app manifests, include _config.php files
$this->getModuleLoader()->getManifest()->activateConfig();
if ($project && $project !== $projectBefore) {
Deprecation::notice('5.0', '$project global is deprecated');
$config->set('project', $project);
}
}
/**
* Turn on error handling
* @throws Exception
*/
protected function bootErrorHandling()
{
// Register error handler
$errorHandler = Injector::inst()->get(ErrorHandler::class);
$errorHandler->start();
// Register error log file
$errorLog = Environment::getEnv('SS_ERROR_LOG');
if ($errorLog) {
$logger = Injector::inst()->get(LoggerInterface::class);
if ($logger instanceof Logger) {
$logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING));
} else {
user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
}
}
}
/**
* Get the environment type
*
* @return string
*
* @deprecated 5.0 use Director::get_environment_type() instead. Since 5.0 it should return only if kernel overrides. No checking SESSION or Environment.
*/
public function getEnvironment()
{
// Check set
if ($this->enviroment) {
return $this->enviroment;
}
// Check saved session
$env = $this->sessionEnvironment();
if ($env) {
return $env;
}
// Check getenv
if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) {
return $env;
}
return self::LIVE;
}
/**
* Check or update any temporary environment specified in the session.
*
* @return null|string
*
* @deprecated 5.0 Use Director::get_session_environment_type() instead
*/
protected function sessionEnvironment()
{
if (!$this->booted) {
// session is not initialyzed yet, neither is manifest
return null;
}
return Director::get_session_environment_type();
}
abstract public function boot($flush = false);
abstract public function isFlushed();
/**
* Check if there's a legacy _ss_environment.php file
*
* @throws HTTPResponse_Exception
*/
protected function detectLegacyEnvironment()
{
// Is there an _ss_environment.php file?
if (!file_exists($this->basePath . '/_ss_environment.php') &&
!file_exists(dirname($this->basePath) . '/_ss_environment.php')
) {
return;
}
// Build error response
$dv = new DebugView();
$body = implode([
$dv->renderHeader(),
$dv->renderInfo(
"Configuration Error",
Director::absoluteBaseURL()
),
$dv->renderParagraph(
'You need to replace your _ss_environment.php file with a .env file, or with environment variables.<br><br>'
. 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
. 'Environment Management</a> docs for more information.'
),
$dv->renderFooter()
]);
// Raise error
$response = new HTTPResponse($body, 500);
throw new HTTPResponse_Exception($response);
}
/**
* If missing configuration, redirect to install.php if it exists.
* Otherwise show a server error to the user.
*
* @param string $msg Optional message to show to the user on an installed project (install.php missing).
*/
protected function redirectToInstaller($msg = '')
{
// Error if installer not available
if (!file_exists(Director::publicFolder() . '/install.php')) {
throw new HTTPResponse_Exception(
$msg,
500
);
}
// Redirect to installer
$response = new HTTPResponse();
$response->redirect(Director::absoluteURL('install.php'));
throw new HTTPResponse_Exception($response);
}
/**
* @return ManifestCacheFactory
*/
protected function buildManifestCacheFactory()
{
return new ManifestCacheFactory([
'namespace' => 'manifestcache',
'directory' => TEMP_PATH,
]);
}
/**
* When manifests are discovering files, tests files in modules using the following CI library type will be ignored.
*
* The purpose of this method is to avoid loading PHPUnit test files with incompatible definitions.
*
* @return string[] List of CI types to ignore as defined by `Module`.
*/
protected function getIgnoredCIConfigs(): array
{
return [];
}
/**
* @return bool
*/
protected function getIncludeTests()
{
return false;
}
/**
* @param bool $bool
*/
protected function setBooted(bool $bool): void
{
$this->booted = $bool;
}
public function shutdown()
{
}
public function nest()
{
// Clone this kernel, nesting config / injector manifest containers
$kernel = clone $this;
$kernel->setConfigLoader($this->configLoader->nest());
$kernel->setInjectorLoader($this->injectorLoader->nest());
$kernel->nestedFrom = $this;
return $kernel;
}
public function activate()
{
$this->configLoader->activate();
$this->injectorLoader->activate();
// Self register
$this->getInjectorLoader()
->getManifest()
->registerService($this, Kernel::class);
return $this;
}
public function getNestedFrom()
{
return $this->nestedFrom;
}
public function getContainer()
{
return $this->getInjectorLoader()->getManifest();
}
public function setInjectorLoader(InjectorLoader $injectorLoader)
{
$this->injectorLoader = $injectorLoader;
$injectorLoader
->getManifest()
->registerService($this, Kernel::class);
return $this;
}
public function getInjectorLoader()
{
return $this->injectorLoader;
}
public function getClassLoader()
{
return $this->classLoader;
}
public function setClassLoader(ClassLoader $classLoader)
{
$this->classLoader = $classLoader;
return $this;
}
public function getModuleLoader()
{
return $this->moduleLoader;
}
public function setModuleLoader(ModuleLoader $moduleLoader)
{
$this->moduleLoader = $moduleLoader;
return $this;
}
public function setEnvironment($environment)
{
if (!in_array($environment, [self::DEV, self::TEST, self::LIVE, null])) {
throw new InvalidArgumentException(
"Director::set_environment_type passed '$environment'. It should be passed dev, test, or live"
);
}
$this->enviroment = $environment;
return $this;
}
public function getConfigLoader()
{
return $this->configLoader;
}
public function setConfigLoader($configLoader)
{
$this->configLoader = $configLoader;
return $this;
}
public function getThemeResourceLoader()
{
return $this->themeResourceLoader;
}
public function setThemeResourceLoader($themeResourceLoader)
{
$this->themeResourceLoader = $themeResourceLoader;
return $this;
}
}

View File

@ -2,190 +2,30 @@
namespace SilverStripe\Core;
use InvalidArgumentException;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use SilverStripe\Config\Collections\CachedConfigCollection;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
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;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Core\Manifest\ModuleManifest;
use SilverStripe\Dev\DebugView;
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
use SilverStripe\Logging\ErrorHandler;
use SilverStripe\ORM\DB;
use SilverStripe\View\PublicThemes;
use SilverStripe\View\SSViewer;
use SilverStripe\View\ThemeManifest;
use SilverStripe\View\ThemeResourceLoader;
use SilverStripe\Dev\Deprecation;
use Exception;
/**
* Simple Kernel container
*/
class CoreKernel implements Kernel
class CoreKernel extends BaseKernel
{
/**
* @var Kernel
*/
protected $nestedFrom = null;
/**
* @var Injector
*/
protected $container = null;
/**
* @var string
*/
protected $environment = null;
/**
* @var ClassLoader
*/
protected $classLoader = null;
/**
* @var ModuleLoader
*/
protected $moduleLoader = null;
/**
* @var ConfigLoader
*/
protected $configLoader = null;
/**
* @var InjectorLoader
*/
protected $injectorLoader = null;
/**
* @var ThemeResourceLoader
*/
protected $themeResourceLoader = null;
protected $basePath = null;
/**
* Indicates whether the Kernel has been booted already
*
* @var bool
*/
private $booted = false;
/**
* Indicates whether the Kernel has been flushed on boot
* Uninitialized before boot
* Uninitialised before boot
*
* @var bool
*/
private $flush;
/**
* Create a new kernel for this application
*
* @param string $basePath Path to base dir for this application
* @param false $flush
* @throws HTTPResponse_Exception
* @throws Exception
*/
public function __construct($basePath)
{
$this->basePath = $basePath;
// 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(['locator' => SilverStripeServiceConfigurationLocator::class]);
$injectorLoader->pushManifest($injector);
$this->setInjectorLoader($injectorLoader);
// Manifest cache factory
$manifestCacheFactory = $this->buildManifestCacheFactory();
// Class loader
$classLoader = ClassLoader::inst();
$classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory));
$this->setClassLoader($classLoader);
// Module loader
$moduleLoader = ModuleLoader::inst();
$moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory);
$moduleLoader->pushManifest($moduleManifest);
$this->setModuleLoader($moduleLoader);
// Config loader
// @todo refactor CoreConfigFactory
$configFactory = new CoreConfigFactory($manifestCacheFactory);
$configManifest = $configFactory->createRoot();
$configLoader = ConfigLoader::inst();
$configLoader->pushManifest($configManifest);
$this->setConfigLoader($configLoader);
// Load template manifest
$themeResourceLoader = ThemeResourceLoader::inst();
$themeResourceLoader->addSet(SSViewer::PUBLIC_THEME, new PublicThemes());
$themeResourceLoader->addSet(SSViewer::DEFAULT_THEME, new ThemeManifest(
$basePath,
null, // project is defined in config, and this argument is deprecated
$manifestCacheFactory
));
$this->setThemeResourceLoader($themeResourceLoader);
}
/**
* Get the environment type
*
* @return string
*
* @deprecated 5.0 use Director::get_environment_type() instead. Since 5.0 it should return only if kernel overrides. No checking SESSION or Environment.
*/
public function getEnvironment()
{
// Check set
if ($this->environment) {
return $this->environment;
}
// Check saved session
$env = $this->sessionEnvironment();
if ($env) {
return $env;
}
// Check getenv
if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) {
return $env;
}
return self::LIVE;
}
/**
* Check or update any temporary environment specified in the session.
*
* @return null|string
*
* @deprecated 5.0 Use Director::get_session_environment_type() instead
*/
protected function sessionEnvironment()
{
if (!$this->booted) {
// session is not initialized yet, neither is manifest
return null;
}
return Director::get_session_environment_type();
}
public function boot($flush = false)
{
$this->flush = $flush;
@ -198,22 +38,23 @@ class CoreKernel implements Kernel
$this->bootDatabaseGlobals();
$this->validateDatabase();
$this->booted = true;
$this->setBooted(true);
}
/**
* Include all _config.php files
* Check that the database configuration is valid, throwing an HTTPResponse_Exception if it's not
*
* @throws HTTPResponse_Exception
*/
protected function bootConfigs()
protected function validateDatabase()
{
global $project;
$projectBefore = $project;
$config = ModuleManifest::config();
// After loading all other app manifests, include _config.php files
$this->getModuleLoader()->getManifest()->activateConfig();
if ($project && $project !== $projectBefore) {
Deprecation::notice('5.0', '$project global is deprecated');
$config->set('project', $project);
$databaseConfig = DB::getConfig();
// Gracefully fail if no DB is configured
if (empty($databaseConfig['database'])) {
$msg = 'Silverstripe Framework requires a "database" key in DB::getConfig(). ' .
'Did you forget to set SS_DATABASE_NAME or SS_DATABASE_CHOOSE_NAME in your environment?';
$this->detectLegacyEnvironment();
$this->redirectToInstaller($msg);
}
}
@ -260,80 +101,6 @@ class CoreKernel implements Kernel
DB::setConfig($databaseConfig);
}
/**
* Check that the database configuration is valid, throwing an HTTPResponse_Exception if it's not
*
* @throws HTTPResponse_Exception
*/
protected function validateDatabase()
{
$databaseConfig = DB::getConfig();
// Gracefully fail if no DB is configured
if (empty($databaseConfig['database'])) {
$msg = 'Silverstripe Framework requires a "database" key in DB::getConfig(). ' .
'Did you forget to set SS_DATABASE_NAME or SS_DATABASE_CHOOSE_NAME in your environment?';
$this->detectLegacyEnvironment();
$this->redirectToInstaller($msg);
}
}
/**
* Check if there's a legacy _ss_environment.php file
*
* @throws HTTPResponse_Exception
*/
protected function detectLegacyEnvironment()
{
// Is there an _ss_environment.php file?
if (!file_exists($this->basePath . '/_ss_environment.php') &&
!file_exists(dirname($this->basePath) . '/_ss_environment.php')
) {
return;
}
// Build error response
$dv = new DebugView();
$body = implode([
$dv->renderHeader(),
$dv->renderInfo(
"Configuration Error",
Director::absoluteBaseURL()
),
$dv->renderParagraph(
'You need to replace your _ss_environment.php file with a .env file, or with environment variables.<br><br>'
. 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
. 'Environment Management</a> docs for more information.'
),
$dv->renderFooter()
]);
// Raise error
$response = new HTTPResponse($body, 500);
throw new HTTPResponse_Exception($response);
}
/**
* If missing configuration, redirect to install.php if it exists.
* Otherwise show a server error to the user.
*
* @param string $msg Optional message to show to the user on an installed project (install.php missing).
*/
protected function redirectToInstaller($msg = '')
{
// Error if installer not available
if (!file_exists(Director::publicFolder() . '/install.php')) {
throw new HTTPResponse_Exception(
$msg,
500
);
}
// Redirect to installer
$response = new HTTPResponse();
$response->redirect(Director::absoluteURL('install.php'));
throw new HTTPResponse_Exception($response);
}
/**
* Load database config from environment
*
@ -451,247 +218,6 @@ class CoreKernel implements Kernel
return null;
}
/**
* Initialise PHP with default variables
*/
protected function bootPHP()
{
if ($this->getEnvironment() === self::LIVE) {
// limited to fatal errors and warnings in live mode
error_reporting(E_ALL & ~(E_DEPRECATED | E_STRICT | E_NOTICE));
} else {
// Report all errors in dev / test mode
error_reporting(E_ALL | E_STRICT);
}
/**
* Ensure we have enough memory
*/
Environment::increaseMemoryLimitTo('64M');
// Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit
if (function_exists('xdebug_enable')) {
$current = ini_get('xdebug.max_nesting_level');
if ((int)$current < 200) {
ini_set('xdebug.max_nesting_level', 200);
}
}
/**
* Set default encoding
*/
mb_http_output('UTF-8');
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');
/**
* Enable better garbage collection
*/
gc_enable();
}
/**
* @return ManifestCacheFactory
*/
protected function buildManifestCacheFactory()
{
return new ManifestCacheFactory([
'namespace' => 'manifestcache',
'directory' => TEMP_PATH,
]);
}
/**
* @return bool
*/
protected function getIncludeTests()
{
return false;
}
/**
* When manifests are discovering files, tests files in modules using the following CI library type will be ignored.
*
* The purpose of this method is to avoid loading PHPUnit test files with incompatible definitions.
*
* @return string[] List of CI types to ignore as defined by `Module`.
*/
protected function getIgnoredCIConfigs(): array
{
return [];
}
/**
* Boot all manifests
*
* @param bool $flush
*/
protected function bootManifests($flush)
{
// Setup autoloader
$this->getClassLoader()->init(
$this->getIncludeTests(),
$flush,
$this->getIgnoredCIConfigs()
);
// Find modules
$this->getModuleLoader()->init(
$this->getIncludeTests(),
$flush,
$this->getIgnoredCIConfigs()
);
// Flush config
if ($flush) {
$config = $this->getConfigLoader()->getManifest();
if ($config instanceof CachedConfigCollection) {
$config->setFlush(true);
}
}
// tell modules to sort, now that config is available
$this->getModuleLoader()->getManifest()->sort();
// Find default templates
$defaultSet = $this->getThemeResourceLoader()->getSet('$default');
if ($defaultSet instanceof ThemeManifest) {
$defaultSet->setProject(
ModuleManifest::config()->get('project')
);
$defaultSet->init(
$this->getIncludeTests(),
$flush,
$this->getIgnoredCIConfigs()
);
}
}
/**
* Turn on error handling
*/
protected function bootErrorHandling()
{
// Register error handler
$errorHandler = Injector::inst()->get(ErrorHandler::class);
$errorHandler->start();
// Register error log file
$errorLog = Environment::getEnv('SS_ERROR_LOG');
if ($errorLog) {
$logger = Injector::inst()->get(LoggerInterface::class);
if ($logger instanceof Logger) {
$logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING));
} else {
user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
}
}
}
public function shutdown()
{
}
public function nest()
{
// Clone this kernel, nesting config / injector manifest containers
$kernel = clone $this;
$kernel->setConfigLoader($this->configLoader->nest());
$kernel->setInjectorLoader($this->injectorLoader->nest());
$kernel->nestedFrom = $this;
return $kernel;
}
public function activate()
{
$this->configLoader->activate();
$this->injectorLoader->activate();
// Self register
$this->getInjectorLoader()
->getManifest()
->registerService($this, Kernel::class);
return $this;
}
public function getNestedFrom()
{
return $this->nestedFrom;
}
public function getContainer()
{
return $this->getInjectorLoader()->getManifest();
}
public function setInjectorLoader(InjectorLoader $injectorLoader)
{
$this->injectorLoader = $injectorLoader;
$injectorLoader
->getManifest()
->registerService($this, Kernel::class);
return $this;
}
public function getInjectorLoader()
{
return $this->injectorLoader;
}
public function getClassLoader()
{
return $this->classLoader;
}
public function setClassLoader(ClassLoader $classLoader)
{
$this->classLoader = $classLoader;
return $this;
}
public function getModuleLoader()
{
return $this->moduleLoader;
}
public function setModuleLoader(ModuleLoader $moduleLoader)
{
$this->moduleLoader = $moduleLoader;
return $this;
}
public function setEnvironment($environment)
{
if (!in_array($environment, [self::DEV, self::TEST, self::LIVE, null])) {
throw new InvalidArgumentException(
"Director::set_environment_type passed '$environment'. It should be passed dev, test, or live"
);
}
$this->environment = $environment;
return $this;
}
public function getConfigLoader()
{
return $this->configLoader;
}
public function setConfigLoader($configLoader)
{
$this->configLoader = $configLoader;
return $this;
}
public function getThemeResourceLoader()
{
return $this->themeResourceLoader;
}
public function setThemeResourceLoader($themeResourceLoader)
{
$this->themeResourceLoader = $themeResourceLoader;
return $this;
}
/**
* Returns whether the Kernel has been flushed on boot
*

View File

@ -0,0 +1,63 @@
<?php
namespace SilverStripe\Core;
use Exception;
/**
* Boot a kernel without requiring a database connection.
* This is a workaround for the lack of composition in the boot stages
* of CoreKernel, as well as for the framework's misguided assumptions
* around the availability of a database for every execution path.
*
* @internal
*/
class DatabaselessKernel extends BaseKernel
{
/**
* Indicates whether the Kernel has been flushed on boot
* Uninitialised before boot
*
* @var bool
*/
private $flush;
/**
* Allows disabling of the configured error handling.
* This can be useful to ensure the execution context (e.g. composer)
* can consistently use its own error handling.
*
* @var boolean
*/
protected $bootErrorHandling = true;
public function setBootErrorHandling(bool $bool)
{
$this->bootErrorHandling = $bool;
return $this;
}
/**
* @param false $flush
* @throws Exception
*/
public function boot($flush = false)
{
$this->flush = $flush;
$this->bootPHP();
$this->bootManifests($flush);
$this->bootErrorHandling();
$this->bootConfigs();
$this->setBooted(true);
}
/**
* @return bool
*/
public function isFlushed()
{
return $this->flush;
}
}

View File

@ -0,0 +1,336 @@
<?php
namespace SilverStripe\ORM\Connect;
use BadMethodCallException;
use Exception;
/**
* Utility class required due to bad coupling in framework.
* Not every framework execution should require a working database connection.
* For example, when generating class and config manifests for deployment bundles,
* or when generating code in a silverstripe/graphql schema build.
*
* This class creates the required no-ops to fulfill the contract,
* and create exceptions as required.
*
* It also avoids introducing new third party core dependencies that
* would be required with https://github.com/tractorcow/silverstripe-proxy-db.
*
* @internal
*/
class NullDatabase extends Database
{
/**
* @var string
*/
private $errorMessage = 'Using NullDatabase, cannot interact with database';
/**
* @var string
*/
private $queryErrorMessage = 'Using NullDatabase, cannot execute query: %s';
/**
* @param string $msg
*/
public function setErrorMessage(string $msg): self
{
$this->errorMessage = $msg;
return $this;
}
/**
* @param string $msg
*/
public function setQueryErrorMessage(string $msg): self
{
$this->queryErrorMessage = $msg;
return $this;
}
/**
* @throws NullDatabaseException
*/
public function query($sql, $errorLevel = E_USER_ERROR)
{
throw new NullDatabaseException(sprintf($this->queryErrorMessage, $sql));
}
/**
* @throws NullDatabaseException
*/
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
{
throw new NullDatabaseException(sprintf($this->queryErrorMessage, $sql));
}
/**
* @throws NullDatabaseException
*/
public function getConnector()
{
throw new NullDatabaseException($this->errorMessage);
}
/**
* @throws NullDatabaseException
*/
public function getSchemaManager()
{
throw new NullDatabaseException($this->errorMessage);
}
/**
* @throws NullDatabaseException
*/
public function getQueryBuilder()
{
throw new NullDatabaseException($this->errorMessage);
}
public function getGeneratedID($table)
{
// no-op
}
public function isActive()
{
return true;
}
public function escapeString($value)
{
return $value;
}
public function quoteString($value)
{
return $value;
}
public function escapeIdentifier($value, $separator = '.')
{
return $value;
}
protected function escapeColumnKeys($fieldValues)
{
return $fieldValues;
}
/**
* @throws NullDatabaseException
*/
public function manipulate($manipulation)
{
throw new NullDatabaseException($this->errorMessage);
}
/**
* @throws NullDatabaseException
*/
public function clearAllData()
{
throw new NullDatabaseException($this->errorMessage);
}
/**
* @throws NullDatabaseException
*/
public function clearTable($table)
{
throw new NullDatabaseException($this->errorMessage);
}
public function nullCheckClause($field, $isNull)
{
return '';
}
public function comparisonClause(
$field,
$value,
$exact = false,
$negate = false,
$caseSensitive = null,
$parameterised = false
) {
return '';
}
public function formattedDatetimeClause($date, $format)
{
return '';
}
public function datetimeIntervalClause($date, $interval)
{
return '';
}
public function datetimeDifferenceClause($date1, $date2)
{
return '';
}
public function concatOperator()
{
return '';
}
public function supportsCollations()
{
return false;
}
public function supportsTimezoneOverride()
{
return false;
}
public function getVersion()
{
return '';
}
public function getDatabaseServer()
{
return '';
}
public function affectedRows()
{
return 0;
}
public function searchEngine(
$classesToSearch,
$keywords,
$start,
$pageLength,
$sortBy = "Relevance DESC",
$extraFilter = "",
$booleanSearch = false,
$alternativeFileFilter = "",
$invertedMatch = false
) {
// no-op
}
public function supportsTransactions()
{
return false;
}
public function supportsSavepoints()
{
return false;
}
public function supportsTransactionMode(string $mode): bool
{
return false;
}
public function withTransaction(
$callback,
$errorCallback = null,
$transactionMode = false,
$errorIfTransactionsUnsupported = false
) {
// no-op
}
public function supportsExtensions($extensions)
{
return false;
}
public function transactionStart($transactionMode = false, $sessionCharacteristics = false)
{
// no-op
}
public function transactionSavepoint($savepoint)
{
// no-op
}
public function transactionRollback($savepoint = false)
{
// no-op
}
public function transactionEnd($chain = false)
{
// no-op
}
public function transactionDepth()
{
return 0;
}
public function supportsLocks()
{
return false;
}
public function canLock($name)
{
return false;
}
public function getLock($name, $timeout = 5)
{
return false;
}
public function releaseLock($name)
{
return false;
}
public function connect($parameters)
{
// no-op
}
public function databaseExists($name)
{
return false;
}
public function databaseList()
{
return [];
}
public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR)
{
// no-op
}
public function dropSelectedDatabase()
{
// no-op
}
public function getSelectedDatabase()
{
// no-op
}
public function now()
{
return '';
}
public function random()
{
return '';
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace SilverStripe\ORM\Connect;
use LogicException;
/**
* Exception thrown when a database operation is attempted on a `NullDatabase`.
* @internal
*/
class NullDatabaseException extends LogicException
{
}