Files
pathfinder/app/main/lib/db/Pool.php
2019-10-08 18:36:20 +02:00

236 lines
6.6 KiB
PHP

<?php
namespace lib\db;
use DB\SQL\Schema;
use controller\LogController;
use Exception\ConfigException;
class Pool extends \Prefab {
/**
* @var string
*/
const POOL_NAME = 'DB';
/**
* error for unsupported database scheme
* @var string
*/
const ERROR_SCHEME = 'DB Scheme "%s" is not supported for DB alias "%s"';
/**
* @var SQL[]
*/
private $connectionStore = [];
/**
* @var [][]
*/
private $errors = [];
/**
* if true, errors will not get logged
* @var bool
*/
private $silent = false;
/**
* callback function for database credentials that accepts an alias string
* @var \Closure
*/
protected $getConfig;
/**
* callback function for required database variables
* @var \Closure
*/
protected $requiredVars;
/**
* Pool constructor.
* @param \Closure $getConfig
* @param \Closure $requiredVars
*/
public function __construct(\Closure $getConfig, \Closure $requiredVars){
$this->getConfig = $getConfig;
$this->requiredVars = $requiredVars;
}
/**
* set "silent" mode (no error logging)
* -> optional clear $this->errors
* @param bool $silent
* @param bool $clearErrors
*/
public function setSilent(bool $silent, bool $clearErrors = false){
$this->silent = $silent;
if($clearErrors){
$this->errors = [];
}
}
/**
* @return bool
*/
public function isSilent() : bool {
return $this->silent;
}
/**
* connect to the DB server itself -> NO database is used
* -> can be used to check if a certain DB exists without connecting to it directly
* @param string $alias
* @return SQL|null
*/
public function connectToServer(string $alias) : ?SQL {
$config = ($this->getConfig)($alias);
$config['NAME'] = '';
return $this->newDB($config);
}
/**
* tries to create a database if not exists
* -> DB user needs rights to create a DB
* @param string $alias
* @return SQL|null
*/
public function createDB(string $alias) : ?SQL {
$db = null;
$config = ($this->getConfig)($alias);
// remove database from $dsn (we want to crate it)
$newDbName = $config['NAME'];
if(!empty($newDbName)){
$config['NAME'] = '';
$db = $this->newDB($config);
if(!is_null($db)){
$schema = new Schema($db);
if(!in_array($newDbName, $schema->getDatabases())){
$db->exec("CREATE DATABASE IF NOT EXISTS
`" . $newDbName . "` DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;");
$db->exec("USE `" . $newDbName . "`");
// check if DB create was successful
$dbCheck = $db->exec("SELECT DATABASE()");
if(
!empty($dbCheck[0]) &&
!empty($checkDbName = reset($dbCheck[0])) &&
$checkDbName == $newDbName
){
// prepare new created DB
$requiredVars = ($this->requiredVars)($db->driver());
$db->prepareDatabase($requiredVars['CHARACTER_SET_DATABASE'], $requiredVars['COLLATION_DATABASE']);
}
}
}
}
return $db;
}
/**
* get active connection from store or init new connection
* @param string $alias
* @return SQL|null
*/
public function getDB(string $alias) : ?SQL {
if(!isset($this->connectionStore[$alias])){
$db = $this->newDB(($this->getConfig)($alias));
if(!is_null($db)){
$this->connectionStore[$alias] = $db;
}
return $db;
}else{
return $this->connectionStore[$alias];
}
}
/**
* get last recent Exceptions from error history
* @param string $alias
* @param int $limit
* @return \Exception[]
*/
public function getErrors(string $alias, int $limit = 1) : array {
return array_slice((array)$this->errors[$alias] , 0, $limit);
}
/**
* build PDO DNS connect string from DB config array
* -> Hint: dbName is not part of the DNS we need -> passed as extra parameter
* @param array $config
* @return string
*/
protected function buildDnsFromConfig(array $config) : string {
$dns = $config['SCHEME'] . ':';
$dns .= $config['SOCKET'] ? 'unix_socket=' . $config['SOCKET'] : 'host=' . $config['HOST'];
$dns .= $config['PORT'] && !$config['SOCKET'] ? ';port=' . $config['PORT'] : '';
$dns .= $config['NAME'] ? ';dbname=' . $config['NAME'] : '';
return $dns;
}
/**
* @param array $config
* @return SQL|null
*/
protected function newDB(array $config) : ?SQL {
$db = null;
if($config['SCHEME'] == 'mysql'){
try{
$db = new SQL($this->buildDnsFromConfig($config), $config['USER'], $config['PASS'], $config['OPTIONS']);
}catch(\PDOException $e){
$this->pushError($config['ALIAS'], $e);
if(!$this->isSilent()){
self::getLogger()->write($e);
}
}
}else{
// unsupported DB type
$this->pushError($config['ALIAS'], new ConfigException(
sprintf(self::ERROR_SCHEME, $config['SCHEME'], $config['ALIAS']))
);
}
return $db;
}
/**
* push new Exception into static error history
* @param string $alias
* @param \Exception $e
*/
protected function pushError(string $alias, \Exception $e){
if(!is_array($this->errors[$alias])){
$this->errors[$alias] = [];
}
// prevent adding same errors twice
if(!empty($this->errors[$alias])){
/**
* @var $lastError \Exception
*/
$lastError = array_values($this->errors[$alias])[0];
if($lastError->getMessage() === $e->getMessage()){
return;
}
}
array_unshift($this->errors[$alias], $e);
if(count($this->errors[$alias]) > 5){
$this->errors[$alias] = array_pop($this->errors[$alias]);
}
}
/**
* @return \Log
*/
static function getLogger() : \Log {
return LogController::getLogger('ERROR');
}
}