- Working on revamping Ulmus bases classes

This commit is contained in:
Dave M. 2026-07-13 19:41:44 +00:00
parent 13b7cf06f1
commit 1dcf32e0da
12 changed files with 303 additions and 52 deletions

View File

@ -2,7 +2,6 @@
namespace Ulmus\Adapter;
use Ulmus\Common\PdoObject;
use Ulmus\Migration\FieldDefinition;
interface AdapterInterface {
@ -12,7 +11,7 @@ interface AdapterInterface {
public const IDENTIFIER_SCHEMA = 4;
public const IDENTIFIER_VALUE = 5;
public function connect() : object /* | PdoObject|mixed */;
public function connect() : object;
public function buildDataSourceName() : string;
public function setup(array $configuration) : void;
public function writableValue(/* mixed */ $value); /*: mixed*/

View File

@ -2,7 +2,7 @@
namespace Ulmus\Adapter;
use Ulmus\Common\PdoObject;
use Ulmus\Pdo;
use Ulmus\Exception\AdapterConfigurationException;
@ -83,10 +83,10 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
}
}
public function connect() : PdoObject
public function connect() : Pdo
{
try {
$pdo = new PdoObject($this->buildDataSourceName(), $this->username, $this->password);
$pdo = new Pdo\Mssql($this->buildDataSourceName(), $this->username, $this->password);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
$pdo->setAttribute(\PDO::SQLSRV_ATTR_ENCODING, \PDO::SQLSRV_ENCODING_UTF8);

View File

@ -8,7 +8,7 @@ use Ulmus\Migration\FieldDefinition;
use Ulmus\Migration\MigrateInterface;
use Ulmus\Migration\SqlMigrationTrait;
use Ulmus\QueryBuilder\Sql;
use Ulmus\Common\PdoObject;
use Ulmus\Pdo;
use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Repository;
@ -71,10 +71,10 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
}
}
public function connect() : PdoObject
public function connect() : \PDO
{
try {
$pdo = new PdoObject($this->buildDataSourceName(), $this->username, $this->password);
$pdo = new Pdo\Mysql($this->buildDataSourceName(), $this->username, $this->password);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, false);

View File

@ -3,7 +3,7 @@
namespace Ulmus\Adapter;
use Collator;
use Ulmus\Common\PdoObject;
use Ulmus\Pdo;
use Ulmus\ConnectionAdapter;
use Ulmus\Entity;
use Ulmus\Migration\FieldDefinition;
@ -24,16 +24,15 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
public null|array $pragmaClose = null,
) { }
public function connect() : PdoObject
public function connect() : \Pdo
{
try {
#$pdo = new PdoObject($this->buildDataSourceName(), null, null);
$pdo = new PdoObject\SqlitePdoObject($this->buildDataSourceName(), null, null);
$pdo = new Pdo\Sqlite($this->buildDataSourceName(), null, null);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
$pdo->onClose = function(PdoObject $obj) {
$pdo->onClose = function(\PDO $obj) {
static::registerPragma($obj, $this->pragmaClose);
};
@ -158,28 +157,28 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
return QueryBuilder\Sql\SqliteQueryBuilder::class;
}
public function exportFunctions(PdoObject $pdo) : void
public function exportFunctions(\PDO $pdo) : void
{
$pdo->sqliteCreateFunction('if', fn($comparison, $yes, $no) => $comparison ? $yes : $no, 3);
$pdo->sqliteCreateFunction('length', fn($string) => strlen($string), 1);
$pdo->sqliteCreateFunction('lcase', fn($string) => strtolower($string), 1);
$pdo->sqliteCreateFunction('ucase', fn($string) => strtoupper($string), 1);
$pdo->sqliteCreateFunction('md5', fn($string) => md5($string), 1);
$pdo->sqliteCreateFunction('sha1', fn($string) => sha1($string), 1);
$pdo->sqliteCreateFunction('sha256', fn($string) => hash('sha256', $string), 1);
$pdo->sqliteCreateFunction('sha512', fn($string) => hash('sha512', $string), 1);
$pdo->sqliteCreateFunction('whirlpool', fn($string) => hash('whirlpool', $string), 1);
$pdo->sqliteCreateFunction('murmur3a', fn($string) => hash('murmur3a', $string), 1);
$pdo->sqliteCreateFunction('murmur3c', fn($string) => hash('murmur3c', $string), 1);
$pdo->sqliteCreateFunction('murmur3f', fn($string) => hash('murmur3f', $string), 1);
$pdo->sqliteCreateFunction('left', fn($string, $length) => substr($string, 0, $length), 2);
$pdo->sqliteCreateFunction('right', fn($string, $length) => substr($string, -$length), 2);
$pdo->sqliteCreateFunction('strcmp', fn($s1, $s2) => strcmp($s1, $s2), 2);
$pdo->sqliteCreateFunction('lpad', fn($string, $length, $pad) => str_pad($string, $length, $pad, STR_PAD_LEFT), 3);
$pdo->sqliteCreateFunction('rpad', fn($string, $length, $pad) => str_pad($string, $length, $pad, STR_PAD_RIGHT), 3);
$pdo->sqliteCreateFunction('concat', fn(...$args) => implode('', $args), -1);
$pdo->sqliteCreateFunction('concat_ws', fn($separator, ...$args) => implode($separator, $args), -1);
$pdo->sqliteCreateFunction('find_in_set', function($string, $string_list) {
$pdo->createFunction('if', fn($comparison, $yes, $no) => $comparison ? $yes : $no, 3);
$pdo->createFunction('length', fn($string) => strlen($string), 1);
$pdo->createFunction('lcase', fn($string) => strtolower($string), 1);
$pdo->createFunction('ucase', fn($string) => strtoupper($string), 1);
$pdo->createFunction('md5', fn($string) => md5($string), 1);
$pdo->createFunction('sha1', fn($string) => sha1($string), 1);
$pdo->createFunction('sha256', fn($string) => hash('sha256', $string), 1);
$pdo->createFunction('sha512', fn($string) => hash('sha512', $string), 1);
$pdo->createFunction('whirlpool', fn($string) => hash('whirlpool', $string), 1);
$pdo->createFunction('murmur3a', fn($string) => hash('murmur3a', $string), 1);
$pdo->createFunction('murmur3c', fn($string) => hash('murmur3c', $string), 1);
$pdo->createFunction('murmur3f', fn($string) => hash('murmur3f', $string), 1);
$pdo->createFunction('left', fn($string, $length) => substr($string, 0, $length), 2);
$pdo->createFunction('right', fn($string, $length) => substr($string, -$length), 2);
$pdo->createFunction('strcmp', fn($s1, $s2) => strcmp($s1, $s2), 2);
$pdo->createFunction('lpad', fn($string, $length, $pad) => str_pad($string, $length, $pad, STR_PAD_LEFT), 3);
$pdo->createFunction('rpad', fn($string, $length, $pad) => str_pad($string, $length, $pad, STR_PAD_RIGHT), 3);
$pdo->createFunction('concat', fn(...$args) => implode('', $args), -1);
$pdo->createFunction('concat_ws', fn($separator, ...$args) => implode($separator, $args), -1);
$pdo->createFunction('find_in_set', function($string, $string_list) {
if ( $string === null || $string_list === null ) {
return null;
}
@ -190,13 +189,13 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
return (int) in_array($string, explode(',', $string_list));
}, 2);
$pdo->sqliteCreateFunction('day', fn($date) => ( new \DateTime($date) )->format('j'), 1);
$pdo->sqliteCreateFunction('month', fn($date) => ( new \DateTime($date) )->format('n'), 1);
$pdo->sqliteCreateFunction('year', fn($date) => ( new \DateTime($date) )->format('Y'), 1);
$pdo->sqliteCreateFunction('str_to_date', fn($value, $format) => ($f = \DateTime::createFromFormat($format, $value)) ? $f->format('Y-m-d H:i:s') : false);
$pdo->createFunction('day', fn($date) => ( new \DateTime($date) )->format('j'), 1);
$pdo->createFunction('month', fn($date) => ( new \DateTime($date) )->format('n'), 1);
$pdo->createFunction('year', fn($date) => ( new \DateTime($date) )->format('Y'), 1);
$pdo->createFunction('str_to_date', fn($value, $format) => ($f = \DateTime::createFromFormat($format, $value)) ? $f->format('Y-m-d H:i:s') : false);
}
public function exportCollations(PdoObject $pdo) : void
public function exportCollations(\PDO $pdo) : void
{
if ( class_exists('Locale') ) {
@ -205,28 +204,28 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
$collator->setStrength(Collator::TERTIARY);
$collator->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON);
$pdo->sqliteCreateCollation('locale_cmp', fn($e1, $e2) => $collator->compare($e1, $e2));
$pdo->sqliteCreateCollation('locale_cmp_desc', fn($e1, $e2) => $collator->compare($e2, $e1));
$pdo->createCollation('locale_cmp', fn($e1, $e2) => $collator->compare($e1, $e2));
$pdo->createCollation('locale_cmp_desc', fn($e1, $e2) => $collator->compare($e2, $e1));
$collatorCi = new Collator(\Locale::getDefault());
$collatorCi->setStrength(Collator::SECONDARY);
$collatorCi->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON);
$pdo->sqliteCreateCollation('locale_cmp_ci', fn($e1, $e2) => $collatorCi->compare($e1, $e2));
$pdo->sqliteCreateCollation('locale_cmp_ci_desc', fn($e1, $e2) => $collatorCi->compare($e2, $e1));
$pdo->createCollation('locale_cmp_ci', fn($e1, $e2) => $collatorCi->compare($e1, $e2));
$pdo->createCollation('locale_cmp_ci_desc', fn($e1, $e2) => $collatorCi->compare($e2, $e1));
}
else {
$comp = fn(string $e1, string $e2) => \iconv('UTF-8', 'ASCII//TRANSLIT', $e1) <=> \iconv('UTF-8', 'ASCII//TRANSLIT', $e2);
$pdo->sqliteCreateCollation('locale_cmp', fn($e1, $e2) => $comp);
$pdo->sqliteCreateCollation('locale_cmp_desc', fn($e2, $e1) => $comp);
$pdo->createCollation('locale_cmp', fn($e1, $e2) => $comp);
$pdo->createCollation('locale_cmp_desc', fn($e2, $e1) => $comp);
$compCi = fn(string $e1, string $e2) => strtoupper(\iconv('UTF-8', 'ASCII//TRANSLIT', $e1)) <=> strtoupper(\iconv('UTF-8', 'ASCII//TRANSLIT', $e2));
$pdo->sqliteCreateCollation('locale_cmp_ci', fn($e1, $e2) => $compCi);
$pdo->sqliteCreateCollation('locale_cmp_ci_desc', fn($e2, $e1) => $compCi);
$pdo->createCollation('locale_cmp_ci', fn($e1, $e2) => $compCi);
$pdo->createCollation('locale_cmp_ci_desc', fn($e2, $e1) => $compCi);
}
}
public static function registerPragma(PdoObject $pdo, array $pragmaList) : void
public static function registerPragma(\PDO $pdo, array $pragmaList) : void
{
$builder = new QueryBuilder\Sql\SqliteQueryBuilder();

View File

@ -5,13 +5,11 @@ namespace Ulmus;
use Psr\SimpleCache\CacheInterface;
use Ulmus\Adapter\AdapterInterface;
use Ulmus\Common\PdoObject;
class ConnectionAdapter
{
protected AdapterInterface $adapter;
protected PdoObject $pdo;
protected \PDO $pdo;
public function __construct(
public string $name = "default",
@ -63,7 +61,7 @@ class ConnectionAdapter
return $this->adapter;
}
public function pdo() : PdoObject
public function pdo() : \PDO
{
return $this->pdo ?? $this->connect()->pdo;
}

View File

@ -237,6 +237,11 @@ trait EntityTrait {
return true;
}
# Unexisting variable
if (! new \ReflectionClass($this)->hasProperty($name)) {
return false;
}
# Handling undefined 'nullable'
return new \ReflectionProperty($this, $name)->isInitialized($this);
}

8
src/Pdo/Mssql.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace Ulmus\Pdo;
class Mssql extends \PDO
{
use PdoTrait;
}

8
src/Pdo/Mysql.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace Ulmus\Pdo;
class Mysql extends \PDO\Mysql
{
use PdoTrait;
}

141
src/Pdo/PdoTrait.php Normal file
View File

@ -0,0 +1,141 @@
<?php
namespace Ulmus\Pdo;
use PDO,
PDOStatement;
trait PdoTrait
{
public bool $executionStatus;
public int $rowCount = 0;
public mixed $lastInsertId = null;
public \Closure $onClose;
public function select(string $sql, array $parameters = []): PDOStatement
{
# static::$dump && call_user_func_array(static::$dump, [ $this->fillDebugSqlParameters($sql, $parameters) ]);
try {
if (false !== ( $statement = $this->prepare($sql) )) {
$statement->setFetchMode(\PDO::FETCH_ASSOC);
$this->execute($statement, $parameters, false);
}
}
catch (\Throwable $e) {
throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters));
}
return $statement;
}
public function __destruct()
{
if ($this->onClose ?? null) {
call_user_func($this->onClose, $this);
}
}
public function runQuery(string $sql, array $parameters = []): ? static
{
# static::$dump && call_user_func_array(static::$dump, [ $this->fillDebugSqlParameters($sql, $parameters) ]);
try {
if (false !== ( $statement = $this->prepare($sql) )) {
return $this->execute($statement, $parameters, true);
}
}
catch(\PDOException $pdo) {
if (!str_starts_with($pdo->getMessage(), 'There is no active transaction')) {
throw $pdo;
}
}
catch (\Throwable $e) {
throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters), (int) $e->getCode(), $e);
}
return null;
}
public function runInsertQuery(string $sql, array $parameters = []) : ? static
{
return $this->runQuery($sql, $parameters);
}
public function runUpdateQuery(string $sql, array $parameters = []) : ? static
{
return $this->runQuery($sql, $parameters);
}
public function runDeleteQuery(string $sql, array $parameters = []) : ? static
{
return $this->runQuery($sql, $parameters);
}
public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true) : ? static
{
$this->executionStatus = false;
$this->lastInsertId = null;
try {
if (! $this->inTransaction()) {
$this->beginTransaction();
}
$this->bindVariables($statement, $parameters);
$this->executionStatus = $statement->execute();
if ( $this->executionStatus ) {
$this->lastInsertId = $this->lastInsertId();
$this->rowCount = $statement->rowCount();
if ( $commit ) {
$this->commit();
}
return $this;
}
else {
throw new \PDOException($statement->errorCode() . " - " . json_encode($statement->errorInfo()));
}
}
catch (\PDOException $e) {
$this->rollback();
throw $e;
}
}
protected function bindVariables(PDOStatement $statement, array &$parameters) : void
{
if ($parameters) {
if (array_is_list($parameters)) {
$parameters = array_combine(range(1, count($parameters)), array_values($parameters));
}
else {
foreach ($parameters as $key => $value) {
$type = match (strtolower(gettype($value))) {
"boolean", "integer" => Pdo::PARAM_INT,
"null" => Pdo::PARAM_NULL,
default => Pdo::PARAM_STR,
};
$statement->bindValue($key, $value, $type);
}
}
}
}
protected function fillDebugSqlParameters(string $sql, array $parameters) : string
{
$parameters = array_reverse($parameters);
return str_replace(array_keys($parameters), array_map(fn($e) => is_numeric($e) ? $e : (
is_bool($e) ? (int) $e : $this->quote($e)
), array_values($parameters)), $sql);
}
}

