Compare commits
108 Commits
attributes
...
master
Author | SHA1 | Date |
---|---|---|
Dave M. | bd0d928f35 | |
Dave M. | 5a5d326d70 | |
Dave M. | b5e96eb7e0 | |
Dave Mc Nicoll | 7c589ab7b4 | |
Dave Mc Nicoll | 589524ca3a | |
Dave Mc Nicoll | 1fac12c928 | |
Dave Mc Nicoll | 9eed2fe1a8 | |
Dave M. | b601939459 | |
Dave M. | e3314f40ac | |
Dave M. | 4ad44f73d7 | |
Dave M. | 68a6636c06 | |
Dave Mc Nicoll | 70c6cde603 | |
Dave Mc Nicoll | 079a3fd110 | |
Dave Mc Nicoll | ec4d02b9d9 | |
Dave Mc Nicoll | 35ceb97241 | |
Dave Mc Nicoll | 83f673b37f | |
Dave Mc Nicoll | 411992c7a8 | |
Dave Mc Nicoll | 5f4f23a8e4 | |
Dave Mc Nicoll | 2fda7e82d7 | |
Dave Mc Nicoll | 573d4cc06b | |
Dave Mc Nicoll | 18313fd7f5 | |
Dave Mc Nicoll | 9503b24467 | |
Dave Mc Nicoll | 3c2ae86653 | |
Dave Mc Nicoll | 9977a25cf5 | |
Dave Mc Nicoll | 3ba8a2a5d0 | |
Dave Mc Nicoll | 4106c8db91 | |
Dave Mc Nicoll | d5cca085bc | |
Dave M. | c56b584140 | |
Dave Mc Nicoll | 8b1c11676d | |
Dave M. | aa07c728fe | |
Dave Mc Nicoll | d4181065cf | |
Dave Mc Nicoll | 62998faf40 | |
Dave M. | feee26cd26 | |
Dave M. | e37944c63a | |
Dave M. | 1d38ae8245 | |
Dave Mc Nicoll | 0c8591f238 | |
Dave Mc Nicoll | bbfd7c02b4 | |
Dave Mc Nicoll | bd9078230d | |
Dave M. | f3be11a590 | |
Dave Mc Nicoll | 2de3139c80 | |
Dave Mc Nicoll | e37eeb85f1 | |
Dave Mc Nicoll | 7de0ecb028 | |
Dave M. | 75231f32b3 | |
Dave Mc Nicoll | 1dd9da6eb6 | |
Dave M. | b38b81d03c | |
Dave M. | 950df5eb99 | |
Dave Mc Nicoll | 9fdebf454d | |
Dave Mc Nicoll | 4571517dc8 | |
Dave M. | 15be1597b8 | |
Dave M. | fa4e686f35 | |
Dave M. | d9fa51fa18 | |
Dave Mc Nicoll | d4d68a029d | |
Dave Mc Nicoll | ea667db552 | |
Dave Mc Nicoll | afe5144dc7 | |
Dave M. | 62792e8325 | |
Dave M. | 3a80fee9c3 | |
Dave Mc Nicoll | 7f8780d328 | |
Dave Mc Nicoll | 9a9f473f3f | |
Dave M. | fab70aec6a | |
Dave Mc Nicoll | c1755d250b | |
Dave Mc Nicoll | acab934f6f | |
Dave M. | 2e377374d4 | |
Dave Mc Nicoll | c2cccc0222 | |
Dave Mc Nicoll | ab582c9318 | |
Dave Mc Nicoll | a4b81f1932 | |
Dave M. | ab1d7e4f5b | |
Dave M. | bfc1e1cf93 | |
Dave M. | 531908b04c | |
Dave M. | 07ae8faaac | |
Dave Mc Nicoll | 756474d460 | |
Dave Mc Nicoll | d297fd9e86 | |
Dave M. | fed7d2e302 | |
Dave M. | 3736fbe0f6 | |
Dave M. | 667c1fe9ab | |
Dave M. | d2bd6c2f58 | |
Dave M. | 953fc35680 | |
Dave M. | 6c6733b503 | |
Dave M. | e641bc321d | |
Dave M. | da5d203768 | |
Dave M. | f77c93ad20 | |
Dave M. | c9d9281c63 | |
Dave Mc Nicoll | 8e0dce3e71 | |
Dave M. | 1927b43999 | |
Dave Mc Nicoll | cc741566fb | |
Dave Mc Nicoll | 8489cb4841 | |
Dave M. | 9ebf7b05d7 | |
Dave M. | 338b599d39 | |
Dave M. | d01624b8aa | |
Dave M. | 01ab6e82d4 | |
Dave Mc Nicoll | a4743b471c | |
Dave Mc Nicoll | bd61522a07 | |
Dave Mc Nicoll | f3712c9cc7 | |
Dave M. | cfaebbecc4 | |
Dave M. | eb6434bb72 | |
Dave M. | 2ad52aba85 | |
Dave Mc Nicoll | 3c5aa51850 | |
Dave M. | 9514a46ae7 | |
Dave M. | cee978ecfd | |
Dave M. | 43880eb428 | |
Dave M. | 58bdce0ea8 | |
Dave M. | fa8adcace1 | |
Dave M. | 36cddf8f78 | |
Dave Mc Nicoll | c6bfaa05f2 | |
Dave Mc Nicoll | c9c6a11ebd | |
Dave M. | d4a9fc8463 | |
Dave M. | 94edb09352 | |
Dave Mc Nicoll | dcccce7893 | |
Dave Mc Nicoll | 31f031715d |
|
@ -6,7 +6,7 @@
|
|||
"authors": [
|
||||
{
|
||||
"name": "Dave Mc Nicoll",
|
||||
"email": "mcndave@gmail.com"
|
||||
"email": "info@mcnd.ca"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
|
|
|
@ -4,7 +4,7 @@ Welcome to the official Ulmus documentation.
|
|||
|
||||
## Quick start
|
||||
|
||||
Creating a simple user entity:
|
||||
Creating a simple user exemple entity:
|
||||
|
||||
*/app/entity/user.php*
|
||||
```php
|
||||
|
@ -21,29 +21,19 @@ class User
|
|||
{
|
||||
use \Ulmus\EntityTrait;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
*/
|
||||
#[Field\Id]
|
||||
public int $id;
|
||||
|
||||
/**
|
||||
* @Field
|
||||
*/
|
||||
#[Field]
|
||||
public string $fullname;
|
||||
|
||||
/**
|
||||
* @Field
|
||||
*/
|
||||
#[Field]
|
||||
public ? string $email;
|
||||
|
||||
/**
|
||||
* @Field('name' => 'is_admin')
|
||||
*/
|
||||
#[Field]
|
||||
public bool $isAdmin = false;
|
||||
|
||||
/**
|
||||
* @DateTime
|
||||
*/
|
||||
#[Field\Datetime]
|
||||
public Datetime $birthday;
|
||||
}
|
||||
```
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# Search Request
|
||||
|
||||
Ulmus comes with a simple search request processor which allows both flexibility and simplicity.
|
||||
|
||||
## Quick start
|
||||
|
||||
Creating a simple user exemple entity:
|
|
@ -15,17 +15,7 @@ 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;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\{ConnectionAdapter, Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder};
|
||||
use Ulmus\{ConnectionAdapter, Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder\Sql\MysqlQueryBuilder, Entity};
|
||||
|
||||
trait DefaultAdapterTrait
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ trait DefaultAdapterTrait
|
|||
|
||||
public function queryBuilderClass() : string
|
||||
{
|
||||
return QueryBuilder::class;
|
||||
return MysqlQueryBuilder::class;
|
||||
}
|
||||
|
||||
public function tableSyntax() : array
|
||||
|
@ -30,9 +30,12 @@ trait DefaultAdapterTrait
|
|||
return $this->database;
|
||||
}
|
||||
|
||||
public function schemaTable(ConnectionAdapter $parent, $databaseName, string $tableName) /* : ? object */
|
||||
public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|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);
|
||||
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 mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
|
||||
|
@ -92,4 +95,27 @@ trait DefaultAdapterTrait
|
|||
|
||||
return $typeOnly ? $type : $type . ( isset($length) ? "($length" . ( ! empty($precision) ? ",$precision" : "" ) . ")" : "" );
|
||||
}
|
||||
|
||||
public function whitelistAttributes(array &$parameters) : void
|
||||
{
|
||||
$parameters = array_intersect_key($parameters, array_flip(static::ALLOWED_ATTRIBUTES));
|
||||
}
|
||||
|
||||
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
|
||||
{
|
||||
if ($field['previous']) {
|
||||
$position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']['field'], AdapterInterface::IDENTIFIER_FIELD));
|
||||
}
|
||||
else {
|
||||
$position = "FIRST";
|
||||
}
|
||||
|
||||
return implode(" ", [
|
||||
strtoupper($field['action']),
|
||||
$this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
|
||||
$definition->getSqlType(),
|
||||
$definition->getSqlParams(),
|
||||
$position,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -9,10 +9,14 @@ use Ulmus\Exception\AdapterConfigurationException;
|
|||
use Ulmus\Ulmus;
|
||||
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\{Entity\InformationSchema\Table, Repository, QueryBuilder};
|
||||
use Ulmus\{Entity\InformationSchema\Table, Migration\MigrateInterface, Repository, QueryBuilder};
|
||||
|
||||
class MsSQL implements AdapterInterface {
|
||||
use DefaultAdapterTrait;
|
||||
class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
|
||||
const ALLOWED_ATTRIBUTES = [
|
||||
'default', 'primary_key', 'auto_increment',
|
||||
];
|
||||
|
||||
const DSN_PREFIX = "sqlsrv";
|
||||
|
||||
|
@ -47,7 +51,9 @@ class MsSQL implements AdapterInterface {
|
|||
public function __construct(
|
||||
?string $server = null,
|
||||
?string $database = null,
|
||||
#[\SensitiveParameter]
|
||||
?string $username = null,
|
||||
#[\SensitiveParameter]
|
||||
?string $password = null,
|
||||
?int $port = null
|
||||
) {
|
||||
|
@ -80,7 +86,7 @@ class MsSQL implements AdapterInterface {
|
|||
$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 {
|
||||
|
@ -200,9 +206,10 @@ class MsSQL implements AdapterInterface {
|
|||
}
|
||||
}
|
||||
|
||||
public function escapeIdentifier(string $segment, int $type) : string
|
||||
public static function escapeIdentifier(string $segment, int $type) : string
|
||||
{
|
||||
switch($type) {
|
||||
default:
|
||||
case static::IDENTIFIER_SCHEMA:
|
||||
case static::IDENTIFIER_DATABASE:
|
||||
case static::IDENTIFIER_TABLE:
|
||||
|
@ -214,26 +221,6 @@ class MsSQL implements AdapterInterface {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
@ -246,6 +233,6 @@ class MsSQL implements AdapterInterface {
|
|||
|
||||
public function queryBuilderClass() : string
|
||||
{
|
||||
return QueryBuilder\MssqlQueryBuilder::class;
|
||||
return QueryBuilder\Sql\MssqlQueryBuilder::class;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,17 +2,22 @@
|
|||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\Entity\InformationSchema\Table;
|
||||
use Ulmus\QueryBuilder;
|
||||
use Ulmus\Repository;
|
||||
use Ulmus\ConnectionAdapter;
|
||||
use Ulmus\Entity\Mysql\Table;
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\Migration\MigrateInterface;
|
||||
use Ulmus\QueryBuilder\Sql;
|
||||
use Ulmus\Common\PdoObject;
|
||||
|
||||
use Ulmus\Exception\AdapterConfigurationException;
|
||||
use Ulmus\Ulmus;
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\Repository;
|
||||
|
||||
class MySQL implements AdapterInterface {
|
||||
use DefaultAdapterTrait;
|
||||
class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
|
||||
const ALLOWED_ATTRIBUTES = [
|
||||
'default', 'primary_key', 'auto_increment', 'update',
|
||||
];
|
||||
|
||||
const DSN_PREFIX = "mysql";
|
||||
|
||||
|
@ -20,9 +25,9 @@ class MySQL implements AdapterInterface {
|
|||
|
||||
public string $database;
|
||||
|
||||
public string $username;
|
||||
protected string $username;
|
||||
|
||||
public string $password;
|
||||
protected string $password;
|
||||
|
||||
public string $charset = "utf8mb4";
|
||||
|
||||
|
@ -33,7 +38,9 @@ class MySQL implements AdapterInterface {
|
|||
public function __construct(
|
||||
?string $hostname = null,
|
||||
?string $database = null,
|
||||
#[\SensitiveParameter]
|
||||
?string $username = null,
|
||||
#[\SensitiveParameter]
|
||||
?string $password = null,
|
||||
?int $port = null,
|
||||
?string $charset = null
|
||||
|
@ -73,7 +80,7 @@ class MySQL implements AdapterInterface {
|
|||
$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 {
|
||||
|
@ -135,9 +142,17 @@ class MySQL implements AdapterInterface {
|
|||
}
|
||||
}
|
||||
|
||||
public function escapeIdentifier(string $segment, int $type) : string
|
||||
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
|
||||
{
|
||||
switch($type) {
|
||||
default:
|
||||
case static::IDENTIFIER_DATABASE:
|
||||
case static::IDENTIFIER_TABLE:
|
||||
case static::IDENTIFIER_FIELD:
|
||||
|
@ -148,27 +163,21 @@ class MySQL implements AdapterInterface {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
|
||||
class MySQLFieldMapper extends SqlFieldMapper
|
||||
{
|
||||
public readonly string $type;
|
||||
|
||||
public readonly string $length;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,46 +2,45 @@
|
|||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Collator;
|
||||
use Ulmus\Common\PdoObject;
|
||||
|
||||
use Ulmus\Entity\Sqlite\Table;
|
||||
use Ulmus\Exception\AdapterConfigurationException;
|
||||
use Ulmus\ConnectionAdapter;
|
||||
use Ulmus\Entity;
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\{Repository, QueryBuilder, Ulmus};
|
||||
use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder};
|
||||
|
||||
class SQLite implements AdapterInterface {
|
||||
use DefaultAdapterTrait;
|
||||
class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
|
||||
const ALLOWED_ATTRIBUTES = [
|
||||
'default', 'primary_key', 'auto_increment', 'collate nocase', 'collate binary', 'collate rtrim',
|
||||
];
|
||||
|
||||
const DSN_PREFIX = "sqlite";
|
||||
|
||||
public string $path;
|
||||
|
||||
public array $pragma;
|
||||
|
||||
public function __construct(
|
||||
? string $path = null,
|
||||
? array $pragma = null
|
||||
) {
|
||||
if ($path !== null) {
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
if ($pragma !== null) {
|
||||
$this->pragma = $pragma;
|
||||
}
|
||||
}
|
||||
public null|string $path = null,
|
||||
public null|array $pragmaBegin = null,
|
||||
public null|array $pragmaClose = null,
|
||||
) { }
|
||||
|
||||
public function connect() : PdoObject
|
||||
{
|
||||
try {
|
||||
$pdo = new PdoObject($this->buildDataSourceName(), null, null);
|
||||
$pdo = new PdoObject\SqlitePdoObject($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,13 +57,15 @@ class SQLite implements AdapterInterface {
|
|||
public function setup(array $configuration) : void
|
||||
{
|
||||
$this->path = $configuration['path'] ?? "";
|
||||
$this->pragma = $configuration['pragma'] ?? [];
|
||||
$this->pragmaBegin = array_filter($configuration['pragma_begin'] ?? []);
|
||||
$this->pragmaClose = array_filter($configuration['pragma_close'] ?? []);
|
||||
}
|
||||
|
||||
# https://sqlite.org/lang_keywords.html
|
||||
public function escapeIdentifier(string $segment, int $type) : string
|
||||
public static function escapeIdentifier(string $segment, int $type) : string
|
||||
{
|
||||
switch($type) {
|
||||
default:
|
||||
case static::IDENTIFIER_DATABASE:
|
||||
case static::IDENTIFIER_TABLE:
|
||||
case static::IDENTIFIER_FIELD:
|
||||
|
@ -87,15 +88,16 @@ class SQLite implements AdapterInterface {
|
|||
return substr($base, 0, strrpos($base, '.') ?: strlen($base));
|
||||
}
|
||||
|
||||
public function schemaTable(string $databaseName, string $tableName) : null|object
|
||||
public function schemaTable(ConnectionAdapter $adapter, string $databaseName, string $tableName) : null|object
|
||||
{
|
||||
return Table::repository()->loadOneFromField(Table::field('tableName'), $tableName);
|
||||
return Entity\Sqlite\Table::repository(Repository::DEFAULT_ALIAS, $adapter)
|
||||
->select(\Ulmus\Common\Sql::raw('this.*'))
|
||||
->loadOneFromField(Entity\Sqlite\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) ) {
|
||||
|
@ -113,6 +115,10 @@ class SQLite implements AdapterInterface {
|
|||
break;
|
||||
|
||||
case "array":
|
||||
$type = "JSON";
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case "string":
|
||||
$type = "TEXT";
|
||||
$length = null;
|
||||
|
@ -128,26 +134,11 @@ class SQLite implements AdapterInterface {
|
|||
}
|
||||
}
|
||||
|
||||
return $typeOnly ? $type : $type . ( $length ? "($length" . ( isset($precision) ? ",$precision" : "" ) . ")" : "" );
|
||||
if (in_array($type, [ 'JSON', 'TEXT', 'BLOB', 'GEOMETRY' ])) {
|
||||
unset($field->default);
|
||||
}
|
||||
|
||||
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;
|
||||
return $typeOnly ? $type : $type . ( $length ? "($length" . ")" : "" );
|
||||
}
|
||||
|
||||
public function tableSyntax() : array
|
||||
|
@ -166,7 +157,7 @@ class SQLite implements AdapterInterface {
|
|||
|
||||
public function queryBuilderClass() : string
|
||||
{
|
||||
return QueryBuilder\SqliteQueryBuilder::class;
|
||||
return QueryBuilder\Sql\SqliteQueryBuilder::class;
|
||||
}
|
||||
|
||||
public function exportFunctions(PdoObject $pdo) : void
|
||||
|
@ -175,6 +166,14 @@ class SQLite implements AdapterInterface {
|
|||
$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,5 +192,72 @@ class SQLite implements AdapterInterface {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
interface SqlAdapterInterface
|
||||
{
|
||||
public static function escapeIdentifier(string $segment, int $type) : string;
|
||||
public function defaultEngine() : ? string;
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?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 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 mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
|
||||
{
|
||||
$mapper = new SqlFieldMapper($field);
|
||||
|
||||
return $typeOnly ? $mapper->type : $mapper->render();
|
||||
}
|
||||
|
||||
public function whitelistAttributes(array &$parameters) : void
|
||||
{
|
||||
$parameters = array_intersect_key($parameters, array_flip(static::ALLOWED_ATTRIBUTES));
|
||||
}
|
||||
|
||||
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
|
||||
{
|
||||
if (! empty($field['previous']) ) {
|
||||
$position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']->field, AdapterInterface::IDENTIFIER_FIELD));
|
||||
}
|
||||
else {
|
||||
$position = "FIRST";
|
||||
}
|
||||
|
||||
return implode(" ", [
|
||||
strtoupper($field['action']),
|
||||
$this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
|
||||
$definition->getSqlType(),
|
||||
$definition->getSqlParams(),
|
||||
$position,
|
||||
]);
|
||||
}
|
||||
|
||||
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 splitAlterQuery() : bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\Entity;
|
||||
|
||||
class SqlFieldMapper
|
||||
{
|
||||
public readonly string $type;
|
||||
|
||||
public readonly 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
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Classes;
|
||||
|
||||
class Collation implements \Notes\Annotation {
|
||||
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Classes;
|
||||
|
||||
class Method implements \Notes\Annotation {
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?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";
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?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');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Property;
|
||||
|
||||
class GroupBy implements \Notes\Annotation {
|
||||
|
||||
public array $fields = [];
|
||||
|
||||
public function __construct(...$field)
|
||||
{
|
||||
if ( $field ) {
|
||||
$this->fields = $field;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Property;
|
||||
|
||||
class Having extends Where {}
|
|
@ -1,31 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Property;
|
||||
|
||||
class On extends Where {}
|
|
@ -1,14 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Property\Relation;
|
||||
|
||||
class Ignore implements \Notes\Annotation {}
|
|
@ -1,19 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,13 +3,20 @@
|
|||
namespace Ulmus\Attribute;
|
||||
|
||||
use Ulmus\Common\EntityField;
|
||||
use Ulmus\Repository;
|
||||
|
||||
class Attribute
|
||||
{
|
||||
public static function handleArrayField(null|\Stringable|string|array $field) : mixed
|
||||
public static function handleArrayField(null|\Stringable|string|array $field, null|string|bool $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : mixed
|
||||
{
|
||||
if ( is_array($field) ) {
|
||||
$class = array_shift($field);
|
||||
$field[1] ??= $alias;
|
||||
|
||||
if (is_array($field[0])) {
|
||||
$field[] = $separator;
|
||||
return $class::fields(...$field);
|
||||
}
|
||||
|
||||
return $class::field(...$field);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Attribute;
|
||||
|
||||
enum ConstrainActionEnum : string
|
||||
{
|
||||
case Cascade = 'cascade';
|
||||
|
||||
case NoAction = 'noaction';
|
||||
|
||||
case Restrict = 'restrict';
|
||||
|
||||
case SetNull = 'setnull';
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Attribute;
|
||||
|
||||
enum IndexTypeEnum : string
|
||||
{
|
||||
case Primary = 'primary';
|
||||
|
||||
case Index = 'index';
|
||||
|
||||
case Unique = 'unique';
|
||||
|
||||
case Spatial = 'spatial';
|
||||
|
||||
case Fulltext = 'fulltext';
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Attribute\Obj;
|
||||
|
||||
interface AdapterAttributeInterface
|
||||
{
|
||||
public function adapter() : false|string;
|
||||
}
|
|
@ -4,5 +4,7 @@ namespace Ulmus\Attribute\Obj;
|
|||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Collation {
|
||||
|
||||
public function __construct(
|
||||
public string $name = ""
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?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,
|
||||
) {}
|
||||
}
|
|
@ -3,12 +3,18 @@
|
|||
namespace Ulmus\Attribute\Obj;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Table {
|
||||
class Table implements AdapterAttributeInterface {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Attribute\Property;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class Collation {
|
||||
public function __construct(
|
||||
public string $name = ""
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?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 = "",
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?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 = "",
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?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 = "",
|
||||
) {}
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
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.
|
||||
|
@ -10,7 +13,7 @@ namespace Ulmus\Attribute\Property\Field;
|
|||
class ForeignKey extends PrimaryKey {
|
||||
public function __construct(
|
||||
public ? string $name = null,
|
||||
public ? string $type = 'bigint',
|
||||
public ? string $type = null,
|
||||
public null|int|string $length = null,
|
||||
public ? int $precision = null,
|
||||
public array $attributes = [
|
||||
|
@ -20,5 +23,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?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 = "",
|
||||
) {}
|
||||
}
|
|
@ -5,8 +5,12 @@ namespace Ulmus\Attribute\Property\Field;
|
|||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class Time extends \Ulmus\Attribute\Property\Field
|
||||
{
|
||||
public function __construct(? string $type = "time", ? int $length = null)
|
||||
{
|
||||
parent::__construct($type, $length);
|
||||
}
|
||||
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,
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,12 @@ namespace Ulmus\Attribute\Property\Field;
|
|||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class Timestamp extends \Ulmus\Attribute\Property\Field
|
||||
{
|
||||
public function __construct(? string $type = "timestamp", ? int $length = null)
|
||||
{
|
||||
parent::__construct($type, $length);
|
||||
}
|
||||
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,
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?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 = "",
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?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 = "",
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?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 = "",
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Attribute\Property;
|
||||
|
||||
use Ulmus\Attribute\IndexTypeEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class Index {
|
||||
public function __construct(
|
||||
public IndexTypeEnum $type = IndexTypeEnum::Index,
|
||||
) {}
|
||||
}
|
|
@ -5,11 +5,11 @@ namespace Ulmus\Attribute\Property;
|
|||
use Ulmus\Attribute\Attribute;
|
||||
|
||||
#[\Attribute]
|
||||
class Join {
|
||||
class Join implements ResettablePropertyInterface {
|
||||
public function __construct(
|
||||
public string $type,
|
||||
public null|string|\Stringable|array $key = null,
|
||||
public null|string|\Stringable $foreignKey = null,
|
||||
public null|string|\Stringable|array $foreignKey = null,
|
||||
public null|string $entity = null,
|
||||
public null|string $alias = null,
|
||||
) {
|
||||
|
|
|
@ -10,10 +10,11 @@ class OrWhere extends Where {
|
|||
public function __construct(
|
||||
public string|\Stringable|array $field,
|
||||
public mixed $value = null,
|
||||
public null|string $operator = null,
|
||||
public null|string $condition = Query\Where::CONDITION_OR,
|
||||
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,
|
||||
) {
|
||||
|
||||
$this->key = Attribute::handleArrayField($this->key);
|
||||
parent::__construct($field, $value, $operator, $condition, $fieldValue, $generateValue);
|
||||
}
|
||||
}
|
|
@ -5,11 +5,11 @@ namespace Ulmus\Attribute\Property;
|
|||
use Ulmus\Attribute\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class Relation {
|
||||
class Relation implements ResettablePropertyInterface {
|
||||
public function __construct(
|
||||
public string $type,
|
||||
public Relation\RelationTypeEnum|string $type,
|
||||
public \Stringable|string|array $key = "",
|
||||
public null|\Closure $generateKey = null,
|
||||
public null|\Closure|array $generateKey = null,
|
||||
public null|\Stringable|string|array $foreignKey = null,
|
||||
public null|\Stringable|string|array $foreignField = null,
|
||||
public array $foreignKeys = [],
|
||||
|
@ -20,7 +20,7 @@ class Relation {
|
|||
public null|\Stringable|string|array $field = null,
|
||||
public null|string $entity = null,
|
||||
public null|string $join = null,
|
||||
public string $function = "loadAll",
|
||||
public null|string $function = null,
|
||||
) {
|
||||
$this->key = Attribute::handleArrayField($this->key);
|
||||
$this->foreignKey = Attribute::handleArrayField($this->foreignKey);
|
||||
|
@ -29,14 +29,13 @@ class Relation {
|
|||
$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 annotation seems to be missing an `entity` entry.");
|
||||
throw new \Exception("Your @Relation attribute seems to be missing an `entity` entry.");
|
||||
}
|
||||
|
||||
return new $e();
|
||||
|
@ -55,17 +54,17 @@ class Relation {
|
|||
|
||||
public function isOneToOne() : bool
|
||||
{
|
||||
return $this->normalizeType() === 'onetoone';
|
||||
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::oneToOne : $this->normalizeType() === 'onetoone';
|
||||
}
|
||||
|
||||
public function isOneToMany() : bool
|
||||
{
|
||||
return $this->normalizeType() === 'onetomany';
|
||||
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::oneToMany : $this->normalizeType() === 'onetomany';
|
||||
}
|
||||
|
||||
public function isManyToMany() : bool
|
||||
{
|
||||
return $this->normalizeType() === 'manytomany';
|
||||
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::manyToMany : $this->normalizeType() === 'manytomany';
|
||||
}
|
||||
|
||||
public function function() : string
|
||||
|
@ -74,7 +73,7 @@ class Relation {
|
|||
return $this->function;
|
||||
}
|
||||
elseif ($this->isOneToOne()) {
|
||||
return 'load';
|
||||
return 'loadOne';
|
||||
}
|
||||
|
||||
return 'loadAll';
|
||||
|
|
|
@ -3,4 +3,10 @@
|
|||
namespace Ulmus\Attribute\Property\Relation;
|
||||
|
||||
#[\Attribute]
|
||||
class Ignore {}
|
||||
class Ignore {
|
||||
|
||||
public function __construct(
|
||||
public bool $ignoreExport = false,
|
||||
) {}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Attribute\Property\Relation;
|
||||
|
||||
enum RelationTypeEnum
|
||||
{
|
||||
case oneToOne;
|
||||
case oneToMany;
|
||||
case manyToMany;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Attribute\Property;
|
||||
|
||||
interface ResettablePropertyInterface
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Attribute\Property;
|
||||
|
||||
use Ulmus\Attribute\IndexTypeEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class Unique {
|
||||
|
||||
public function __construct(
|
||||
public IndexTypeEnum $type = IndexTypeEnum::Unique,
|
||||
) {}
|
||||
}
|
|
@ -3,11 +3,19 @@
|
|||
namespace Ulmus\Attribute\Property;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class Virtual extends Field {
|
||||
class Virtual extends Field implements ResettablePropertyInterface {
|
||||
|
||||
public bool $readonly = true;
|
||||
|
||||
public function __construct(
|
||||
public ? string $method = null,
|
||||
) {}
|
||||
public null|string|array $method = null,
|
||||
public ? \Closure $closure = null,
|
||||
) {
|
||||
$this->method ??= [ static::class, 'noop' ];
|
||||
}
|
||||
|
||||
public static function noop() : null
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,27 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
namespace Ulmus\Common;
|
||||
|
||||
use Ulmus\Annotation\Annotation;
|
||||
use Ulmus\Attribute;
|
||||
use Ulmus\Attribute\Property\{ Field };
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\Ulmus,
|
||||
Ulmus\Adapter\AdapterInterface,
|
||||
Ulmus\Annotation\Property\Field,
|
||||
Ulmus\Query\WhereRawParameter;
|
||||
|
||||
class EntityField implements WhereRawParameter
|
||||
|
@ -30,7 +28,7 @@ class EntityField implements WhereRawParameter
|
|||
|
||||
public function name($useAlias = true) : string
|
||||
{
|
||||
$name = $this->entityResolver->searchFieldAnnotation($this->name, [ Attribute\Property\Field::class, Field::class ] )->name ?? $this->name;
|
||||
$name = $this->entityResolver->searchFieldAnnotation($this->name, [ Field::class ] )->name ?? $this->name;
|
||||
|
||||
$name = $this->entityResolver->databaseAdapter()->adapter()->escapeIdentifier($name, AdapterInterface::IDENTIFIER_FIELD);
|
||||
|
||||
|
@ -56,12 +54,19 @@ class EntityField implements WhereRawParameter
|
|||
$definition = new FieldDefinition($adapter, $field);
|
||||
|
||||
return implode(" ", [
|
||||
$definition->getSqlName(),
|
||||
$adapter->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
|
||||
$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
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
namespace Ulmus\Common;
|
||||
|
||||
use Notes\Common\ReflectedClass;
|
||||
use Notes\Common\ReflectedProperty;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Ulmus\Ulmus,
|
||||
Ulmus\Annotation\Classes\Table,
|
||||
Ulmus\Annotation\Property\Field,
|
||||
Ulmus\Annotation\Property\Virtual,
|
||||
Ulmus\Annotation\Property\Relation,
|
||||
Ulmus\Attribute;
|
||||
Ulmus\Attribute\Obj\Table,
|
||||
Ulmus\Attribute\Obj\AdapterAttributeInterface,
|
||||
Ulmus\Attribute\Property\Field,
|
||||
Ulmus\Attribute\Property\Relation,
|
||||
Ulmus\Attribute\Property\Virtual;
|
||||
|
||||
use Notes\Annotation;
|
||||
use Notes\Common\ReflectedAttribute;
|
||||
|
||||
use Notes\ObjectReflection;
|
||||
|
||||
|
@ -24,13 +26,7 @@ class EntityResolver {
|
|||
|
||||
public string $entityClass;
|
||||
|
||||
public array $uses;
|
||||
|
||||
public array $class;
|
||||
|
||||
public array $properties;
|
||||
|
||||
public array $methods;
|
||||
public ReflectedClass $reflectedClass;
|
||||
|
||||
protected array $fieldList = [];
|
||||
|
||||
|
@ -38,14 +34,10 @@ class EntityResolver {
|
|||
{
|
||||
$this->entityClass = $entityClass;
|
||||
|
||||
list($this->uses, $this->class, $this->methods, $this->properties) = array_values(
|
||||
ObjectReflection::fromClass($entityClass, $cache)->read()
|
||||
);
|
||||
|
||||
$this->resolveAnnotations();
|
||||
$this->reflectedClass = ObjectReflection::fromClass($entityClass, $cache)->reflectClass();
|
||||
}
|
||||
|
||||
public function field($name, $fieldKey = self::KEY_ENTITY_NAME, $throwException = true) : ? array
|
||||
public function field($name, $fieldKey = self::KEY_ENTITY_NAME, $throwException = true) : null|ReflectedProperty
|
||||
{
|
||||
try{
|
||||
return $this->fieldList($fieldKey)[$name] ?? null;
|
||||
|
@ -59,43 +51,34 @@ class EntityResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function searchField($name) : null|array
|
||||
public function searchField($name) : null|ReflectedProperty
|
||||
{
|
||||
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->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 )) {
|
||||
foreach($this->reflectedClass->getProperties(true) as $item) {
|
||||
foreach($item->getAttributes() as $tag) {
|
||||
if ( $tag->object instanceof Field ) {
|
||||
if ( $skipVirtual && $tag->object instanceof 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:
|
||||
|
@ -112,13 +95,15 @@ 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 ( 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];
|
||||
if ( $property ) {
|
||||
foreach($property->getAttributes() as $tag) {
|
||||
if ( $tag->object instanceof Relation ) {
|
||||
return $property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,12 +111,8 @@ class EntityResolver {
|
|||
return [];
|
||||
}
|
||||
catch(\Throwable $e) {
|
||||
# if ( $throwException) {
|
||||
throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}");
|
||||
# }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function searchFieldAnnotation(string $field, array|object|string $annotationType, bool $caseSensitive = true) : ? object
|
||||
|
@ -139,25 +120,19 @@ class EntityResolver {
|
|||
return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null;
|
||||
}
|
||||
|
||||
public function searchFieldAnnotationList(string $field, array|object|string $annotationType, bool $caseSensitive = true) : array
|
||||
public function searchFieldAnnotationList(string $field, array|object|string $attributeType, bool $caseSensitive = true) : false|array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
$search = $caseSensitive ? $this->properties : array_change_key_case($this->properties, \CASE_LOWER);
|
||||
$properties = $this->reflectedClass->getProperties(true);
|
||||
|
||||
$annotations = is_array($annotationType) ? $annotationType : [ $annotationType ];
|
||||
$search = $caseSensitive ? $properties : array_change_key_case($properties, \CASE_LOWER);
|
||||
|
||||
if ( null !== ( $search[$field] ?? null ) ) {
|
||||
foreach($search[$field]['tags'] ?? [] as $tag) {
|
||||
foreach($annotations as $annotation) {
|
||||
if ( $tag['object'] instanceof $annotation ) {
|
||||
$list[] = $tag['object'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_map(fn(ReflectedAttribute $e) => $e->object, $search[$field]->getAttributes((array) $attributeType));
|
||||
}
|
||||
|
||||
return $list;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function tableName($required = false) : string
|
||||
|
@ -173,7 +148,7 @@ class EntityResolver {
|
|||
return $table->name ?? "";
|
||||
}
|
||||
|
||||
public function tableAnnotation($required = false) : Table|Attribute\Obj\Table
|
||||
public function tableAnnotation($required = false) : null|Table
|
||||
{
|
||||
if ( null === $table = $this->getTableAttribute() ) {
|
||||
if ($required) {
|
||||
|
@ -184,17 +159,17 @@ class EntityResolver {
|
|||
return $table;
|
||||
}
|
||||
|
||||
public function databaseName() : ? string
|
||||
public function databaseName() : null|string
|
||||
{
|
||||
return $this->tableAnnotation(false)->database ?? $this->databaseAdapter()->adapter()->databaseName() ?? null;
|
||||
}
|
||||
|
||||
public function sqlAdapter() : \Ulmus\ConnectionAdapter
|
||||
{
|
||||
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.");
|
||||
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.");
|
||||
}
|
||||
else {
|
||||
return $adapter;
|
||||
|
@ -213,7 +188,7 @@ class EntityResolver {
|
|||
return $this->sqlAdapter();
|
||||
}
|
||||
|
||||
public function schemaName(bool $required = false) : ? string
|
||||
public function schemaName(bool $required = false) : null|string
|
||||
{
|
||||
if ( null === $table = $this->getTableAttribute() ) {
|
||||
throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation");
|
||||
|
@ -226,10 +201,10 @@ class EntityResolver {
|
|||
return $table->schema ?? null;
|
||||
}
|
||||
|
||||
public function getPrimaryKeyField() : ? array
|
||||
public function getPrimaryKeyField() : null|array
|
||||
{
|
||||
foreach($this->fieldList() as $key => $value) {
|
||||
$field = $this->searchFieldAnnotation($key, [ Attribute\Property\Field::class, Field::class ]);
|
||||
$field = $this->searchFieldAnnotation($key, [ Field::class ]);
|
||||
if ( null !== $field ) {
|
||||
if ( false !== ( $field->attributes['primary_key'] ?? false ) ) {
|
||||
return [ $key => $field ];
|
||||
|
@ -250,107 +225,24 @@ class EntityResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
protected function getAdapterInterfaceAttribute() : null|object
|
||||
{
|
||||
return $this->getAttributeImplementing(AdapterAttributeInterface::class);
|
||||
}
|
||||
|
||||
protected function getTableAttribute()
|
||||
{
|
||||
return $this->getAnnotationFromClassname(Attribute\Obj\Table::class, false) ?: $this->getAnnotationFromClassname( Table::class );
|
||||
return $this->getAttributeImplementing(Table::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an annotation into it's object's counterpart
|
||||
*/
|
||||
public function getAnnotationFromClassname(string $className, bool $throwError = true) : ? object
|
||||
public function getAttributeImplementing(string $interface) : null|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->reflectedClass->getAttributes(true) as $item) {
|
||||
if ($item->object instanceof $interface) {
|
||||
return $item->object;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,27 +11,37 @@ 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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
catch (\Throwable $e) {
|
||||
throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters));
|
||||
}
|
||||
|
||||
return $statement;
|
||||
}
|
||||
}
|
||||
catch (\PDOException $e) {
|
||||
throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters));
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->onClose ?? null) {
|
||||
call_user_func($this->onClose, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function runQuery(string $sql, array $parameters = []): ? PDOStatement
|
||||
public function runQuery(string $sql, array $parameters = []): ? static
|
||||
{
|
||||
static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]);
|
||||
|
||||
|
@ -40,47 +50,56 @@ class PdoObject extends PDO {
|
|||
return $this->execute($statement, $parameters, true);
|
||||
}
|
||||
}
|
||||
catch (\PDOException $e) {
|
||||
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));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function runInsertQuery(string $sql, array $parameters = [])
|
||||
public function runInsertQuery(string $sql, array $parameters = []) : ? static
|
||||
{
|
||||
return $this->runQuery($sql, $parameters);
|
||||
}
|
||||
|
||||
public function runUpdateQuery(string $sql, array $parameters = [])
|
||||
public function runUpdateQuery(string $sql, array $parameters = []) : ? static
|
||||
{
|
||||
return $this->runQuery($sql, $parameters);
|
||||
}
|
||||
|
||||
public function runDeleteQuery(string $sql, array $parameters = []): ? PDOStatement
|
||||
public function runDeleteQuery(string $sql, array $parameters = []) : ? static
|
||||
{
|
||||
return $this->runQuery($sql, $parameters);
|
||||
}
|
||||
|
||||
public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true): ? PDOStatement
|
||||
public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true) : ? static
|
||||
{
|
||||
$this->executionStatus = false;
|
||||
$this->lastInsertId = null;
|
||||
|
||||
try {
|
||||
if ( ! $this->inTransaction() ) {
|
||||
$this->beginTransaction();
|
||||
}
|
||||
|
||||
$this->executionStatus = empty($parameters) ? $statement->execute() : $statement->execute($parameters);
|
||||
$this->bindVariables($statement, $parameters);
|
||||
|
||||
$this->executionStatus = $statement->execute();
|
||||
|
||||
if ( $this->executionStatus ) {
|
||||
$statement->lastInsertId = $this->lastInsertId();
|
||||
$this->lastInsertId = $this->lastInsertId();
|
||||
$this->rowCount = $statement->rowCount();
|
||||
|
||||
if ( $commit ) {
|
||||
$this->commit();
|
||||
}
|
||||
|
||||
return $statement;
|
||||
return $this;
|
||||
}
|
||||
else {
|
||||
throw new \PDOException($statement->errorCode() . " - " . json_encode($statement->errorInfo()));
|
||||
|
@ -92,13 +111,41 @@ class PdoObject extends PDO {
|
|||
throw $e;
|
||||
}
|
||||
catch (\Throwable $e) {
|
||||
if ( function_exists("debogueur") ) {
|
||||
debogueur($statement, $parameters, $commit);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?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 false;
|
||||
}
|
||||
|
||||
public function rollback() : bool
|
||||
{
|
||||
if (0 !== $this->openedTransaction) {
|
||||
return $this->exec('ROLLBACK TO transaction_' . $this->openedTransaction--) !== false;
|
||||
}
|
||||
|
||||
return parent::rollback();
|
||||
}
|
||||
|
||||
protected function openTransaction() : bool
|
||||
{
|
||||
return parent::beginTransaction();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?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 false;
|
||||
}
|
||||
|
||||
protected function openTransaction(): bool
|
||||
{
|
||||
return $this->exec("BEGIN IMMEDIATE TRANSACTION") !== false;
|
||||
}
|
||||
}
|
|
@ -56,7 +56,8 @@ abstract class Sql {
|
|||
$this->identifier = $identifier;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
};
|
||||
|
@ -67,7 +68,7 @@ abstract class Sql {
|
|||
return static::identifier($sql);
|
||||
}
|
||||
|
||||
public static function escape($value)
|
||||
public static function escape($value) : mixed
|
||||
{
|
||||
switch(true) {
|
||||
case is_object($value):
|
||||
|
@ -83,8 +84,12 @@ abstract class Sql {
|
|||
return $value;
|
||||
}
|
||||
|
||||
public static function parameter($value) : string
|
||||
public static function collate(string $name) : 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,26 +2,23 @@
|
|||
|
||||
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(string $name = "default", array $configuration = [], bool $default = false)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
$this->configuration = $configuration;
|
||||
|
||||
public function __construct(
|
||||
public string $name = "default",
|
||||
protected array $configuration = [],
|
||||
public bool $default = false,
|
||||
public ? CacheInterface $cacheObject = null
|
||||
) {
|
||||
Ulmus::registerAdapter($this, $default);
|
||||
}
|
||||
|
||||
|
@ -33,7 +30,7 @@ class ConnectionAdapter
|
|||
$this->adapter = $this->instanciateAdapter($adapterName);
|
||||
}
|
||||
else {
|
||||
throw new \InvalidArgumentException("Adapter not found within your configuration array.");
|
||||
throw new \InvalidArgumentException(sprintf("Adapter not found within your configuration array. (%s)", json_encode($connection)));
|
||||
}
|
||||
|
||||
$this->adapter->setup($connection);
|
||||
|
@ -77,7 +74,7 @@ class ConnectionAdapter
|
|||
* @param string $name An Ulmus adapter or full class name implementing AdapterInterface
|
||||
* @return AdapterInterface
|
||||
*/
|
||||
protected function instanciateAdapter($name) : AdapterInterface
|
||||
protected function instanciateAdapter(string $name) : AdapterInterface
|
||||
{
|
||||
$class = substr($name, 0, 2) === "\\" ? $name : sprintf("\\%s\\Adapter\\%s", __NAMESPACE__, $name);
|
||||
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
<?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 (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) : 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 ] );
|
||||
|
||||
if ($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;
|
||||
}
|
||||
|
||||
# @TODO Must fix recursive bug.. this last check is way too basic to work
|
||||
if ( $entity->__isset($name) && ($relation->entity ?? $relation->bridge) !== static::class ) {
|
||||
if ( null !== $value = $entity->__isset($name) ?? null ) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?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;
|
||||
}
|
|
@ -12,12 +12,20 @@ class Datetime extends \DateTime implements EntityObjectInterface {
|
|||
{
|
||||
$value = $arguments[0];
|
||||
|
||||
try {
|
||||
# From Timestamp
|
||||
if ( is_numeric($value) ) {
|
||||
return new static("@$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);
|
||||
}
|
||||
|
||||
return new static($value);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function save()
|
||||
|
|
|
@ -2,123 +2,124 @@
|
|||
|
||||
namespace Ulmus\Entity\InformationSchema;
|
||||
|
||||
use Notes\Common\ReflectedProperty;
|
||||
use Ulmus\Entity\Field\Datetime;
|
||||
|
||||
/**
|
||||
* @Table('name' => "columns", 'database' => "information_schema")
|
||||
*/
|
||||
use Ulmus\{Attribute\Obj\Table};
|
||||
use Ulmus\Attribute\Property\{Field, Filter, FilterJoin, Relation, Join, Virtual, Where};
|
||||
|
||||
#[Table(name: "columns", database: "information_schema")]
|
||||
class Column
|
||||
{
|
||||
use \Ulmus\EntityTrait;
|
||||
|
||||
/**
|
||||
* @Field('name' => "TABLE_CATALOG", 'length' => 512)
|
||||
*/
|
||||
#[Field\Id]
|
||||
public ? id $srs_id;
|
||||
|
||||
#[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' => [ 'primary_key' => true ])
|
||||
*/
|
||||
#[Field(name: "COLUMN_NAME", length: 64, attributes: [ 'unsigned' => 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)
|
||||
*/
|
||||
#[Field(name: "COLUMN_KEY", length: 3)]
|
||||
public string $key;
|
||||
|
||||
/**
|
||||
* @Field('name' => "EXTRA", 'length' => 30)
|
||||
*/
|
||||
#[Field(name: "EXTRA", length: 30)]
|
||||
public string $extra;
|
||||
|
||||
/**
|
||||
* @Field('name' => "PRIVILEGES", 'length' => 80)
|
||||
*/
|
||||
#[Field(name: "PRIVILEGES", length: 80)]
|
||||
public string $privileges;
|
||||
|
||||
/**
|
||||
* @Field('name' => "COLUMN_COMMENT", 'length' => 1024)
|
||||
*/
|
||||
#[Field(name: "COLUMN_COMMENT", length: 1024)]
|
||||
public string $comment;
|
||||
|
||||
/**
|
||||
* @Field('name' => "IS_GENERATED", 'length' => 6)
|
||||
*/
|
||||
public string $generated;
|
||||
# #[Field(name: "IS_GENERATED", length: 6)]
|
||||
# public string $generated;
|
||||
|
||||
/**
|
||||
* @Field('name' => "GENERATION_EXPRESSION", 'type' => "longtext")
|
||||
*/
|
||||
#[Field(name: "GENERATION_EXPRESSION", type: "longtext")]
|
||||
public ? string $generationExpression;
|
||||
|
||||
public function matchFieldDefinition(ReflectedProperty $definition) : bool
|
||||
{
|
||||
$nullable = $this->nullable === 'YES';
|
||||
|
||||
if ($nullable !== $definition->allowsNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isset($definition->value) && $this->canHaveDefaultValue() ) {
|
||||
if ( $definition->value !== $this->defaultValue()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
elseif (! isset($definition->value)) {
|
||||
if ( ! $this->defaultValueIsNull() ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -5,131 +5,90 @@ namespace Ulmus\Entity\InformationSchema;
|
|||
use Ulmus\EntityCollection,
|
||||
Ulmus\Entity\Field\Datetime;
|
||||
|
||||
/**
|
||||
* @Table('name' => "tables", 'database' => "information_schema")
|
||||
*/
|
||||
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")]
|
||||
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('oneToMany', 'key' => 'name', 'foreignKey' => Column::field('tableName'), 'entity' => Column::class)
|
||||
* @Where('TABLE_SCHEMA', Column::field('tableSchema'))
|
||||
*/
|
||||
#[Relation(type: "oneToMany", key: "name", foreignKey: [ Column::class, 'tableName' ], entity: Column::class)]
|
||||
#[Where(field: 'TABLE_SCHEMA', generateValue: [ Table::class, 'getSchema' ])]
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Entity\Mysql;
|
||||
|
||||
class Column extends \Ulmus\Entity\InformationSchema\Column
|
||||
{
|
||||
# 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'
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Entity\Mysql;
|
||||
|
||||
use Ulmus\EntityCollection,
|
||||
Ulmus\Entity\Field\Datetime;
|
||||
|
||||
use Ulmus\{Attribute\Obj\Table as TableObj};
|
||||
use Ulmus\Attribute\Property\{Field, Filter, FilterJoin, Relation, Join, Virtual, Where};
|
||||
|
||||
#[TableObj(name: "tables", database: "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' ])]
|
||||
public EntityCollection $columns;
|
||||
}
|
|
@ -2,47 +2,60 @@
|
|||
|
||||
namespace Ulmus\Entity\Sqlite;
|
||||
|
||||
use Notes\Common\ReflectedProperty;
|
||||
use Ulmus\EntityCollection;
|
||||
|
||||
/**
|
||||
* @Table
|
||||
*/
|
||||
use Ulmus\{Attribute\Obj\Table};
|
||||
use Ulmus\Attribute\Property\{Field, Filter, FilterJoin, Relation, Join, Virtual, Where};
|
||||
|
||||
#[Table]
|
||||
class Column
|
||||
{
|
||||
use \Ulmus\EntityTrait;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
*/
|
||||
#[Field\Id]
|
||||
public int $cid;
|
||||
|
||||
/**
|
||||
* @Field
|
||||
*/
|
||||
#[Field]
|
||||
public string $type;
|
||||
|
||||
/**
|
||||
* @Field
|
||||
*/
|
||||
#[Field]
|
||||
public string $name;
|
||||
|
||||
/**
|
||||
* @Virtual
|
||||
*/
|
||||
#[Virtual]
|
||||
public string $tableName;
|
||||
|
||||
/**
|
||||
* @Field('name' => "notnull")
|
||||
*/
|
||||
#[Field(name: "notnull")]
|
||||
public bool $notNull;
|
||||
|
||||
/**
|
||||
* @Field('name' => 'dflt_value')
|
||||
*/
|
||||
#[Field(name: "dflt_value")]
|
||||
public ? string $defaultValue;
|
||||
|
||||
/**
|
||||
* @Field('name' => "pk")
|
||||
*/
|
||||
#[Field(name: "pk")]
|
||||
public bool $primaryKey;
|
||||
|
||||
public function matchFieldDefinition(ReflectedProperty $definition) : bool
|
||||
{
|
||||
if ($this->notNull === $definition->allowsNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($definition->value)) {
|
||||
if ( $definition->value !== $this->defaultValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
elseif (! isset($definition->value)) {
|
||||
if ( ! $this->defaultValueIsNull() ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function defaultValueIsNull() : bool
|
||||
{
|
||||
return $this->defaultValue === null || $this->defaultValue === "NULL";
|
||||
}
|
||||
}
|
|
@ -3,41 +3,38 @@
|
|||
namespace Ulmus\Entity\Sqlite;
|
||||
|
||||
use Ulmus\EntityCollection;
|
||||
use Ulmus\Query\{From, Select};
|
||||
use Ulmus\{Attribute\Obj\Table, Repository, Ulmus};
|
||||
use Ulmus\Attribute\Property\{Field, Filter, FilterJoin, Relation, Join, Virtual, Where};
|
||||
|
||||
/**
|
||||
* @Table('name' => "sqlite_master")
|
||||
*/
|
||||
#[Table(name: "sqlite_master")]
|
||||
class Schema
|
||||
{
|
||||
use \Ulmus\EntityTrait;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
*/
|
||||
#[Field\Id]
|
||||
public ? string $name;
|
||||
|
||||
/**
|
||||
* @Field
|
||||
*/
|
||||
#[Field]
|
||||
public ? string $type;
|
||||
|
||||
/**
|
||||
* @Field('name' => 'tbl_name')
|
||||
*/
|
||||
#[Field(name: "tbl_name")]
|
||||
public ? string $tableName;
|
||||
|
||||
/**
|
||||
* @Field
|
||||
*/
|
||||
#[Field]
|
||||
public ? int $rootpage;
|
||||
|
||||
/**
|
||||
* @Field
|
||||
*/
|
||||
#[Field]
|
||||
public ? string $sql;
|
||||
|
||||
/**
|
||||
* @Relation('oneToMany', 'key' => 'tableName', 'foreignKey' => 'tableName', 'entity' => Schema::class)
|
||||
*/
|
||||
#[Virtual(method: "filterColumns")]
|
||||
public EntityCollection $columns;
|
||||
|
||||
public function filterColumns() : EntityCollection
|
||||
{
|
||||
$adapter = Ulmus::$registeredAdapters[$this->loadedFromAdapter];
|
||||
|
||||
return Column::repository(Repository\SqliteRepository::DEFAULT_ALIAS, $adapter)
|
||||
->pragma('table_info', $this->tableName)->collectionFromQuery();
|
||||
}
|
||||
}
|
|
@ -3,18 +3,24 @@
|
|||
namespace Ulmus\Entity\Sqlite;
|
||||
|
||||
use Ulmus\ConnectionAdapter;
|
||||
use Ulmus\EntityCollection;
|
||||
use Ulmus\Repository;
|
||||
|
||||
#[\Ulmus\Attribute\Obj\Table(name: "sqlite_master")]
|
||||
class Table extends Schema
|
||||
{
|
||||
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null): Repository
|
||||
{
|
||||
return new class(static::class, $alias, $adapter) extends Repository\SqliteRepository
|
||||
$repository = new class(static::class, $alias, $adapter) extends Repository\SqliteRepository
|
||||
{
|
||||
public function finalizeQuery(): void
|
||||
{
|
||||
$this->select(Table::field('tableName'))->groupBy(Table::field('tableName'));
|
||||
}
|
||||
};
|
||||
|
||||
$repository->entityClass = static::class;
|
||||
|
||||
return $repository;
|
||||
}
|
||||
}
|
|
@ -4,10 +4,18 @@ namespace Ulmus;
|
|||
|
||||
use Generator;
|
||||
|
||||
class EntityCollection extends \ArrayObject {
|
||||
class EntityCollection extends \ArrayObject implements \JsonSerializable {
|
||||
|
||||
public ? string $entityClass = null;
|
||||
|
||||
public static function instance(array $data = [], null|string $entityClass = null) : self
|
||||
{
|
||||
$instance = new static($data);
|
||||
$instance->entityClass = $entityClass;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public function filters(Callable $callback, bool $yieldValueOnly = false) : Generator
|
||||
{
|
||||
$idx = 0;
|
||||
|
@ -49,6 +57,11 @@ class EntityCollection extends \ArrayObject {
|
|||
return $this->filtersCollection($callback, true, false)->toArray();
|
||||
}
|
||||
|
||||
public function filtersCount(Callable $callback) : int
|
||||
{
|
||||
return $this->filtersCollection($callback, true, false)->count();
|
||||
}
|
||||
|
||||
public function filtersOne(Callable $callback) : ? object
|
||||
{
|
||||
foreach($this->filters($callback, true) as $item) {
|
||||
|
@ -58,6 +71,26 @@ class EntityCollection extends \ArrayObject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function extract(Callable $callback) : self
|
||||
{
|
||||
$idx = 0;
|
||||
$replace = [];
|
||||
$collection = new static();
|
||||
|
||||
foreach($this as $key => $item) {
|
||||
if ( $callback($item, $key, $idx++) ) {
|
||||
$collection->append($item);
|
||||
}
|
||||
else {
|
||||
$replace[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$this->replaceWith($replace);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function iterate(Callable $callback) : self
|
||||
{
|
||||
foreach($this as $item) {
|
||||
|
@ -98,14 +131,14 @@ class EntityCollection extends \ArrayObject {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function search($value, string $field, bool $strict = true) : Generator
|
||||
public function search(mixed $value, string $field, bool $strict = true) : Generator
|
||||
{
|
||||
foreach($this->filters(fn($v) => isset($v->$field) ? ( $strict ? $v->$field === $value : $v->$field == $value ) : false) as $key => $item) {
|
||||
yield $key => $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function searchOne($value, string $field, bool $strict = true) : ? object
|
||||
public function searchOne(mixed $value, string $field, bool $strict = true) : ? object
|
||||
{
|
||||
# Returning first value only
|
||||
foreach($this->search($value, $field, $strict) as $item) {
|
||||
|
@ -115,22 +148,22 @@ class EntityCollection extends \ArrayObject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function searchAll(/* mixed*/ $values, string $field, bool $strict = true, bool $compareArray = false) : self
|
||||
public function searchAll(mixed $values, string $field, bool $strict = true, bool $compareArray = false) : self
|
||||
{
|
||||
$obj = new static();
|
||||
$collection = new static();
|
||||
|
||||
$values = is_array($values) && $compareArray ? [ $values ] : $values;
|
||||
|
||||
foreach((array) $values as $value) {
|
||||
foreach ($this->search($value, $field, $strict) as $item) {
|
||||
$obj->append($item);
|
||||
$collection->append($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function diffAll(/* mixed */ $values, string $field, bool $strict = true, bool $compareArray = false) : self
|
||||
public function diffAll(mixed $values, string $field, bool $strict = true, bool $compareArray = false) : self
|
||||
{
|
||||
$obj = new static($this->getArrayCopy());
|
||||
|
||||
|
@ -189,7 +222,18 @@ class EntityCollection extends \ArrayObject {
|
|||
return $list;
|
||||
}
|
||||
|
||||
public function unique(/*stringable|callable */ $field, bool $strict = false) : self
|
||||
|
||||
public function walk(Callable $callback) : self
|
||||
{
|
||||
return $this->filtersCollection($callback);
|
||||
}
|
||||
|
||||
public function sum($field) : float|int
|
||||
{
|
||||
return array_sum($this->column($field));
|
||||
}
|
||||
|
||||
public function unique(\Stringable|callable|string $field, bool $strict = false) : self
|
||||
{
|
||||
$list = [];
|
||||
$obj = new static();
|
||||
|
@ -282,6 +326,26 @@ class EntityCollection extends \ArrayObject {
|
|||
return $list;
|
||||
}
|
||||
|
||||
public function toFilteredArray(Callable $callback) : array {
|
||||
$list = [];
|
||||
|
||||
foreach($this as $entity) {
|
||||
$list[] = $callback($entity);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function toJsonArray(bool $includeRelations = false) : array {
|
||||
$list = [];
|
||||
|
||||
foreach($this as $entity) {
|
||||
$list[] = $entity instanceof \JsonSerializable ? $entity->jsonSerialize() : $entity->toArray($includeRelations);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function fromArray(array $datasets, ? string /*stringable*/ $entityClass = null) : self
|
||||
{
|
||||
foreach($datasets as $dataset) {
|
||||
|
@ -302,6 +366,23 @@ class EntityCollection extends \ArrayObject {
|
|||
return ( new $className() )->fromArray($dataset);
|
||||
}
|
||||
|
||||
public function arrayToEntities(array $list, ? string /*stringable*/ $entityClass = null) : self
|
||||
{
|
||||
$collection = new static();
|
||||
$collection->entityClass = $entityClass ?? $this->entityClass;
|
||||
|
||||
foreach($list as $dataset) {
|
||||
$collection->append($this->arrayToEntity($dataset, $entityClass));
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return $this->toJsonArray(true);
|
||||
}
|
||||
|
||||
public function append($value) : void
|
||||
{
|
||||
if ( is_array($value) ) {
|
||||
|
@ -370,14 +451,16 @@ class EntityCollection extends \ArrayObject {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function sort(callable $callback, $function = "uasort") : self
|
||||
public function sort(null|callable $callback = null, $function = "uasort") : self
|
||||
{
|
||||
$callback ??= fn($e1, $e2) => $e1 <=> $e2;
|
||||
|
||||
call_user_func_array([ $this, $function ], [ $callback ]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function rsort(callable $callback, $function = "uasort") : self
|
||||
public function rsort(null|callable $callback = null, $function = "uasort") : self
|
||||
{
|
||||
return $this->sort(...func_get_args())->reverse();
|
||||
}
|
||||
|
|
|
@ -2,135 +2,82 @@
|
|||
|
||||
namespace Ulmus;
|
||||
|
||||
use Ulmus\{ Repository, Query, Common\EntityResolver, Common\EntityField };
|
||||
use Ulmus\Annotation\Classes\{ Method, Table, Collation, };
|
||||
use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Relation, OrderBy, Where, OrWhere, Join, Virtual, On, WithJoin, };
|
||||
use Ulmus\Annotation\Property\Field\{ PrimaryKey, Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Text, Mediumtext, Longtext, Blob, Mediumblob, Longblob, };
|
||||
use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore };
|
||||
use Notes\Attribute\Ignore;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\{Attribute\Property\Join,
|
||||
Attribute\Property\Relation,
|
||||
Attribute\Property\ResettablePropertyInterface,
|
||||
Attribute\Property\Virtual,
|
||||
Common\EntityResolver,
|
||||
Common\EntityField,
|
||||
Entity\DatasetHandler,
|
||||
Entity\EntityInterface,
|
||||
QueryBuilder\QueryBuilderInterface};
|
||||
use Ulmus\SearchRequest\{Attribute\SearchParameter,
|
||||
SearchMethodEnum,
|
||||
SearchRequestInterface,
|
||||
SearchRequestFromRequestTrait,
|
||||
SearchRequestPaginationTrait};
|
||||
|
||||
trait EntityTrait {
|
||||
use EventTrait;
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
protected bool $entityStrictFieldsDeclaration = false;
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
protected array $entityDatasetUnmatchedFields = [];
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
#[Ignore]
|
||||
public array $entityLoadedDataset = [];
|
||||
|
||||
public function __construct() {
|
||||
#[Ignore]
|
||||
protected bool $entityStrictFieldsDeclaration = false;
|
||||
|
||||
#[Ignore]
|
||||
protected array $entityDatasetUnmatchedFields = [];
|
||||
|
||||
#[Ignore]
|
||||
protected DatasetHandler $datasetHandler;
|
||||
|
||||
#[Ignore]
|
||||
public function __construct(iterable|null $dataset = null)
|
||||
{
|
||||
$this->initializeEntity($dataset);
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function initializeEntity(iterable|null $dataset = null) : void
|
||||
{
|
||||
if ($dataset) {
|
||||
$this->fromArray($dataset);
|
||||
}
|
||||
|
||||
$this->datasetHandler = new DatasetHandler(static::resolveEntity(), $this->entityStrictFieldsDeclaration);
|
||||
|
||||
$this->resetVirtualProperties();
|
||||
}
|
||||
|
||||
/**entityLoadedDataset
|
||||
* @Ignore
|
||||
*/
|
||||
#[Ignore]
|
||||
public function entityFillFromDataset(iterable $dataset, bool $overwriteDataset = false) : self
|
||||
{
|
||||
$loaded = $this->isLoaded();
|
||||
|
||||
$entityResolver = $this->resolveEntity();
|
||||
$handler = $this->datasetHandler->push($dataset);
|
||||
|
||||
foreach($dataset as $key => $value) {
|
||||
|
||||
$field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null;
|
||||
$field ??= $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 {
|
||||
$this->entityDatasetUnmatchedFields[$key] = $value;
|
||||
}
|
||||
}
|
||||
elseif ( is_null($value) ) {
|
||||
$this->{$field['name']} = null;
|
||||
}
|
||||
elseif ( $field['type'] === 'array' ) {
|
||||
if ( is_string($value)) {
|
||||
$this->{$field['name']} = substr($value, 0, 1) === "a" ? unserialize($value) : json_decode($value, true);
|
||||
}
|
||||
elseif ( is_array($value) ) {
|
||||
$this->{$field['name']} = $value;
|
||||
}
|
||||
}
|
||||
elseif ( EntityField::isScalarType($field['type']) ) {
|
||||
|
||||
if ( $field['type'] === 'string' ) {
|
||||
$annotation = $entityResolver->searchFieldAnnotation($field['name'], [ Attribute\Property\Field::class, Field::class ] );
|
||||
|
||||
if ( $annotation->length ?? null ) {
|
||||
$value = mb_substr($value, 0, $annotation->length);
|
||||
}
|
||||
}
|
||||
elseif ( $field['type'] === 'bool' ) {
|
||||
$value = (bool) $value;
|
||||
foreach($handler as $field => $value) {
|
||||
$this->$field = $value;
|
||||
}
|
||||
|
||||
$this->{$field['name']} = $value;
|
||||
}
|
||||
elseif ( $value instanceof \UnitEnum ) {
|
||||
$this->{$field['name']} = $value;
|
||||
}
|
||||
elseif (enum_exists($field['type'])) {
|
||||
$this->{$field['name']} = $field['type']::from($value);
|
||||
}
|
||||
elseif ( ! $field['builtin'] ) {
|
||||
try {
|
||||
$this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]);
|
||||
}
|
||||
catch(\Error $e) {
|
||||
$f = $field['type'];
|
||||
throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field['name']));
|
||||
}
|
||||
}
|
||||
$this->entityDatasetUnmatchedFields = $handler->getReturn();
|
||||
|
||||
# Keeping original data to diff on UPDATE query
|
||||
if ( ! $loaded /* || $isLoadedDataset */ ) {
|
||||
#if ( $field !== null ) {
|
||||
# $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
|
||||
# $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!!
|
||||
#}
|
||||
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER);
|
||||
if ( ! $loaded ) {
|
||||
$this->entityLoadedDataset = array_change_key_case(is_array($dataset) ? $dataset : iterator_to_array($dataset), \CASE_LOWER);
|
||||
}
|
||||
elseif ($overwriteDataset) {
|
||||
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset;
|
||||
}
|
||||
$this->entityLoadedDataset = array_change_key_case(is_array($dataset) ? $dataset : iterator_to_array($dataset), \CASE_LOWER) + $this->entityLoadedDataset;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function resetVirtualProperties() : self
|
||||
{
|
||||
foreach($this->resolveEntity()->properties as $prop => $property) {
|
||||
if ( empty($property['builtin']) ) {
|
||||
foreach($property['tags'] as $tag) {
|
||||
if ( in_array(strtolower($tag['tag']), [ 'relation', 'join', 'virtual' ] ) ) {
|
||||
unset($this->$prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fromArray(iterable $dataset) : self
|
||||
{
|
||||
return $this->entityFillFromDataset($dataset);
|
||||
}
|
||||
|
||||
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array
|
||||
#[Ignore]
|
||||
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false, bool $rewriteValue = true) : array
|
||||
{
|
||||
if ( $returnSource ) {
|
||||
return $this->entityLoadedDataset;
|
||||
|
@ -138,58 +85,59 @@ trait EntityTrait {
|
|||
|
||||
$dataset = [];
|
||||
|
||||
$entityResolver = $this->resolveEntity();
|
||||
|
||||
foreach($entityResolver->fieldList(Common\EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) {
|
||||
$annotation = $entityResolver->searchFieldAnnotation($key, [ Attribute\Property\Field::class, Field::class ]);
|
||||
|
||||
if ( isset($this->$key) ) {
|
||||
$dataset[$annotation->name ?? $key] = static::repository()->adapter->adapter()->writableValue($this->$key);
|
||||
}
|
||||
elseif ( $field['nullable'] ) {
|
||||
$dataset[$annotation->name ?? $key] = null;
|
||||
}
|
||||
foreach($this->datasetHandler->pull($this) as $field => $value) {
|
||||
$dataset[$field] = $rewriteValue ? static::repository()->adapter->adapter()->writableValue($value) : $value;
|
||||
}
|
||||
|
||||
# @TODO Must fix recursive bug !
|
||||
if ($includeRelations) {
|
||||
foreach($entityResolver->properties as $name => $field){
|
||||
$relation = $entityResolver->searchFieldAnnotation($key, [ Attribute\Property\Relation::class. Relation::class ] );
|
||||
|
||||
if ( $relation && isset($this->$name) && ($relation->entity ?? $relation->bridge) !== static::class ) {
|
||||
if ( null !== $value = $this->$name ?? null ) {
|
||||
if ( is_iterable($value) ) {
|
||||
$list = [];
|
||||
|
||||
foreach($value as $entity) {
|
||||
$list[] = $entity->entityGetDataset(false);
|
||||
}
|
||||
|
||||
$dataset[$name] = $list;
|
||||
}
|
||||
elseif ( is_object($value) ) {
|
||||
$dataset[$name] = $value->entityGetDataset(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach($this->datasetHandler->pullRelation($this) as $field => $object) {
|
||||
$dataset[$field] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $dataset;
|
||||
}
|
||||
|
||||
public function toArray($includeRelations = false, array $filterFields = null) : array
|
||||
#[Ignore]
|
||||
public function resetVirtualProperties() : self
|
||||
{
|
||||
$dataset = $this->entityGetDataset($includeRelations);
|
||||
foreach($this->resolveEntity()->reflectedClass->getProperties(true) as $field => $property) {
|
||||
foreach($property->attributes as $tag) {
|
||||
|
||||
if ( $tag->object instanceof ResettablePropertyInterface ) {
|
||||
unset($this->$field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function fromArray(iterable|EntityInterface $dataset) : static
|
||||
{
|
||||
if ($dataset instanceof EntityInterface) {
|
||||
$dataset = $dataset->toArray();
|
||||
}
|
||||
|
||||
return $this->entityFillFromDataset($dataset);
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function toArray($includeRelations = false, array $filterFields = null, bool $rewriteValue = true) : array
|
||||
{
|
||||
$dataset = $this->entityGetDataset($includeRelations, false, $rewriteValue);
|
||||
|
||||
return $filterFields ? array_intersect_key($dataset, array_flip($filterFields)) : $dataset;
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function toCollection() : EntityCollection
|
||||
{
|
||||
return static::entityCollection([ $this ]);
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function isLoaded() : bool
|
||||
{
|
||||
if (empty($this->entityLoadedDataset)) {
|
||||
|
@ -205,6 +153,7 @@ trait EntityTrait {
|
|||
return isset($this->$key);
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function __get(string $name)
|
||||
{
|
||||
$relation = new Repository\RelationBuilder($this);
|
||||
|
@ -216,12 +165,10 @@ trait EntityTrait {
|
|||
throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name));
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function __isset(string $name) : bool
|
||||
{
|
||||
#if ( null !== $relation = static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
|
||||
# return isset($this->{$relation->key});
|
||||
#}
|
||||
$rel = static::resolveEntity()->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ]);
|
||||
$rel = static::resolveEntity()->searchFieldAnnotation($name, [ Attribute\Property\Relation::class ]);
|
||||
|
||||
if ( $this->isLoaded() && $rel ) {
|
||||
return true;
|
||||
|
@ -230,22 +177,21 @@ trait EntityTrait {
|
|||
return isset($this->$name);
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function __sleep()
|
||||
{
|
||||
return array_merge(array_keys($this->resolveEntity()->fieldList()), [ 'entityLoadedDataset' ] );
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->resetVirtualProperties();
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function __clone()
|
||||
{
|
||||
foreach($this as $prop) {
|
||||
|
||||
}
|
||||
|
||||
if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) {
|
||||
$key = key($pkField);
|
||||
|
||||
|
@ -253,21 +199,25 @@ trait EntityTrait {
|
|||
}
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public function jsonSerialize() : mixed
|
||||
{
|
||||
return $this->entityGetDataset();
|
||||
return $this->entityGetDataset(true, false, false);
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public static function resolveEntity() : EntityResolver
|
||||
{
|
||||
return Ulmus::resolveEntity(static::class);
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository
|
||||
{
|
||||
return Ulmus::repository(static::class, $alias, $adapter);
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public static function entityCollection(...$arguments) : EntityCollection
|
||||
{
|
||||
$collection = new EntityCollection(...$arguments);
|
||||
|
@ -276,20 +226,44 @@ trait EntityTrait {
|
|||
return $collection;
|
||||
}
|
||||
|
||||
public static function queryBuilder() : QueryBuilder
|
||||
#[Ignore]
|
||||
public static function queryBuilder() : QueryBuilderInterface
|
||||
{
|
||||
return Ulmus::queryBuilder(static::class);
|
||||
}
|
||||
|
||||
public static function field($name, ? string $alias = Repository::DEFAULT_ALIAS) : EntityField
|
||||
#[Ignore]
|
||||
public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField
|
||||
{
|
||||
return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class));
|
||||
$default = ( $alias === false ? '' : static::repository()::DEFAULT_ALIAS ); # bw compatibility, to be deprecated
|
||||
|
||||
$alias = $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : $default;
|
||||
|
||||
return new EntityField(static::class, $name, $alias, Ulmus::resolveEntity(static::class));
|
||||
}
|
||||
|
||||
public static function fields(array $fields, ? string $alias = Repository::DEFAULT_ALIAS) : string
|
||||
#[Ignore]
|
||||
public static function fields(array $fields, null|string|false $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : string
|
||||
{
|
||||
return implode(', ', array_map(function($item) use ($alias){
|
||||
return implode($separator, array_map(function($item) use ($alias){
|
||||
return static::field($item, $alias);
|
||||
}, $fields));
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public static function searchRequest(...$arguments) : SearchRequest\SearchRequestInterface
|
||||
{
|
||||
return new /* #[SearchRequest\Attribute\SearchRequestParameter(YourEntityClass::class)] */ class(... $arguments) extends SearchRequest\SearchRequest {
|
||||
# Define searchable properties here, some ex:
|
||||
|
||||
# #[SearchParameter(method: SearchMethodEnum::Where)]
|
||||
# public ? string $username = null;
|
||||
|
||||
# #[SearchParameter(method: SearchMethodEnum::Where, toggle: true)]
|
||||
# public ? bool $hidden = null;
|
||||
|
||||
# #[SearchParameter(method: SearchMethodEnum::Like)]
|
||||
# public ? string $word = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue