Compare commits

..

No commits in common. "master" and "attributes" have entirely different histories.

172 changed files with 2638 additions and 4383 deletions

View File

@ -6,7 +6,7 @@
"authors": [
{
"name": "Dave Mc Nicoll",
"email": "info@mcnd.ca"
"email": "mcndave@gmail.com"
}
],
"require": {

View File

@ -4,7 +4,7 @@ Welcome to the official Ulmus documentation.
## Quick start
Creating a simple user exemple entity:
Creating a simple user entity:
*/app/entity/user.php*
```php
@ -21,19 +21,29 @@ class User
{
use \Ulmus\EntityTrait;
#[Field\Id]
/**
* @Id
*/
public int $id;
#[Field]
/**
* @Field
*/
public string $fullname;
#[Field]
/**
* @Field
*/
public ? string $email;
#[Field]
/**
* @Field('name' => 'is_admin')
*/
public bool $isAdmin = false;
#[Field\Datetime]
/**
* @DateTime
*/
public Datetime $birthday;
}
```

View File

@ -1,7 +0,0 @@
# Search Request
Ulmus comes with a simple search request processor which allows both flexibility and simplicity.
## Quick start
Creating a simple user exemple entity:

View File

@ -15,7 +15,17 @@ interface AdapterInterface {
public function connect() : object /* | PdoObject|mixed */;
public function buildDataSourceName() : string;
public function setup(array $configuration) : void;
public function escapeIdentifier(string $segment, int $type) : string;
public function defaultEngine() : ? string;
public function writableValue(/* mixed */ $value); /*: mixed*/
/* public function databaseName() : string;
public function mapFieldType(FieldDefinition $field) : string;
public function schemaTable(string $databaseName, string $tableName) /*: object|EntityCollection
*/
public function repositoryClass() : string;
public function queryBuilderClass() : string;
public function tableSyntax() : array;
}

View File

@ -0,0 +1,95 @@
<?php
namespace Ulmus\Adapter;
use Ulmus\{ConnectionAdapter, Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder};
trait DefaultAdapterTrait
{
public function repositoryClass() : string
{
return Repository::class;
}
public function queryBuilderClass() : string
{
return QueryBuilder::class;
}
public function tableSyntax() : array
{
return [
'ai' => "AUTO_INCREMENT",
'pk' => "PRIMARY KEY",
'unsigned' => "UNSIGNED",
];
}
public function databaseName() : string
{
return $this->database;
}
public function schemaTable(ConnectionAdapter $parent, $databaseName, string $tableName) /* : ? object */
{
return Table::repository(Repository::DEFAULT_ALIAS, $parent)->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName);
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$type = $field->type;
$length = $field->length;
if ( is_a($type, Entity\Field\Date::class, true) ) {
$type = "DATE";
}
elseif ( is_a($type, Entity\Field\Time::class, true) ) {
$type = "TIME";
}
elseif ( is_a($type, \DateTime::class, true) ) {
$type = "DATETIME";
}
switch($type) {
case "bool":
$type = "TINYINT";
$length = 1;
break;
case "array":
case "string":
if ($length && $length <= 255) {
$type = "VARCHAR";
break;
}
elseif (! $length || ( $length <= 65535 ) ) {
$type = "TEXT";
}
elseif ( $length <= 16777215 ) {
$type = "MEDIUMTEXT";
}
elseif ($length <= 4294967295) {
$type = "LONGTEXT";
}
else {
throw new \Exception("A column with size bigger than 4GB cannot be created.");
}
# Length is unnecessary on TEXT fields
unset($length);
break;
case "float":
$type = "DOUBLE";
break;
default:
$type = strtoupper($type);
break;
}
return $typeOnly ? $type : $type . ( isset($length) ? "($length" . ( ! empty($precision) ? ",$precision" : "" ) . ")" : "" );
}
}

View File

@ -9,19 +9,10 @@ use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Ulmus;
use Ulmus\Migration\FieldDefinition;
use Ulmus\{ConnectionAdapter,
Entity\InformationSchema\Table,
Migration\MigrateInterface,
Migration\SqlMigrationTrait,
Repository,
QueryBuilder};
use Ulmus\{Entity\InformationSchema\Table, Repository, QueryBuilder};
class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
use SqlAdapterTrait, SqlMigrationTrait;
const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment',
];
class MsSQL implements AdapterInterface {
use DefaultAdapterTrait;
const DSN_PREFIX = "sqlsrv";
@ -44,7 +35,7 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
public string $database;
public string $username;
public string $password;
public string $traceFile;
@ -56,9 +47,7 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
public function __construct(
?string $server = null,
?string $database = null,
#[\SensitiveParameter]
?string $username = null,
#[\SensitiveParameter]
?string $password = null,
?int $port = null
) {
@ -91,7 +80,7 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
$pdo->setAttribute(\PDO::SQLSRV_ATTR_ENCODING, \PDO::SQLSRV_ENCODING_UTF8);
}
catch(\PDOException $ex){
catch(PDOException $ex){
throw $ex;
}
finally {
@ -210,18 +199,10 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
$this->traceOn = $configuration['trace_on'];
}
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$mapper = new MsSQLFieldMapper($field);
return $typeOnly ? $mapper->type : $mapper->render();
}
public static function escapeIdentifier(string $segment, int $type) : string
public function escapeIdentifier(string $segment, int $type) : string
{
switch($type) {
default:
case static::IDENTIFIER_SCHEMA:
case static::IDENTIFIER_DATABASE:
case static::IDENTIFIER_TABLE:
@ -233,6 +214,26 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
}
}
public function writableValue(mixed $value) /*: mixed*/
{
switch (true) {
case $value instanceof \UnitEnum:
return Ulmus::convertEnum($value);
case is_object($value):
return Ulmus::convertObject($value);
case is_array($value):
return json_encode($array);
case is_bool($value):
return (int) $value;
}
return $value;
}
public function defaultEngine(): ? string
{
return null;
@ -245,15 +246,6 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
public function queryBuilderClass() : string
{
return QueryBuilder\Sql\MssqlQueryBuilder::class;
}
public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object
{
return \Ulmus\Entity\Mssql\Table::repository(Repository::DEFAULT_ALIAS, $adapter)
->select(\Ulmus\Common\Sql::raw('this.*'))
->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
->or($this->escapeIdentifier('table_catalog', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName);
return QueryBuilder\MssqlQueryBuilder::class;
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace Ulmus\Adapter;
use Ulmus\Migration\FieldDefinition;
class MsSQLFieldMapper extends SqlFieldMapper
{
public function map() : void
{
parent::map();
if (in_array($this->type, [ 'CHAR', 'VARCHAR', 'TEXT', ])) {
$this->type = "N" . $this->type;
};
}
/* @TODO !
public function postProcess() : void
{
if (
in_array($this->type, [ 'BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB', 'JSON', 'TEXT', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'GEOMETRY' ]) &&
! is_object($this->field->default ?? false) # Could be a functional default, which would now be valid
) {
unset($this->field->default);
}
}
* */
}

View File

@ -2,34 +2,28 @@
namespace Ulmus\Adapter;
use Ulmus\ConnectionAdapter;
use Ulmus\Entity\Mysql\Table;
use Ulmus\Migration\FieldDefinition;
use Ulmus\Migration\MigrateInterface;
use Ulmus\Migration\SqlMigrationTrait;
use Ulmus\QueryBuilder\Sql;
use Ulmus\Entity\InformationSchema\Table;
use Ulmus\QueryBuilder;
use Ulmus\Repository;
use Ulmus\Common\PdoObject;
use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Repository;
use Ulmus\Ulmus;
use Ulmus\Migration\FieldDefinition;
class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
use SqlAdapterTrait, SqlMigrationTrait;
const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment', 'update',
];
class MySQL implements AdapterInterface {
use DefaultAdapterTrait;
const DSN_PREFIX = "mysql";
public string $hostname;
public string $database;
protected string $username;
protected string $password;
public string $username;
public string $password;
public string $charset = "utf8mb4";
public ? string $socket;
@ -39,9 +33,7 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
public function __construct(
?string $hostname = null,
?string $database = null,
#[\SensitiveParameter]
?string $username = null,
#[\SensitiveParameter]
?string $password = null,
?int $port = null,
?string $charset = null
@ -81,7 +73,7 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
$pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
}
catch(\PDOException $ex){
catch(PDOException $ex){
throw $ex;
}
finally {
@ -143,17 +135,9 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
}
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$mapper = new MySQLFieldMapper($field);
return $typeOnly ? $mapper->type : $mapper->render();
}
public static function escapeIdentifier(string $segment, int $type) : string
public function escapeIdentifier(string $segment, int $type) : string
{
switch($type) {
default:
case static::IDENTIFIER_DATABASE:
case static::IDENTIFIER_TABLE:
case static::IDENTIFIER_FIELD:
@ -164,43 +148,27 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
}
}
public function writableValue(mixed $value) : mixed
{
switch (true) {
case $value instanceof \UnitEnum:
return Ulmus::convertEnum($value);
case is_object($value):
return Ulmus::convertObject($value);
case is_array($value):
return json_encode($value);
case is_bool($value):
return (int) $value;
}
return $value;
}
public function defaultEngine(): ? string
{
return "InnoDB";
}
public function queryBuilderClass() : string
{
return Sql\MysqlQueryBuilder::class;
}
public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object
{
return Table::repository(Repository::DEFAULT_ALIAS, $adapter)
->select(\Ulmus\Common\Sql::raw('this.*'))
->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName);
}
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
{
if ($field['action'] === 'add') {
if (! empty($field['previous'])) {
$position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']->field, AdapterInterface::IDENTIFIER_FIELD));
}
else {
$position = "FIRST";
}
}
return implode(" ", array_filter([
strtoupper($field['action']),
$this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
$definition->getSqlType(),
$definition->getSqlParams(),
$position ?? null,
]));
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace Ulmus\Adapter;
use Ulmus\Migration\FieldDefinition;
class MySQLFieldMapper extends SqlFieldMapper
{
public function map() : void
{
$type = $this->field->type;
$length = $this->field->length;
if ( enum_exists($type) ) {
# Haven't found a better way yet to check for BackendEnum without an instance of the object
if ( ! method_exists($type, 'tryFrom') ) {
throw new \Ulmus\Exception\BackedEnumRequired(sprintf("You must define your enum as a BackedEnum instead of an UnitEnum for field '%s'.", $this->field->getColumnName()));
}
$this->length = implode(',', array_map(fn($e) => MySQL::escapeIdentifier($e->value, MySQL::IDENTIFIER_VALUE) , $type::cases()));
$this->type = "ENUM";
}
else {
parent::map();
}
}
public function postProcess() : void
{
if (
in_array($this->type, [ 'BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB', 'JSON', 'TEXT', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'GEOMETRY' ]) &&
! is_object($this->field->default ?? false) # Could be a functional default, which would now be valid
) {
unset($this->field->default);
}
}
}

View File

@ -2,46 +2,46 @@
namespace Ulmus\Adapter;
use Collator;
use Ulmus\Common\PdoObject;
use Ulmus\ConnectionAdapter;
use Ulmus\Entity;
use Ulmus\Entity\Sqlite\Table;
use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Migration\FieldDefinition;
use Ulmus\{Migration\MigrateInterface, Migration\SqlMigrationTrait, Repository, QueryBuilder};
use Ulmus\{Repository, QueryBuilder, Ulmus};
class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
use SqlAdapterTrait, SqlMigrationTrait;
const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment', 'collate nocase', 'collate binary', 'collate rtrim',
];
class SQLite implements AdapterInterface {
use DefaultAdapterTrait;
const DSN_PREFIX = "sqlite";
public string $path;
public array $pragma;
public function __construct(
public null|string $path = null,
public null|array $pragmaBegin = null,
public null|array $pragmaClose = null,
) { }
? string $path = null,
? array $pragma = null
) {
if ($path !== null) {
$this->path = $path;
}
if ($pragma !== null) {
$this->pragma = $pragma;
}
}
public function connect() : PdoObject
{
try {
#$pdo = new PdoObject($this->buildDataSourceName(), null, null);
$pdo = new PdoObject\SqlitePdoObject($this->buildDataSourceName(), null, null);
$pdo = new PdoObject($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) {
static::registerPragma($obj, $this->pragmaClose);
};
$this->exportFunctions($pdo);
$this->exportCollations($pdo);
$this->registerPragma($pdo, $this->pragmaBegin);
}
catch(\PDOException $ex){
catch(PDOException $ex){
throw $ex;
}
@ -58,15 +58,13 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
public function setup(array $configuration) : void
{
$this->path = $configuration['path'] ?? "";
$this->pragmaBegin = array_filter($configuration['pragma_begin'] ?? []);
$this->pragmaClose = array_filter($configuration['pragma_close'] ?? []);
$this->pragma = $configuration['pragma'] ?? [];
}
# https://sqlite.org/lang_keywords.html
public static function escapeIdentifier(string $segment, int $type) : string
public function escapeIdentifier(string $segment, int $type) : string
{
switch($type) {
default:
case static::IDENTIFIER_DATABASE:
case static::IDENTIFIER_TABLE:
case static::IDENTIFIER_FIELD:
@ -89,16 +87,15 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
return substr($base, 0, strrpos($base, '.') ?: strlen($base));
}
public function schemaTable(ConnectionAdapter $adapter, string $databaseName, string $tableName) : null|object
public function schemaTable(string $databaseName, string $tableName) : null|object
{
return Entity\Sqlite\Table::repository(Repository::DEFAULT_ALIAS, $adapter)
->select(\Ulmus\Common\Sql::raw('this.*'))
->loadOneFromField(Entity\Sqlite\Table::field('tableName'), $tableName);
return Table::repository()->loadOneFromField(Table::field('tableName'), $tableName);
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$type = $field->type;
$length = $field->length;
if ( is_a($type, Entity\Field\Date::class, true) || is_a($type, Entity\Field\Time::class, true) || is_a($type, \DateTime::class, true) ) {
@ -116,10 +113,6 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
break;
case "array":
$type = "JSON";
$length = null;
break;
case "string":
$type = "TEXT";
$length = null;
@ -135,11 +128,26 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
}
}
if (in_array($type, [ 'JSON', 'TEXT', 'BLOB', 'GEOMETRY' ])) {
unset($field->default);
return $typeOnly ? $type : $type . ( $length ? "($length" . ( isset($precision) ? ",$precision" : "" ) . ")" : "" );
}
public function writableValue(mixed $value) /*: mixed*/
{
switch (true) {
case $value instanceof \UnitEnum:
return Ulmus::convertEnum($value);
case is_object($value):
return Ulmus::convertObject($value);
case is_array($value):
return json_encode($value);
case is_bool($value):
return (int) $value;
}
return $typeOnly ? $type : $type . ( $length ? "($length" . ")" : "" );
return $value;
}
public function tableSyntax() : array
@ -158,7 +166,7 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
public function queryBuilderClass() : string
{
return QueryBuilder\Sql\SqliteQueryBuilder::class;
return QueryBuilder\SqliteQueryBuilder::class;
}
public function exportFunctions(PdoObject $pdo) : void
@ -167,14 +175,6 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
$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);
@ -193,72 +193,5 @@ 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);
}
public function exportCollations(PdoObject $pdo) : void
{
if ( class_exists('Locale') ) {
# @TODO debug this, case-sensitivity not working properly !!
$collator = new Collator(\Locale::getDefault());
$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));
$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));
}
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);
$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);
}
}
public static function registerPragma(PdoObject $pdo, array $pragmaList) : void
{
$builder = new QueryBuilder\Sql\SqliteQueryBuilder();
foreach($pragmaList as $pragma) {
list($key, $value) = explode('=', $pragma) + [ null, null ];
$sql = $builder->pragma($key, $value)->render();
$query = $pdo->query($sql);
if ( ! $query->execute() ) {
throw new \InvalidArgumentException(sprintf("Pragma query could not be executed : %s", $sql));
}
$builder->reset();
}
}
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
{
return implode(" ", [
strtoupper($field['action']),
$this->escapeIdentifier($definition->getSqlName(), static::IDENTIFIER_FIELD),
$definition->getSqlType(),
$definition->getSqlParams(),
]);
}
public function splitAlterQuery() : bool
{
return true;
}
}
}