43
src/Pdo/SqlPdoTrait.php Normal file
View File

@ -0,0 +1,43 @@
<?php
namespace Ulmus\Pdo;
trait SqlPdoTrait
{
protected int $openedTransaction = 0;
public function beginTransaction(): bool
{
if ( 0 === $this->openedTransaction++ ) {
return $this->openTransaction();
}
return $this->exec("SAVEPOINT transaction_{$this->openedTransaction}") !== false;
}
public function commit() : bool
{
if ( 0 === --$this->openedTransaction) {
return parent::commit();
}
return $this->exec("RELEASE SAVEPOINT transaction_{$this->openedTransaction}") !== false;
}
public function rollback() : bool
{
if ($this->openedTransaction > 1) {
return $this->exec('ROLLBACK TO transaction_' . $this->openedTransaction--) !== false;
}
elseif ($this->openedTransaction-- === 1) {
return parent::rollback();
}
return false;
}
protected function openTransaction() : bool
{
return parent::beginTransaction();
}
}

8
src/Pdo/Sqlite.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace Ulmus\Pdo;
class Sqlite extends \PDO\Sqlite
{
use PdoTrait, SqlitePdoTrait;
}

View File

@ -0,0 +1,42 @@
<?php
namespace Ulmus\Pdo;
trait SqlitePdoTrait
{
use SqlPdoTrait;
public bool $inTransaction = false;
public function inTransaction(): bool
{
return $this->openedTransaction > 0;
}
public function commit(): bool
{
if ( 0 === --$this->openedTransaction) {
return $this->exec("COMMIT") !== false;
}
return $this->exec("RELEASE SAVEPOINT transaction_{$this->openedTransaction}") !== false;
}
public function rollback() : bool
{
if ($this->openedTransaction > 1) {
return $this->exec('ROLLBACK TO transaction_' . $this->openedTransaction--) !== false;
}
elseif ($this->openedTransaction-- === 1) {
# We must do it manually since opening a transaction manually stucks PDO into thinking we've got no transaction opened
return $this->exec('ROLLBACK') !== false;
}
return false;
}
protected function openTransaction(): bool
{
return $this->exec("BEGIN IMMEDIATE TRANSACTION") !== false;
}
}