Skip to content

Commit

Permalink
NEW DatabaselessKernel to support operation without DB
Browse files Browse the repository at this point in the history
This is required for GraphQL code generation in CI (without a working runtime database/webserver environment).
Context: silverstripe/silverstripe-graphql#388
  • Loading branch information
chillu committed Jul 12, 2021
1 parent ae61be3 commit d25e0fa
Show file tree
Hide file tree
Showing 4 changed files with 367 additions and 0 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"psr/container": "1.0.0",
"silverstripe/config": "^1@dev",
"silverstripe/assets": "^1@dev",
"silverstripe/event-dispatcher": "^0.1.0",
"silverstripe/vendor-plugin": "^1.4",
"sminnee/callbacklist": "^0.1",
"swiftmailer/swiftmailer": "~5.4",
Expand Down
35 changes: 35 additions & 0 deletions src/Core/DatabaselessKernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace SilverStripe\Core;

use SilverStripe\EventDispatcher\Dispatch\Dispatcher;
use SilverStripe\EventDispatcher\Event\EventContextInterface;
use SilverStripe\EventDispatcher\Event\EventHandlerInterface;
use SilverStripe\ORM\Connect\NullDatabase;
use SilverStripe\ORM\DB;

/**
* 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 CoreKernel
{
protected $queryErrorMessage = 'Booted with DatabaseLessKernel, cannot execute query: %s';

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

$this->bootPHP();
$this->bootManifests($flush);
$this->bootErrorHandling();
$this->bootConfigs();

$this->booted = true;
}

}
17 changes: 17 additions & 0 deletions src/ORM/Connect/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use SilverStripe\ORM\PaginatedList;
use SilverStripe\ORM\Queries\SQLUpdate;
use SilverStripe\ORM\Queries\SQLInsert;
use SilverStripe\EventDispatcher\Dispatch\Dispatcher;
use SilverStripe\EventDispatcher\Symfony\Event;
use BadMethodCallException;
use Exception;
use SilverStripe\Dev\Backtrace;
Expand Down Expand Up @@ -151,6 +153,13 @@ public function query($sql, $errorLevel = E_USER_ERROR)
return null;
}

Dispatcher::singleton()->trigger('database.query', Event::create(
null,
[
'sql' => $sql
]
));

// Benchmark query
$connector = $this->connector;
return $this->benchmarkQuery(
Expand All @@ -177,6 +186,14 @@ public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
return null;
}

Dispatcher::singleton()->trigger('database.preparedQuery', Event::create(
null,
[
'sql' => $sql,
'parameters' => $parameters
]
));

// Benchmark query
$connector = $this->connector;
return $this->benchmarkQuery(
Expand Down
314 changes: 314 additions & 0 deletions src/ORM/Connect/NullDatabase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
<?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
*/
protected $errorMessage = 'Using NullDatabase, cannot interact with database';

/**
* @var string
*/
protected $queryErrorMessage = 'Using NullDatabase, cannot execute query: %s';

/**
* @param string $msg
* @return self
*/
public function setErrorMessage(string $msg)
{
$this->errorMessage = $msg;
return $this;
}

/**
* @param string $msg
* @return self
*/
public function setQueryErrorMessage(string $msg)
{
$this->queryErrorMessage = $msg;
return $this;
}

public function query($sql, $errorLevel = E_USER_ERROR)
{
throw new \LogicException(sprintf($this->queryErrorMessage, $sql));
}

public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
{
throw new \LogicException(sprintf($this->queryErrorMessage, $sql));
}

public function getConnector()
{
throw new \LogicException($this->errorMessage);
}

public function getSchemaManager()
{
throw new \LogicException($this->errorMessage);
}

public function getQueryBuilder()
{
throw new \LogicException($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;
}

public function manipulate($manipulation)
{
throw new \LogicException($this->errorMessage);
}

public function clearAllData()
{
throw new \LogicException($this->errorMessage);
}

public function clearTable($table)
{
throw new \LogicException($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 '';
}
}

0 comments on commit d25e0fa

Please sign in to comment.