View File

@ -1,9 +0,0 @@
<?php
namespace Ulmus\Adapter;
interface SqlAdapterInterface
{
public static function escapeIdentifier(string $segment, int $type) : string;
public function defaultEngine() : ? string;
}

View File

@ -1,62 +0,0 @@
<?php
namespace Ulmus\Adapter;
use Ulmus\{Common\Sql,
ConnectionAdapter,
Entity\InformationSchema\Table,
Migration\FieldDefinition,
Repository,
QueryBuilder\SqlQueryBuilder,
Ulmus};
trait SqlAdapterTrait
{
public function repositoryClass() : string
{
return Repository::class;
}
public function queryBuilderClass() : string
{
return SqlQueryBuilder::class;
}
public function tableSyntax() : array
{
return [
'ai' => "AUTO_INCREMENT",
'pk' => "PRIMARY KEY",
'unsigned' => "UNSIGNED",
];
}
public function databaseName() : string
{
return $this->database;
}
public function whitelistAttributes(array &$parameters) : void
{
$parameters = array_intersect_key($parameters, array_flip(static::ALLOWED_ATTRIBUTES));
}
public function writableValue(mixed $value) : mixed
{
switch (true) {
case $value instanceof \UnitEnum:
return Ulmus::convertEnum($value);
case is_object($value):
return Ulmus::convertObject($value);
case is_array($value):
return json_encode($value);
case is_bool($value):
return (int) $value;
}
return $value;
}
}

View File

@ -1,99 +0,0 @@
<?php
namespace Ulmus\Adapter;
use Ulmus\Migration\FieldDefinition;
use Ulmus\Entity;
class SqlFieldMapper
{
public string $type;
public string $length;
public function __construct(
public FieldDefinition $field,
) {
$this->map();
}
public function map() : void
{
$type = $this->field->type;
$length = $this->field->length;
if ( is_a($type, Entity\Field\Date::class, true) ) {
$type = "DATE";
}
elseif ( is_a($type, Entity\Field\Time::class, true) ) {
$type = "TIME";
}
elseif ( is_a($type, \DateTime::class, true) ) {
$type = "DATETIME";
}
elseif ( enum_exists($type) ) {
# Haven't found a better way yet to check for BackendEnum without an instance of the object
if ( ! method_exists($type, 'tryFrom') ) {
throw new \Ulmus\Exception\BackedEnumRequired(sprintf("You must define your enum as a BackedEnum instead of an UnitEnum for field '%s'.", $this->field->getColumnName()));
}
$type = "string";
}
switch($type) {
case "bool":
$this->type = "TINYINT";
$this->length = 1;
break;
case "array":
$this->type = "JSON";
break;
case "string":
if ($length && $length <= 255) {
$this->type = "VARCHAR";
$this->length = $length;
break;
}
elseif (! $length || ( $length <= 65535 ) ) {
$this->type = "TEXT";
}
elseif ( $length <= 16777215 ) {
$this->type = "MEDIUMTEXT";
}
elseif ($length <= 4294967295) {
$this->type = "LONGTEXT";
}
else {
throw new \Exception("A column with a length bigger than 4GB cannot be created.");
}
# Length is unnecessary on TEXT fields
unset($length);
break;
case "float":
$this->type = "DOUBLE";
default:
if ($length) {
$this->length = $length;
}
$this->type ??= strtoupper($type);
}
$this->postProcess();
}
public function render() : string
{
return $this->type . ( isset($this->length) ? "($this->length)" : "" );
}
public function postProcess() : void
{
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Ulmus\Annotation\Classes;
class Collation implements \Notes\Annotation {
}

View File

@ -0,0 +1,7 @@
<?php
namespace Ulmus\Annotation\Classes;
class Method implements \Notes\Annotation {
}

View File

@ -0,0 +1,27 @@
<?php
namespace Ulmus\Annotation\Classes;
class Table implements \Notes\Annotation {
public string $name;
public string $database;
public string $schema;
public string $adapter;
public string $engine;
public function __construct($name = null, $engine = null)
{
if ( $name !== null ) {
$this->name = $name;
}
if ( $engine !== null ) {
$this->engine = $engine;
}
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Ulmus\Annotation\Property;
class Field implements \Notes\Annotation {
public string $type;
public string $name;
public int $length;
public int $precision;
public array $attributes = [];
public bool $nullable;
public /* mixed */ $default;
public bool $readonly = false;
public function __construct(? string $type = null, ? int $length = null)
{
if ( $type !== null ) {
$this->type = $type;
}
if ( $length !== null ) {
$this->length = $length;
}
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Bigint extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "bigint", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Blob extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "blob", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class CreatedAt extends \Ulmus\Annotation\Property\Field {
public function __construct()
{
$this->nullable = false;
$this->type = "timestamp";
$this->attributes['default'] = "CURRENT_TIMESTAMP";
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Date extends \Ulmus\Annotation\Property\Field {
public function __construct(? string $type = "date", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Datetime extends \Ulmus\Annotation\Property\Field {
public function __construct(? string $type = "datetime", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Ulmus\Annotation\Property\Field;
/**
* Since we need consistancy between the declaration of our ID and FK fields, it
* was decided to extend the Id class instead of Field for this case.
*/
class ForeignKey extends PrimaryKey {
public function __construct(? string $type = null, ? int $length = null)
{
parent::__construct($type, $length);
unset($this->nullable);
$this->attributes['primary_key'] = false;
$this->attributes['auto_increment'] = false;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Id extends \Ulmus\Annotation\Property\Field\PrimaryKey {
public function __construct()
{
$this->attributes['unsigned'] = true;
$this->attributes['auto_increment'] = true;
parent::__construct('bigint');
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Longblob extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "longblob", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Longtext extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "longtext", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Mediumblob extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "mediumblob", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Mediumtext extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "mediumtext", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class PrimaryKey extends \Ulmus\Annotation\Property\Field {
public function __construct(? string $type = null, ? int $length = null)
{
$this->nullable = false;
$this->attributes['primary_key'] = true;
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Text extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "text", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Time extends \Ulmus\Annotation\Property\Field {
public function __construct(? string $type = "time", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Blob extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "tinyblob", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Tinyint extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "tinyint", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class UpdatedAt extends \Ulmus\Annotation\Property\Field {
public function __construct()
{
$this->nullable = true;
$this->type = "timestamp";
$this->attributes['update'] = "CURRENT_TIMESTAMP";
$this->attributes['default'] = null;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Ulmus\Annotation\Property;
class Filter implements \Notes\Annotation {
public string $method;
public function __construct(string $method = null)
{
if ( $method !== null ) {
$this->method = $method;
}
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Ulmus\Annotation\Property;
class FilterJoin implements \Notes\Annotation {
public string $method;
public function __construct(string $method = null)
{
if ( $method !== null ) {
$this->method = $method;
}
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Ulmus\Annotation\Property;
class GroupBy implements \Notes\Annotation {
public array $fields = [];
public function __construct(...$field)
{
if ( $field ) {
$this->fields = $field;
}
}
}

View File

@ -0,0 +1,5 @@
<?php
namespace Ulmus\Annotation\Property;
class Having extends Where {}

View File

@ -0,0 +1,31 @@
<?php
namespace Ulmus\Annotation\Property;
class Join implements \Notes\Annotation {
public string $type;
public string|Stringable $key;
public string|Stringable $foreignKey;
public string $entity;
public string $alias;
public function __construct(? string $type = null, /*? string|Stringable*/ $key = null, /*? string|Stringable*/ $foreignKey = null)
{
if ($type !== null) {
$this->type = $type;
}
if ($key !== null) {
$this->key = $key;
}
if ($foreignKey !== null) {
$this->foreignKey = $foreignKey;
}
}
}

View File

@ -0,0 +1,5 @@
<?php
namespace Ulmus\Annotation\Property;
class On extends Where {}

View File

@ -0,0 +1,14 @@
<?php
namespace Ulmus\Annotation\Property;
use Ulmus\Query;
class OrWhere extends Where {
public function __construct(/* ? Stringable */ $field = null, $value = null, ? string $operator = null, ? string $condition = null)
{
parent::__construct($field, $value, $operator, Query\Where::CONDITION_OR);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Ulmus\Annotation\Property;
class OrderBy implements \Notes\Annotation {
public string $field;
public string $order = "ASC";
public function __construct(string $field = null, string $order = null)
{
if ( $field !== null ) {
$this->field = $field;
}
if ( $order !== null ) {
$this->order = $order;
}
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Ulmus\Annotation\Property;
class Relation implements \Notes\Annotation {
public string $type;
public /*stringable*/ $key;
public /* callable */ $generateKey;
public /*stringable*/ $foreignKey;
public array $foreignKeys;
public string $bridge;
public /*stringable*/ $bridgeKey;
public /*stringable*/ $bridgeForeignKey;
public string $entity;
public string $join;
public string $function = "loadAll";
public function __construct(string $type = null)
{
if ( $type !== null ) {
$this->type = $type;
}
}
public function entity() {
try {
$e = $this->entity;
} catch (\Throwable $ex) {
throw new \Exception("Your @Relation annotation seems to be missing an `entity` entry.");
}
return new $e();
}
public function bridge() {
$e = $this->bridge;
return new $e();
}
public function normalizeType() : string
{
return strtolower(str_replace(['-', '_', ' '], '', $this->type));
}
public function isOneToOne() : bool
{
return $this->normalizeType() === 'onetoone';
}
public function isOneToMany() : bool
{
return $this->normalizeType() === 'onetomany';
}
public function isManyToMany() : bool
{
return $this->normalizeType() === 'manytomany';
}
public function function() : string
{
if ($this->function) {
return $this->function;
}
elseif ($this->isOneToOne()) {
return 'load';
}
return 'loadAll';
}
public function hasBridge() : bool
{
return isset($this->bridge);
}
}

View File

@ -0,0 +1,5 @@
<?php
namespace Ulmus\Annotation\Property\Relation;
class Ignore implements \Notes\Annotation {}

View File

@ -0,0 +1,19 @@
<?php
namespace Ulmus\Annotation\Property;
class Virtual extends Field {
public bool $readonly = true;
public \Closure $closure;
public string $method;
public function __construct(? \Closure $closure = null)
{
if ( $closure !== null ) {
$this->closure = $closure;
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Ulmus\Annotation\Property;
use Ulmus\Query;
class Where implements \Notes\Annotation {
public /* stringable */ $field;
public $value;
public string $operator;
public string $condition;
public function __construct(/* ? Stringable */ $field = null, $value = null, ? string $operator = null, ? string $condition = null)
{
if ( $field !== null ) {
$this->field = $field;
}
if ( $value !== null ) {
$this->value = $value;
}
$this->operator = $operator !== null ? $operator : Query\Where::OPERATOR_EQUAL;
$this->condition = $condition !== null ? $condition : Query\Where::CONDITION_AND;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Ulmus\Annotation\Property;
class WithJoin implements \Notes\Annotation {
protected array $joins;
public function __construct(/*Stringable|array|null*/ $joins = null)
{
if ( $joins ) {
$this->joins = (array)$joins;
}
}
}

View File

@ -3,26 +3,13 @@
namespace Ulmus\Attribute;
use Ulmus\Common\EntityField;
use Ulmus\Repository;
class Attribute
{
public static function handleArrayField(null|\Stringable|string|array $field, null|string|bool $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : mixed
public static function handleArrayField(null|\Stringable|string|array $field) : mixed
{
if ( is_array($field) ) {
if (count($field) < 2) {
throw new \RuntimeException(
sprintf("Array field must be formed of at least two things, a class name, and a property. (received %s)", json_encode($field))
);
}
$class = array_shift($field);
$field[1] ??= $alias;
if (is_array($field[0])) {
$field[] = $separator;
return $class::fields(...$field);
}
return $class::field(...$field);
}

View File

@ -1,14 +0,0 @@
<?php
namespace Ulmus\Attribute;
enum ConstrainActionEnum : string
{
case Cascade = 'cascade';
case NoAction = 'noaction';
case Restrict = 'restrict';
case SetNull = 'setnull';
}

View File

@ -1,16 +0,0 @@
<?php
namespace Ulmus\Attribute;
enum IndexTypeEnum : string
{
case Primary = 'primary';
case Index = 'index';
case Unique = 'unique';
case Spatial = 'spatial';
case Fulltext = 'fulltext';
}

View File

@ -1,8 +0,0 @@
<?php
namespace Ulmus\Attribute\Obj;
interface AdapterAttributeInterface
{
public function adapter() : false|string;
}

View File

@ -4,7 +4,5 @@ namespace Ulmus\Attribute\Obj;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Collation {
public function __construct(
public string $name = ""
) {}
}

View File

@ -1,23 +0,0 @@
<?php
namespace Ulmus\Attribute\Obj;
use Ulmus\Attribute\Attribute;
use Ulmus\Attribute\ConstrainActionEnum;
use Ulmus\Attribute\IndexTypeEnum;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class Constrain {
public function __construct(
public array $columns,
public null|IndexTypeEnum $type = null,
public null|string $name = null,
public null|array|string|\Stringable $foreignKey = null,
public null|array|string|\Stringable $references = null,
public ConstrainActionEnum $onDelete = ConstrainActionEnum::NoAction,
public ConstrainActionEnum $onUpdate = ConstrainActionEnum::NoAction,
) {
$this->foreignKey = Attribute::handleArrayField($this->foreignKey, false);
$this->references = Attribute::handleArrayField($this->references, false);
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace Ulmus\Attribute\Obj;
use Ulmus\Attribute\IndexTypeEnum;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class Index {
public function __construct(
public string|array $column,
public IndexTypeEnum $type = IndexTypeEnum::Unique,
public null|string $name = null,
) {}
}

View File

@ -3,18 +3,12 @@
namespace Ulmus\Attribute\Obj;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Table implements AdapterAttributeInterface {
class Table {
public function __construct(
public ? string $name = null,
public ? string $database = null,
public ? string $schema = null,
public ? string $adapter = null,
public ? string $engine = null,
public string $description = "",
) {}
public function adapter() : false|string
{
return $this->adapter ?: false;
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Collation {
public function __construct(
public string $name = ""
) {}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Binary extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "binary",
public null|int|string $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
public string $description = "",
) {}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Char extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "char",
public null|int|string $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
public string $description = "",
) {}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Double extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "float",
public null|int|string $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
public string $description = "",
) {}
}

View File

@ -2,9 +2,6 @@
namespace Ulmus\Attribute\Property\Field;
use Ulmus\Attribute\Attribute;
use Ulmus\Attribute\ConstrainActionEnum;
/**
* Since we need consistancy between the declaration of our ID and FK fields, it
* was decided to extend the PK class instead of Field for this case.
@ -13,7 +10,7 @@ use Ulmus\Attribute\ConstrainActionEnum;
class ForeignKey extends PrimaryKey {
public function __construct(
public ? string $name = null,
public ? string $type = null,
public ? string $type = 'bigint',
public null|int|string $length = null,
public ? int $precision = null,
public array $attributes = [
@ -23,10 +20,5 @@ class ForeignKey extends PrimaryKey {
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
public null|string $relation = null,
public ConstrainActionEnum $onDelete = ConstrainActionEnum::NoAction,
public ConstrainActionEnum $onUpdate = ConstrainActionEnum::NoAction,
) {
#$this->references = Attribute::handleArrayField($this->references, false);
}
) {}
}

View File

@ -16,6 +16,6 @@ class Id extends \Ulmus\Attribute\Property\Field {
],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = true,
public bool $readonly = false,
) {}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Json extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "json",
public null|int|string $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
public string $description = "",
) {}
}

View File

@ -5,12 +5,8 @@ namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Time extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "time",
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
public function __construct(? string $type = "time", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -5,12 +5,8 @@ namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Timestamp extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "timestamp",
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
public function __construct(? string $type = "timestamp", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Varbinary extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "varbinary",
public null|int|string $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
public string $description = "",
) {}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Varchar extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "varchar",
public null|int|string $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
public string $description = "",
) {}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Year extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "year",
public null|int|string $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
public string $description = "",
) {}
}

View File

@ -6,5 +6,6 @@ namespace Ulmus\Attribute\Property;
class Filter {
public function __construct(
public string $method = ""
) {}
)
{}
}

View File

@ -1,12 +0,0 @@
<?php
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\IndexTypeEnum;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Index {
public function __construct(
public IndexTypeEnum $type = IndexTypeEnum::Index,
) {}
}

View File

@ -3,34 +3,17 @@
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\Attribute;
use Ulmus\Repository;
#[\Attribute]
class Join implements ResettablePropertyInterface {
class Join {
public function __construct(
public string $type,
public null|string|\Stringable|array $key = null,
public null|string|\Stringable|array $foreignKey = null,
public null|string|\Stringable $foreignKey = null,
public null|string $entity = null,
public null|string $alias = null,
) {
$this->key = Attribute::handleArrayField($this->key);
$this->foreignKey = Attribute::handleArrayField($this->foreignKey);
}
## AWAITING THE Closures in constant expression from PHP 8.5 !
public function foreignKey(? Repository $repository = null) : mixed
{
if (is_string($this->foreignKey)) {
if (str_starts_with($this->foreignKey, 'fn(') && str_contains($this->foreignKey, '=>')) {
$closure = eval("return {$this->foreignKey};");
return $repository ? $closure($repository) : $closure;
}
return $this->entity::field($this->foreignKey, $this->alias);
}
return $this->foreignKey;
}
}

View File

@ -10,11 +10,10 @@ class OrWhere extends Where {
public function __construct(
public string|\Stringable|array $field,
public mixed $value = null,
public string $operator = Query\Where::OPERATOR_EQUAL,
public string $condition = Query\Where::CONDITION_OR,
public string|\Stringable|array|null $fieldValue = null,
public null|array|\Closure $generateValue = null,
public null|string $operator = null,
public null|string $condition = Query\Where::CONDITION_OR,
) {
parent::__construct($field, $value, $operator, $condition, $fieldValue, $generateValue);
$this->key = Attribute::handleArrayField($this->key);
}
}

View File

@ -3,14 +3,13 @@
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\Attribute;
use Ulmus\Repository;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Relation implements ResettablePropertyInterface {
class Relation {
public function __construct(
public Relation\RelationTypeEnum|string $type,
public string $type,
public \Stringable|string|array $key = "",
public null|\Closure|array $generateKey = null,
public null|\Closure $generateKey = null,
public null|\Stringable|string|array $foreignKey = null,
public null|\Stringable|string|array $foreignField = null,
public array $foreignKeys = [],
@ -21,8 +20,7 @@ class Relation implements ResettablePropertyInterface {
public null|\Stringable|string|array $field = null,
public null|string $entity = null,
public null|string $join = null,
public null|string $function = null,
public null|string $alias = null,
public string $function = "loadAll",
) {
$this->key = Attribute::handleArrayField($this->key);
$this->foreignKey = Attribute::handleArrayField($this->foreignKey);
@ -31,13 +29,14 @@ class Relation implements ResettablePropertyInterface {
$this->bridgeField = Attribute::handleArrayField($this->bridgeField);
$this->bridgeForeignKey = Attribute::handleArrayField($this->bridgeForeignKey);
$this->field = Attribute::handleArrayField($this->field);
}
public function entity() {
try {
$e = $this->entity;
} catch (\Throwable $ex) {
throw new \Exception("Your @Relation attribute seems to be missing an `entity` entry.");
throw new \Exception("Your @Relation annotation seems to be missing an `entity` entry.");
}
return new $e();
@ -56,17 +55,17 @@ class Relation implements ResettablePropertyInterface {
public function isOneToOne() : bool
{
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::oneToOne : $this->normalizeType() === 'onetoone';
return $this->normalizeType() === 'onetoone';
}
public function isOneToMany() : bool
{
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::oneToMany : $this->normalizeType() === 'onetomany';
return $this->normalizeType() === 'onetomany';
}
public function isManyToMany() : bool
{
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::manyToMany : $this->normalizeType() === 'manytomany';
return $this->normalizeType() === 'manytomany';
}
public function function() : string
@ -75,7 +74,7 @@ class Relation implements ResettablePropertyInterface {
return $this->function;
}
elseif ($this->isOneToOne()) {
return 'loadOne';
return 'load';
}
return 'loadAll';
@ -85,20 +84,4 @@ class Relation implements ResettablePropertyInterface {
{
return (bool) $this->bridge;
}
## AWAITING THE Closures in constant expression from PHP 8.5 !
public function foreignKey(? Repository $repository = null) : mixed
{
if (is_string($this->foreignKey)) {
if (str_starts_with($this->foreignKey, 'fn(') && str_contains($this->foreignKey, '=>')) {
$closure = eval("return {$this->foreignKey};");
return $repository ? $closure($repository) : $closure;
}
return $this->entity::field($this->foreignKey, $this->alias);
}
return $this->foreignKey;
}
}

View File

@ -3,10 +3,4 @@
namespace Ulmus\Attribute\Property\Relation;
#[\Attribute]
class Ignore {
public function __construct(
public bool $ignoreExport = false,
) {}
}
class Ignore {}

View File

@ -1,42 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Relation;
use Ulmus\Attribute\Property\Relation;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class ManyToMany extends Relation
{
public function __construct(
public \Stringable|string|array $key = "",
public null|\Closure|array $generateKey = null,
public null|\Stringable|string|array $foreignKey = null,
public null|\Stringable|string|array $foreignField = null,
public array $foreignKeys = [],
public null|string $bridge = null,
public null|\Stringable|string|array $bridgeKey = null,
public null|\Stringable|string|array $bridgeField = null,
public null|\Stringable|string|array $bridgeForeignKey = null,
public null|\Stringable|string|array $field = null,
public null|string $entity = null,
public null|string $join = null,
public null|string $function = null,
) {
parent::__construct(
RelationTypeEnum::manyToMany,
$this->key,
$this->generateKey,
$this->foreignKey,
$this->foreignField,
$this->foreignKeys,
$this->bridge,
$this->bridgeKey,
$this->bridgeField,
$this->bridgeForeignKey,
$this->field,
$this->entity,
$this->join,
$this->function,
);
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Relation;
use Ulmus\Attribute\Property\Relation;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class OneToMany extends Relation
{
public function __construct(
public \Stringable|string|array $key = "",
public null|\Closure|array $generateKey = null,
public null|\Stringable|string|array $foreignKey = null,
public null|\Stringable|string|array $foreignField = null,
public array $foreignKeys = [],
public null|string $bridge = null,
public null|\Stringable|string|array $bridgeKey = null,
public null|\Stringable|string|array $bridgeField = null,
public null|\Stringable|string|array $bridgeForeignKey = null,
public null|\Stringable|string|array $field = null,
public null|string $entity = null,
public null|string $join = null,
public null|string $function = null,
) {
parent::__construct(
RelationTypeEnum::oneToMany,
$this->key,
$this->generateKey,
$this->foreignKey,
$this->foreignField,
$this->foreignKeys,
$this->bridge,
$this->bridgeKey,
$this->bridgeField,
$this->bridgeForeignKey,
$this->field,
$this->entity,
$this->join,
$this->function,
);
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Relation;
use Ulmus\Attribute\Property\Relation;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class OneToOne extends Relation
{
public function __construct(
public \Stringable|string|array $key = "",
public null|\Closure|array $generateKey = null,
public null|\Stringable|string|array $foreignKey = null,
public null|\Stringable|string|array $foreignField = null,
public array $foreignKeys = [],
public null|string $bridge = null,
public null|\Stringable|string|array $bridgeKey = null,
public null|\Stringable|string|array $bridgeField = null,
public null|\Stringable|string|array $bridgeForeignKey = null,
public null|\Stringable|string|array $field = null,
public null|string $entity = null,
public null|string $join = null,
public null|string $function = null,
) {
parent::__construct(
RelationTypeEnum::oneToOne,
$this->key,
$this->generateKey,
$this->foreignKey,
$this->foreignField,
$this->foreignKeys,
$this->bridge,
$this->bridgeKey,
$this->bridgeField,
$this->bridgeForeignKey,
$this->field,
$this->entity,
$this->join,
$this->function,
);
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace Ulmus\Attribute\Property\Relation;
enum RelationTypeEnum
{
case oneToOne;
case oneToMany;
case manyToMany;
}

View File

@ -1,8 +0,0 @@
<?php
namespace Ulmus\Attribute\Property;
interface ResettablePropertyInterface
{
}

View File

@ -1,13 +0,0 @@
<?php
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\IndexTypeEnum;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Unique {
public function __construct(
public IndexTypeEnum $type = IndexTypeEnum::Unique,
) {}
}

View File

@ -3,20 +3,11 @@
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Virtual extends Field implements ResettablePropertyInterface {
class Virtual extends Field {
public bool $readonly = true;
public function __construct(
public null|string|array $method = null,
public ? \Closure $closure = null,
public null|string $name = null,
) {
$this->method ??= [ static::class, 'noop' ];
}
public static function noop() : null
{
return null;
}
public ? string $method = null,
) {}
}

View File

@ -12,27 +12,7 @@ class Where {
public mixed $value = null,
public string $operator = Query\Where::OPERATOR_EQUAL,
public string $condition = Query\Where::CONDITION_AND,
public string|\Stringable|array|null $fieldValue = null,
public null|array|\Closure $generateValue = null,
) {
$this->field = Attribute::handleArrayField($field);
$this->fieldValue = Attribute::handleArrayField($fieldValue);
}
public function getValue(/* null|EntityInterface */ $entity = null) : mixed
{
if ($this->generateValue) {
if ($entity) {
return call_user_func_array($this->generateValue, [ $entity ]);
}
else {
throw new \Exception(sprintf("Could not generate value from non-instanciated entity for field %s.", (string) $this->field));
}
}
elseif ($this->fieldValue && $entity) {
throw new \Exception(sprintf("Field value, from %s, could not be included in query since the entity is already loaded; it is meant to be used with a OneToOne relation loaded within a join.", (string) $this->fieldValue));
}
return $this->fieldValue ?? $this->value;
}
}

View File

@ -5,6 +5,6 @@ namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class WithJoin {
public function __construct(
public array $joins = []
array $joins = []
) {}
}

View File

@ -1,22 +0,0 @@
<?php
namespace Ulmus\Cache;
use Psr\SimpleCache\CacheInterface;
trait CacheEventTrait
{
public function __construct(
protected CacheInterface $cache,
protected string $cacheKey,
) {}
public function purgeCache() : void
{
$keys = $this->cache->get($this->cacheKey);
if ( $keys && is_iterable($keys) ) {
$this->cache->deleteMultiple(array_map(fn($e) => sprintf("%s:%s:", $this->cacheKey, $e), $keys));
}
}
}

View File

@ -1,117 +0,0 @@
<?php
namespace Ulmus;
use Psr\SimpleCache\CacheInterface;
use Ulmus\Entity\EntityInterface;
use Ulmus\QueryBuilder\QueryBuilderInterface;
use Ulmus\Repository\RepositoryInterface;
trait CacheTrait
{
protected CacheInterface $cache;
public function attachCachingObject(CacheInterface $cache) : self
{
$cacheKey = "";
$this->cache = $cache;
# Reading from cache
$this->eventRegister(new class($cacheKey) implements Event\Repository\CollectionFromQueryDatasetInterface {
public function __construct(
protected string & $cacheKey
) {}
public function execute(RepositoryInterface $repository, array &$data): void
{
$this->cacheKey = $repository->queryBuilder->hashSerializedQuery();
$data = $repository->getFromCache( $this->cacheKey) ?: [];
}
});
# Setting to cache
$this->eventRegister(new class($cacheKey) implements Event\Repository\CollectionFromQueryInterface {
public function __construct(
protected string & $cacheKey
) {}
public function execute(RepositoryInterface $repository, EntityCollection $collection): EntityCollection
{
$repository->setToCache( $this->cacheKey, $collection->map(fn(EntityInterface $e) => $e->entityGetDataset(false, true)));
$this->cacheKey = "";
return $collection;
}
});
$this->eventRegister(new class($this->cache, $this->entityCacheKey()) implements Event\Query\Insert {
use Cache\CacheEventTrait;
public function execute(RepositoryInterface $repository, object|array $entity, ?array $dataset = null, bool $replace = false): void
{
$this->purgeCache();
}
});
# Cache invalidation
$this->eventRegister(new class($this->cache, $this->entityCacheKey()) implements Event\Query\Update {
use Cache\CacheEventTrait;
public function execute(RepositoryInterface $repository, object|array $entity, ?array $dataset = null, bool $replace = false): void
{
$this->purgeCache();
}
});
$this->eventRegister(new class($this->cache, $this->entityCacheKey()) implements Event\Query\Delete {
use Cache\CacheEventTrait;
public function execute(RepositoryInterface $repository, EntityInterface $entity): void
{
$this->purgeCache();
}
});
$this->eventRegister(new class($this->cache, $this->entityCacheKey()) implements Event\Query\Truncate {
use Cache\CacheEventTrait;
public function execute(RepositoryInterface $repository, ?string $table = null, ?string $alias = null, ?string $schema = null): void
{
$this->purgeCache();
}
});
return $this;
}
protected function entityCacheKey() : string
{
return sprintf("%s.%s", $this->entityResolver->databaseName(), $this->entityResolver->tableName());
}
public function getFromCache(string $key) : mixed
{
$keys = $this->cache->get($this->entityCacheKey(), []);
if (in_array($key, $keys)) {
return $this->cache->get(sprintf("%s:%s:", $this->entityCacheKey(), $key));
}
return null;
}
public function setToCache(string $key, mixed $value) : void
{
$keys = $this->cache->get($this->entityCacheKey(), []);
if (! in_array($key, $keys)) {
$keys[] = $key;
$this->cache->set($this->entityCacheKey(), $keys);
}
$this->cache->set(sprintf("%s:%s:", $this->entityCacheKey(), $key), $value);
}
}

View File

@ -2,10 +2,12 @@
namespace Ulmus\Common;
use Ulmus\Attribute\Property\{ Field };
use Ulmus\Annotation\Annotation;
use Ulmus\Attribute;
use Ulmus\Migration\FieldDefinition;
use Ulmus\Ulmus,
Ulmus\Adapter\AdapterInterface,
Ulmus\Annotation\Property\Field,
Ulmus\Query\WhereRawParameter;
class EntityField implements WhereRawParameter
@ -28,7 +30,7 @@ class EntityField implements WhereRawParameter
public function name($useAlias = true) : string
{
$name = $this->entityResolver->searchFieldAnnotation($this->name, [ Field::class ] )->name ?? $this->name;
$name = $this->entityResolver->searchFieldAnnotation($this->name, [ Attribute\Property\Field::class, Field::class ] )->name ?? $this->name;
$name = $this->entityResolver->databaseAdapter()->adapter()->escapeIdentifier($name, AdapterInterface::IDENTIFIER_FIELD);
@ -54,19 +56,12 @@ class EntityField implements WhereRawParameter
$definition = new FieldDefinition($adapter, $field);
return implode(" ", [
$adapter->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
$definition->getSqlName(),
$definition->getSqlType(),
$definition->getSqlParams(),
]);
}
public static function generateAlterColumn(AdapterInterface $adapter, array $field) : string
{
$definition = new FieldDefinition($adapter, $field['definition']);
return $adapter->generateAlterColumn($definition, $field);
}
public static function isObjectType($type) : bool
{
# @Should be fixed with isBuiltIn() instead, it won't be correct based only on name

View File

@ -2,18 +2,15 @@
namespace Ulmus\Common;
use Notes\Common\ReflectedClass;
use Notes\Common\ReflectedProperty;
use Psr\SimpleCache\CacheInterface;
use Ulmus\Attribute\Obj\Index;
use Ulmus\Ulmus,
Ulmus\Attribute\Obj\Table,
Ulmus\Attribute\Obj\AdapterAttributeInterface,
Ulmus\Attribute\Property\Field,
Ulmus\Attribute\Property\Relation,
Ulmus\Attribute\Property\Virtual;
Ulmus\Annotation\Classes\Table,
Ulmus\Annotation\Property\Field,
Ulmus\Annotation\Property\Virtual,
Ulmus\Annotation\Property\Relation,
Ulmus\Attribute;
use Notes\Common\ReflectedAttribute;
use Notes\Annotation;
use Notes\ObjectReflection;
@ -27,7 +24,13 @@ class EntityResolver {
public string $entityClass;
public ReflectedClass $reflectedClass;
public array $uses;
public array $class;
public array $properties;
public array $methods;
protected array $fieldList = [];
@ -35,10 +38,14 @@ class EntityResolver {
{
$this->entityClass = $entityClass;
$this->reflectedClass = ObjectReflection::fromClass($entityClass, $cache)->reflectClass();
list($this->uses, $this->class, $this->methods, $this->properties) = array_values(
ObjectReflection::fromClass($entityClass, $cache)->read()
);
$this->resolveAnnotations();
}
public function field($name, $fieldKey = self::KEY_ENTITY_NAME, $throwException = true) : null|ReflectedProperty
public function field($name, $fieldKey = self::KEY_ENTITY_NAME, $throwException = true) : ? array
{
try{
return $this->fieldList($fieldKey)[$name] ?? null;
@ -52,33 +59,43 @@ class EntityResolver {
return null;
}
public function searchField($name) : null|ReflectedProperty
public function searchField($name) : null|array
{
return $this->field($name, self::KEY_ENTITY_NAME, false) ?: $this->field($name, self::KEY_COLUMN_NAME, false);
try{
return $this->field($name, self::KEY_ENTITY_NAME, false) ?: $this->field($name, self::KEY_COLUMN_NAME, false);
}
catch(\Throwable $e) {
if ( $throwException) {
throw new \InvalidArgumentException("Can't find entity field's column named `$name` from entity {$this->entityClass}");
}
}
return null;
}
public function fieldList($fieldKey = self::KEY_ENTITY_NAME, bool $skipVirtual = false) : array
{
$fieldList = [];
foreach($this->reflectedClass->getProperties(true) as $item) {
foreach($item->getAttributes() as $tag) {
if ( $tag->object instanceof Field ) {
if ( $skipVirtual && $tag->object instanceof Virtual ) {
foreach($this->properties as $item) {
foreach($item['tags'] ?? [] as $tag) {
if ( $tag['object'] instanceof Field or $tag['object'] instanceof Attribute\Property\Field ) {
if ( $skipVirtual && ($tag['object'] instanceof Virtual or $tag['object'] instanceof Attribute\Property\Virtual )) {
break;
}
switch($fieldKey) {
case static::KEY_LC_ENTITY_NAME:
$key = strtolower($item->name);
$key = strtolower($item['name']);
break;
case static::KEY_ENTITY_NAME:
$key = $item->name;
$key = $item['name'];
break;
case static::KEY_COLUMN_NAME:
$key = strtolower($tag->object->name ?? $item->name);
$key = strtolower($tag['object']->name ?? $item['name']);
break;
default:
@ -95,49 +112,52 @@ class EntityResolver {
return $fieldList;
}
public function relation(string $name) : array
public function relation(string $name) : ? array
{
$property = $this->reflectedClass->getProperties(true)[$name] ?? false;
try{
if ( $property ) {
foreach($property->getAttributes() as $tag) {
if ( $tag->object instanceof Relation ) {
return $property;
if ( null !== ( $this->properties[$name] ?? null ) ) {
foreach($this->properties[$name]['tags'] ?? [] as $tag) {
if ( $tag['object'] instanceof Relation or $tag['object'] instanceof Attribute\Property\Relation ) {
return $this->properties[$name];
}
}
}
return [];
}
catch(\Throwable $e) {
throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}");
# if ( $throwException) {
throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}");
# }
}
return null;
}
public function getPropertyEntityType(string $name) : false|string
{
return $this->reflectedClass->getProperties(true)[$name]->getTypes()[0]->type ?? false;
}
public function searchFieldAnnotation(string $field, array|object|string $annotationType, bool $caseSensitive = true) : ? object
{
return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null;
}
public function searchFieldAnnotationList(string $field, array|object|string $attributeType, bool $caseSensitive = true) : false|array
public function searchFieldAnnotationList(string $field, array|object|string $annotationType, bool $caseSensitive = true) : array
{
$list = [];
$properties = $this->reflectedClass->getProperties(true);
$search = $caseSensitive ? $this->properties : array_change_key_case($this->properties, \CASE_LOWER);
$search = $caseSensitive ? $properties : array_change_key_case($properties, \CASE_LOWER);
$annotations = is_array($annotationType) ? $annotationType : [ $annotationType ];
if ( null !== ( $search[$field] ?? null ) ) {
return array_map(fn(ReflectedAttribute $e) => $e->object, $search[$field]->getAttributes((array) $attributeType));
foreach($search[$field]['tags'] ?? [] as $tag) {
foreach($annotations as $annotation) {
if ( $tag['object'] instanceof $annotation ) {
$list[] = $tag['object'];
}
}
}
}
return false;
return $list;
}
public function tableName($required = false) : string
@ -153,7 +173,7 @@ class EntityResolver {
return $table->name ?? "";
}
public function tableAnnotation($required = false) : null|Table
public function tableAnnotation($required = false) : Table|Attribute\Obj\Table
{
if ( null === $table = $this->getTableAttribute() ) {
if ($required) {
@ -164,17 +184,17 @@ class EntityResolver {
return $table;
}
public function databaseName() : null|string
public function databaseName() : ? string
{
return $this->tableAnnotation(false)->database ?? $this->databaseAdapter()->adapter()->databaseName() ?? null;
}
public function sqlAdapter() : \Ulmus\ConnectionAdapter
{
if ( $adapterObj = $this->getAdapterInterfaceAttribute()) {
if ( false !== $adapterName = $adapterObj->adapter() ) {
if ( null === ( $adapter = \Ulmus\Ulmus::$registeredAdapters[$adapterName] ?? null ) ) {
throw new \Exception("Requested database adapter `$adapterName` is not registered.");
if ( null !== $table = $this->getTableAttribute() ) {
if ( $table->adapter ?? null ) {
if ( null === ( $adapter = \Ulmus\Ulmus::$registeredAdapters[$table->adapter] ?? null ) ) {
throw new \Exception("Requested database adapter `{$table->adapter}` is not registered.");
}
else {
return $adapter;
@ -193,7 +213,7 @@ class EntityResolver {
return $this->sqlAdapter();
}
public function schemaName(bool $required = false) : null|string
public function schemaName(bool $required = false) : ? string
{
if ( null === $table = $this->getTableAttribute() ) {
throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation");
@ -206,10 +226,10 @@ class EntityResolver {
return $table->schema ?? null;
}
public function getPrimaryKeyField() : null|array
public function getPrimaryKeyField() : ? array
{
foreach($this->fieldList() as $key => $value) {
$field = $this->searchFieldAnnotation($key, [ Field::class ]);
$field = $this->searchFieldAnnotation($key, [ Attribute\Property\Field::class, Field::class ]);
if ( null !== $field ) {
if ( false !== ( $field->attributes['primary_key'] ?? false ) ) {
return [ $key => $field ];
@ -220,9 +240,9 @@ class EntityResolver {
return null;
}
public function getCompoundKeyFields() : ? Index
public function getCompoundKeyFields() : ? array
{
return $this->getAttributeImplementing(Index::class);
return null;
}
public function getUniqueFields() : ? array
@ -230,24 +250,107 @@ class EntityResolver {
return null;
}
protected function getAdapterInterfaceAttribute() : null|object
{
return $this->getAttributeImplementing(AdapterAttributeInterface::class);
}
protected function getTableAttribute()
{
return $this->getAttributeImplementing(Table::class);
return $this->getAnnotationFromClassname(Attribute\Obj\Table::class, false) ?: $this->getAnnotationFromClassname( Table::class );
}
public function getAttributeImplementing(string $interface) : null|object
/**
* Transform an annotation into it's object's counterpart
*/
public function getAnnotationFromClassname(string $className, bool $throwError = true) : ? object
{
foreach ($this->reflectedClass->getAttributes(true) as $item) {
if ($item->object instanceof $interface) {
return $item->object;
if ( $name = $this->uses[$className] ?? false ) {
foreach(array_reverse($this->class['tags']) as $item) {
if ( $item['tag'] === $name ) {
return $this->instanciateAnnotationObject($item);
}
foreach($this->properties as $item) {
foreach(array_reverse($item['tags']) as $item) {
if ( $item['tag'] === $name ) {
return $this->instanciateAnnotationObject($item);
}
}
}
foreach($this->methods as $item) {
foreach(array_reverse($item['tags']) as $item) {
if ( $item['tag'] === $name ) {
return $this->instanciateAnnotationObject($item);
}
}
}
}
if ($throwError) {
throw new \TypeError("Annotation `$className` could not be found within your object `{$this->entityClass}`");
}
}
elseif ($throwError) {
throw new \InvalidArgumentException("Class `$className` was not found within {$this->entityClass} uses statement (or it's children / traits)");
}
return null;
}
public function instanciateAnnotationObject(array|\ReflectionAttribute $tagDefinition) : object
{
if ($tagDefinition instanceof \ReflectionAttribute) {
$obj = $tagDefinition->newInstance();
}
else {
$arguments = $this->extractArguments($tagDefinition['arguments']);
if (false === $class = array_search($tagDefinition['tag'], $this->uses)) {
throw new \InvalidArgumentException("Annotation class `{$tagDefinition['tag']}` was not found within {$this->entityClass} uses statement (or it's children / traits)");
}
$obj = new $class(... $arguments['constructor']);
foreach ($arguments['setter'] as $key => $value) {
$obj->$key = $value;
}
}
return $obj;
}
/**
* Extracts arguments from an Annotation definition, easing object's declaration.
*/
protected function extractArguments(array $arguments) : array
{
$list = [
'setter' => [],
'constructor' => [],
];
ksort($arguments);
foreach($arguments as $key => $value) {
$list[ is_int($key) ? 'constructor' : 'setter' ][$key] = $value;
}
return $list;
}
protected function resolveAnnotations()
{
foreach($this->class['tags'] as &$tag) {
$tag['object'] ??= $this->instanciateAnnotationObject($tag);
}
foreach($this->properties as &$property) {
foreach($property['tags'] as &$tag){
$tag['object'] ??= $this->instanciateAnnotationObject($tag);
}
}
foreach($this->methods as &$method) {
foreach($method['tags'] as &$tag){
$tag['object'] ??= $this->instanciateAnnotationObject($tag);
}
}
}
}

View File

@ -11,37 +11,27 @@ class PdoObject extends PDO {
public bool $executionStatus;
public int $rowCount = 0;
public mixed $lastInsertId = null;
public \Closure $onClose;
public function select(string $sql, array $parameters = []): PDOStatement
public function select(string $sql, array $parameters = []): PDOStatement
{
static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]);
try {
if (false !== ( $statement = $this->prepare($sql) )) {
$statement = $this->execute($statement, $parameters, false);
$statement->setFetchMode(\PDO::FETCH_ASSOC);
$this->execute($statement, $parameters, false);
return $statement;
}
}
catch (\Throwable $e) {
}
catch (\PDOException $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
/**
* @deprecated
*/
public function runQuery(string $sql, array $parameters = []): ? PDOStatement
{
static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]);
@ -49,57 +39,48 @@ class PdoObject extends PDO {
if (false !== ( $statement = $this->prepare($sql) )) {
return $this->execute($statement, $parameters, true);
}
}
catch(\PDOException $pdo) {
if ( substr($pdo->getMessage(), 0, 30) !== '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);
}
catch (\PDOException $e) {
throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters));
}
return null;
}
public function runInsertQuery(string $sql, array $parameters = []) : ? static
public function runInsertQuery(string $sql, array $parameters = [])
{
return $this->runQuery($sql, $parameters);
}
public function runUpdateQuery(string $sql, array $parameters = []) : ? static
public function runUpdateQuery(string $sql, array $parameters = [])
{
return $this->runQuery($sql, $parameters);
}
public function runDeleteQuery(string $sql, array $parameters = []) : ? static
public function runDeleteQuery(string $sql, array $parameters = []): ? PDOStatement
{
return $this->runQuery($sql, $parameters);
}
public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true) : ? static
public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true): ? PDOStatement
{
$this->executionStatus = false;
$this->lastInsertId = null;
try {
if (! $this->inTransaction()) {
if ( ! $this->inTransaction() ) {
$this->beginTransaction();
}
$this->bindVariables($statement, $parameters);
$this->executionStatus = $statement->execute();
$this->executionStatus = empty($parameters) ? $statement->execute() : $statement->execute($parameters);
if ( $this->executionStatus ) {
$this->lastInsertId = $this->lastInsertId();
$this->rowCount = $statement->rowCount();
$statement->lastInsertId = $this->lastInsertId();
if ( $commit ) {
$this->commit();
}
return $this;
return $statement;
}
else {
throw new \PDOException($statement->errorCode() . " - " . json_encode($statement->errorInfo()));
@ -111,39 +92,13 @@ class PdoObject extends PDO {
throw $e;
}
catch (\Throwable $e) {
if ( function_exists("debogueur") ) {
debogueur($statement, $parameters, $commit);
}
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) {
switch (strtolower(gettype($value))) {
#$type = Pdo::PARAM_BOOL;
#break;
case "boolean":
case "integer":
$type = Pdo::PARAM_INT;
break;
case "null":
$type = Pdo::PARAM_NULL;
break;
case "string":
default:
$type = Pdo::PARAM_STR;
}
$statement->bindValue($key, $value, $type);
}
}
}
return null;
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace Ulmus\Common\PdoObject;
class SqlPdoObject extends \Ulmus\Common\PdoObject
{
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();
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace Ulmus\Common\PdoObject;
class SqlitePdoObject extends SqlPdoObject
{
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;
}
}

View File

@ -56,19 +56,18 @@ abstract class Sql {
$this->identifier = $identifier;
}
public function __toString() : string
{
public function __toString() {
return $this->identifier;
}
};
}
public static function raw(string $sql) : object
{
return static::identifier($sql);
}
public static function escape($value) : mixed
public static function escape($value)
{
switch(true) {
case is_object($value):
@ -84,12 +83,8 @@ abstract class Sql {
return $value;
}
public static function collate(string $name) : string
public static function parameter($value) : string
{
if ( ! preg_match('/^[a-z0-9$_]+$/i',$name) ) {
throw new \InvalidArgumentException(sprintf("Given identifier '%s' should contains supported characters in function name (a-Z, 0-9, $ and _)", $name));
}
return static::raw(sprintf("COLLATE %s", $name));
}
}

View File

@ -2,23 +2,26 @@
namespace Ulmus;
use Psr\SimpleCache\CacheInterface;
use Ulmus\Adapter\AdapterInterface;
use Ulmus\Common\PdoObject;
class ConnectionAdapter
{
public string $name;
public array $configuration;
protected AdapterInterface $adapter;
protected PdoObject $pdo;
public function __construct(
public string $name = "default",
protected array $configuration = [],
public bool $default = false,
public ? CacheInterface $cacheObject = null
) {
public function __construct(string $name = "default", array $configuration = [], bool $default = false)
{
$this->name = $name;
$this->configuration = $configuration;
Ulmus::registerAdapter($this, $default);
}
@ -30,7 +33,7 @@ class ConnectionAdapter
$this->adapter = $this->instanciateAdapter($adapterName);
}
else {
throw new \InvalidArgumentException(sprintf("Adapter not found within your configuration array. (%s)", json_encode($connection)));
throw new \InvalidArgumentException("Adapter not found within your configuration array.");
}
$this->adapter->setup($connection);
@ -50,7 +53,7 @@ class ConnectionAdapter
public function connect() : self
{
$this->pdo = $this->adapter->connect();
return $this;
}
@ -74,7 +77,7 @@ class ConnectionAdapter
* @param string $name An Ulmus adapter or full class name implementing AdapterInterface
* @return AdapterInterface
*/
protected function instanciateAdapter(string $name) : AdapterInterface
protected function instanciateAdapter($name) : AdapterInterface
{
$class = substr($name, 0, 2) === "\\" ? $name : sprintf("\\%s\\Adapter\\%s", __NAMESPACE__, $name);

View File

@ -2,27 +2,12 @@
namespace Ulmus\Container;
use Ulmus\ConnectionAdapter;
class AdapterProxy
{
public array $connections = [];
public function __construct(...$arguments)
{
$this->push(array_filter($arguments));
}
public function push(array|ConnectionAdapter $connection) : void
{
foreach((array) $connection as $adapter) {
foreach($this->connections as $existing) {
if ($adapter === $existing) {
continue 2;
}
}
$this->connections[] = $adapter;
}
$this->connections = $arguments;
}
}

View File

@ -1,159 +0,0 @@
<?php
namespace Ulmus\Entity;
use Generator;
use Ulmus\Attribute\Property\Field;
use Ulmus\Attribute\Property\Relation;
use Ulmus\Common\EntityField;
use Ulmus\Common\EntityResolver;
use Ulmus\Ulmus;
class DatasetHandler
{
public function __construct(
protected EntityResolver $entityResolver,
protected bool $entityStrictFieldsDeclaration = false,
) {}
public function pull(object $entity) : Generator
{
foreach($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) {
$attribute = $this->entityResolver->searchFieldAnnotation($key,[ Field::class ]);
if ( $entity->__isset($key) ) {
yield $attribute->name ?? $key => $entity->$key;
}
elseif ( $field->allowsNull() ) {
yield $attribute->name ?? $key => null;
}
}
}
public function push(iterable $dataset) : Generator|array
{
$unmatched = [];
foreach($dataset as $key => $value) {
$field = $this->entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? $this->entityResolver->field(strtolower($key), EntityResolver::KEY_LC_ENTITY_NAME, false);
if ( $field === null ) {
if ($this->entityStrictFieldsDeclaration ) {
throw new \Exception("Field `$key` can not be found within your entity ".static::class);
}
else {
$unmatched[$key] = $value;
}
continue;
}
$type = $field->getTypes()[0];
if ( is_null($value) ) {
yield $field->name => null;
}
elseif ( $field->expectType('array') ) {
if ( is_string($value)) {
if (empty($value)) {
yield $field->name => [];
}
elseif (substr($value, 0, 1) === "a") {
yield $field->name => unserialize($value);
}
else {
$data = json_decode($value, true);
if (json_last_error() !== \JSON_ERROR_NONE) {
throw new \Exception(sprintf("JSON error while decoding in EntityTrait : '%s' given %s", json_last_error_msg(), $value));
}
yield $field->name => $data;
}
}
elseif ( is_array($value) ) {
yield $field->name => $value;
}
}
elseif ( EntityField::isScalarType($type->type) ) {
if ( $type->type === 'string' ) {
$attribute = $this->entityResolver->searchFieldAnnotation($field->name, [ Field::class ] );
if ( $attribute->length ?? null ) {
$value = mb_substr($value, 0, $attribute->length);
}
}
elseif ( $type->type === 'bool' ) {
$value = (bool) $value;
}
yield $field->name => $value;
}
elseif ( $value instanceof \UnitEnum ) {
yield $field->name => $value;
}
elseif (enum_exists($type->type)) {
yield $field->name => $type->type::from($value);
}
elseif ( ! $type->builtIn ) {
try {
yield $field->name => Ulmus::instanciateObject($type->type, [ $value ]);
}
catch(\Error $e) {
throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field->name));
}
}
}
return $unmatched;
}
public function pullRelation(object $entity, bool $onlyPreviouslyLoaded = true) : Generator
{
foreach($this->entityResolver->reflectedClass->getProperties(true) as $name => $field) {
$relation = $this->entityResolver->searchFieldAnnotation($name, [ Relation::class ] );
if ($relation) {
$ignore = $this->entityResolver->searchFieldAnnotation($name, [ Relation\Ignore::class ] );
$load = $onlyPreviouslyLoaded ? (new \ReflectionProperty($entity::class, $name))->isInitialized($entity) : true;
if ( ! $load || ( $ignore && $ignore->ignoreExport ) ) {
if ( $relation->isOneToOne() ) {
# @TODO TO INCLUDED INTO getTypes() RETURNED CLASS WHEN DONE !
yield $name => ( new \ReflectionClass($field->getTypes()[0]) )->newInstanceWithoutConstructor();
}
else {
# empty collection
yield $name => [];
}
continue;
}
if ( $entity->__isset($name) && ($relation->entity ?? $relation->bridge) !== static::class ) {
$value = $entity->$name ?? null;
if ( null !== $value ) {
if ( is_iterable($value) ) {
$list = [];
foreach($value as $entityObj) {
$list[] = $entityObj->entityGetDataset(false);
}
yield $name => $list;
}
elseif ( is_object($value) ) {
yield $name => $value->entityGetDataset(false);
}
}
}
}
}
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace Ulmus\Entity;
use Ulmus\Common\{ EntityField, EntityResolver };
use Ulmus\{ConnectionAdapter,
EntityCollection,
QueryBuilder\QueryBuilderInterface,
Repository,
SearchRequest\SearchableInterface};
interface EntityInterface extends SearchableInterface /* extends \JsonSerializable */
{
public function fromArray(iterable $dataset) : static;
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array;
public function toArray($includeRelations = false, array $filterFields = null) : array;
public function toCollection() : EntityCollection;
public function isLoaded() : bool;
public function jsonSerialize() : mixed;
public static function resolveEntity() : EntityResolver;
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository;
public static function entityCollection(...$arguments) : EntityCollection;
public static function queryBuilder() : QueryBuilderInterface;
public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField;
public static function fields(array $fields, null|string|false $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : string;
}

View File

@ -12,20 +12,12 @@ class Datetime extends \DateTime implements EntityObjectInterface {
{
$value = $arguments[0];
try {
# From Timestamp
if ( is_numeric($value) ) {
$obj = new static("@$value");
}
else {
$obj = new static($value);
}
}
catch(\Throwable $ex) {
throw new \Exception(sprintf("An error occured trying to instanciate from '%s'. %s", $value, $ex->getMessage()), $ex->getCode(), $ex);
# From Timestamp
if ( is_numeric($value) ) {
return new static("@$value");
}
return $obj;
return new static($value);
}
public function save()

View File

@ -2,116 +2,123 @@
namespace Ulmus\Entity\InformationSchema;
use Notes\Common\ReflectedProperty;
use Ulmus\Entity\Field\Datetime;
use Ulmus\{Attribute\Obj\Table};
use Ulmus\Attribute\Property\{Field, Filter, FilterJoin, Relation, Join, Virtual, Where};
#[Table(name: "columns", database: "information_schema")]
/**
* @Table('name' => "columns", 'database' => "information_schema")
*/
class Column
{
use \Ulmus\EntityTrait;
#[Field(name: "TABLE_CATALOG", length: 512)]
/**
* @Field('name' => "TABLE_CATALOG", 'length' => 512)
*/
public string $tableCatalog;
#[Field(name: "TABLE_SCHEMA", length: 64)]
/**
* @Field('name' => "TABLE_SCHEMA", 'length' => 64)
*/
public string $tableSchema;
#[Field(name: "TABLE_NAME", length: 64)]
/**
* @Field('name' => "TABLE_NAME", 'length' => 64)
*/
public string $tableName;
#[Field(name: "COLUMN_NAME", length: 64, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "COLUMN_NAME", 'length' => 64, 'attributes' => [ 'primary_key' => true ])
*/
public string $name;
#[Field(name: "ORDINAL_POSITION", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "ORDINAL_POSITION", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public int $ordinalPosition;
#[Field(name: "COLUMN_DEFAULT", type: "longtext")]
/**
* @Field('name' => "COLUMN_DEFAULT", 'type' => "longtext")
*/
public ? string $default;
#[Field(name: "IS_NULLABLE", length: 3)]
/**
* @Field('name' => "IS_NULLABLE", 'length' => 3)
*/
public string $nullable;
#[Field(name: "DATA_TYPE", length: 64)]
/**
* @Field('name' => "DATA_TYPE", 'length' => 64)
*/
public string $dataType;
#[Field(name: "CHARACTER_MAXIMUM_LENGTH", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "CHARACTER_MAXIMUM_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? int $characterMaximumLength;
#[Field(name: "CHARACTER_OCTET_LENGTH", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "CHARACTER_OCTET_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? int $characterOctetLength;
#[Field(name: "NUMERIC_PRECISION", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "NUMERIC_PRECISION", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? int $numericPrecision;
#[Field(name: "NUMERIC_SCALE", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "NUMERIC_SCALE", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? int $numericScale;
#[Field(name: "DATETIME_PRECISION", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "DATETIME_PRECISION", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? int $datetimePrecision;
#[Field(name: "CHARACTER_SET_NAME", length: 32)]
/**
* @Field('name' => "CHARACTER_SET_NAME", 'length' => 32)
*/
public ? string $characterSetName;
#[Field(name: "COLLATION_NAME", length: 32)]
/**
* @Field('name' => "COLLATION_NAME", 'length' => 32)
*/
public ? string $collationName;
# #[Field(name: "COLLATION_TYPE", type: "longtext")]
# public string $type;
/**
* @Field('name' => "COLLATION_TYPE", 'type' => "longtext")
*/
public string $type;
/**
* @Field('name' => "COLUMN_KEY", 'length' => 3)
*/
public string $key;
# #[Field(name: "IS_GENERATED", length: 6)]
# public string $generated;
/**
* @Field('name' => "EXTRA", 'length' => 30)
*/
public string $extra;
public function matchFieldDefinition(ReflectedProperty $definition) : bool
{
$nullable = $this->nullable === 'YES';
/**
* @Field('name' => "PRIVILEGES", 'length' => 80)
*/
public string $privileges;
$definitionValue = $this->getDefinitionValue($definition);
/**
* @Field('name' => "COLUMN_COMMENT", 'length' => 1024)
*/
public string $comment;
if ($nullable !== $definition->allowsNull()) {
return false;
}
/**
* @Field('name' => "IS_GENERATED", 'length' => 6)
*/
public string $generated;
if ( isset($definitionValue) && $this->canHaveDefaultValue() ) {
if ( $definitionValue !== $this->defaultValue() ) {
# dump($definition->value, $this->defaultValue(), $definition->name);
return false;
}
}
elseif (! isset($definitionValue)) {
if ( ! $this->defaultValueIsNull() ) {
return false;
}
}
return true;
}
/**
* @Field('name' => "GENERATION_EXPRESSION", 'type' => "longtext")
*/
public ? string $generationExpression;
protected function getDefinitionValue(ReflectedProperty $definition) : mixed
{
# Attribute's value first, then defined in class value
return $definition->getAttribute(Field::class)->object->default ?? $definition->value ?? null;
}
protected function defaultValueIsNull() : bool
{
return $this->defaultValue() === null;
}
protected function defaultValue() : mixed
{
if (is_numeric($this->default)) {
return (int) $this->default;
}
return $this->default;
}
protected function canHaveDefaultValue() : bool
{
return true;
}
}

View File

@ -5,90 +5,131 @@ namespace Ulmus\Entity\InformationSchema;
use Ulmus\EntityCollection,
Ulmus\Entity\Field\Datetime;
use Ulmus\{Attribute\Obj\Table as TableObj, Entity\EntityInterface};
use Ulmus\Attribute\Property\{Field, Filter, FilterJoin, Relation, Join, Virtual, Where};
#[TableObj(name: "tables", database: "information_schema")]
/**
* @Table('name' => "tables", 'database' => "information_schema")
*/
class Table
{
use \Ulmus\EntityTrait;
#[Field(name: "TABLE_CATALOG", length: 512)]
/**
* @Field('name' => "TABLE_CATALOG", 'length' => 512)
*/
public string $catalog;
#[Field(name: "TABLE_SCHEMA", length: 64)]
/**
* @Field('name' => "TABLE_SCHEMA", 'length' => 64)
*/
public string $schema;
#[Field(name: "TABLE_NAME", length: 64, attributes: [ 'primary_key' => true, ])]
/**
* @Field('name' => "TABLE_NAME", 'length' => 64, 'attributes' => [ 'primary_key' => true ])
*/
public string $name;
#[Field(name: "TABLE_TYPE", length: 64)]
/**
* @Field('name' => "TABLE_TYPE", 'length' => 64)
*/
public string $type;
#[Field(name: "ENGINE", length: 64)]
/**
* @Field('name' => "ENGINE", 'length' => 64)
*/
public ? string $engine ;
#[Field(name: "VERSION", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "VERSION", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $version;
#[Field(name: "ROW_FORMAT", length: 10)]
/**
* @Field('name' => "ROW_FORMAT", 'length' => 10)
*/
public ? string $rowFormat;
#[Field(name: "TABLE_ROWS", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "TABLE_ROWS", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $rows;
#[Field(name: "AVG_ROW_LENGTH", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "AVG_ROW_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $averageRowLength;
#[Field(name: "DATA_LENGTH", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "DATA_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $dataLength;
#[Field(name: "MAX_DATA_LENGTH", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "MAX_DATA_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $maxDataLength;
#[Field(name: "INDEX_LENGTH", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "INDEX_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $indexLength;
#[Field(name: "DATA_FREE", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "DATA_FREE", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $dataFree;
#[Field(name: "AUTO_INCREMENT", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "AUTO_INCREMENT", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $autoIncrement;
#[Field(name: "CREATE_TIME")]
/**
* @Field('name' => "CREATE_TIME")
*/
public ? Datetime $createTime;
#[Field(name: "UPDATE_TIME")]
/**
* @Field('name' => "UPDATE_TIME")
*/
public ? Datetime $updateTime;
#[Field(name: "CHECK_TIME")]
/**
* @Field('name' => "CHECK_TIME")
*/
public ? Datetime $checkTime;
#[Field(name: "TABLE_COLLATION", length: 32)]
/**
* @Field('name' => "TABLE_COLLATION", 'length' => 32)
*/
public ? string $tableCollation;
#[Field(name: "CHECKSUM", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "CHECKSUM", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? string $checksum;
#[Field(name: "CREATE_OPTIONS", length: 2048)]
/**
* @Field('name' => "CREATE_OPTIONS", 'length' => 2048)
*/
public ? string $createOptions;
#[Field(name: "TABLE_COMMENT", length: 2048)]
/**
* @Field('name' => "TABLE_COMMENT", 'length' => 2048)
*/
public string $tableComment;
#[Field(name: "MAX_INDEX_LENGTH", type: "bigint", length: 21, attributes: [ 'unsigned' => true, ])]
/**
* @Field('name' => "MAX_INDEX_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ])
*/
public ? int $maxIndexLength;
#[Field(name: "TEMPORARY", length: 1)]
/**
* @Field('name' => "TEMPORARY", 'length' => 1)
*/
public ? string $temporary;
#[Relation(type: "oneToMany", key: "name", foreignKey: [ Column::class, 'tableName' ], entity: Column::class)]
#[Where(field: 'TABLE_SCHEMA', generateValue: [ Table::class, 'getSchema' ])]
/**
* @Relation('oneToMany', 'key' => 'name', 'foreignKey' => Column::field('tableName'), 'entity' => Column::class)
* @Where('TABLE_SCHEMA', Column::field('tableSchema'))
*/
public EntityCollection $columns;
# Awaiting PHP 8.5 https://wiki.php.net/rfc/closures_in_const_expr
public static function getSchema(Table $entity) : string
{
return $entity->schema;
}
}

View File

@ -1,85 +0,0 @@
<?php
namespace Ulmus\Entity\Mssql;
use Ulmus\Attribute\Obj\Table as TableObj;
use Ulmus\Attribute\Property\Field;
use Ulmus\ConnectionAdapter;
use Ulmus\Entity\Mysql\id;
use Ulmus\Repository;
use Ulmus\Ulmus;
#[TableObj(name: "columns", schema: "information_schema")]
class Column extends \Ulmus\Entity\InformationSchema\Column
{
/*
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null): Repository
{
$adapter = Ulmus::$registeredAdapters[$this->loadedFromAdapter];
return Ulmus::repository(static::class, $alias, $adapter);
}*/
# TODO ! Handle FUNCTIONAL default value
protected function canHaveDefaultValue(): bool
{
return ! in_array(strtoupper($this->dataType), [
# 'BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB', 'JSON', 'TEXT', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'GEOMETRY'
]);
}
protected function defaultValue() : mixed
{
if ($this->default === null) {
return null;
}
# Removing first pairs of brackets
$default = $this->isBetween($this->default, '(', ')') ? substr($this->default, 1, -1) : $this->default;
if ($default === "NULL") {
return null;
}
# Checking if another pairs of brackets surrounds the value
if ($this->isBetween($default, '(', ')')) {
$default = substr($default, 1, -1);
if (is_numeric($default)) {
return (int) $default;
}
}
elseif ($this->isBetween($default, "'", "'")) {
$default = new class($default) implements \Stringable {
public function __construct(
private readonly string $default
) {}
public function __toString(): string
{
return substr($this->default, 1, -1);;
}
};
}
else {
$default = new class($default) implements \Stringable {
public function __construct(
private readonly string $default
) {}
public function __toString(): string
{
return $this->default;
}
};
}
return (string) $default;
}
private function isBetween(string $str, string $first, string $last) : bool
{
return str_starts_with($str, $first) && str_ends_with($str, $last);
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace Ulmus\Entity\Mssql;
use Ulmus\EntityCollection,
Ulmus\Entity\Field\Datetime;
use Ulmus\{Attribute\Obj\Table as TableObj, Ulmus};
use Ulmus\Attribute\Property\{Field, Filter, FilterJoin, Relation, Join, Virtual, Where};
use Ulmus\Repository;
#[TableObj(name: "tables", schema: "information_schema")]
class Table extends \Ulmus\Entity\InformationSchema\Table
{
#[Relation(type: "oneToMany", key: "name", foreignKey: [ Column::class, 'tableName' ], entity: Column::class)]
#[Where('TABLE_SCHEMA', generateValue: [ Table::class, 'getSchema' ])]
##[Filter(method: "filterColumns")]
public EntityCollection $columns;
public function filterColumns() : Repository
{
$adapter = Ulmus::$registeredAdapters[$this->loadedFromAdapter];
return Column::repository(Repository\MssqlRepository::DEFAULT_ALIAS, $adapter);
}
}

Some files were not shown because too many files have changed in this diff Show More