Compare commits
19
Commits
2fda7e82d7
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fd6889c6f | ||
|
|
bd0d928f35 | ||
|
|
5a5d326d70 | ||
|
|
b5e96eb7e0 | ||
|
|
7c589ab7b4 | ||
|
|
589524ca3a | ||
|
|
1fac12c928 | ||
|
|
9eed2fe1a8 | ||
|
|
b601939459 | ||
|
|
e3314f40ac | ||
|
|
4ad44f73d7 | ||
|
|
68a6636c06 | ||
|
|
70c6cde603 | ||
|
|
079a3fd110 | ||
|
|
ec4d02b9d9 | ||
|
|
35ceb97241 | ||
|
|
83f673b37f | ||
|
|
411992c7a8 | ||
|
|
5f4f23a8e4 |
No files matched your search
@@ -39,7 +39,7 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
public string $database;
|
||||
|
||||
public string $username;
|
||||
|
||||
|
||||
public string $password;
|
||||
|
||||
public string $traceFile;
|
||||
@@ -51,7 +51,9 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
public function __construct(
|
||||
?string $server = null,
|
||||
?string $database = null,
|
||||
#[\SensitiveParameter]
|
||||
?string $username = null,
|
||||
#[\SensitiveParameter]
|
||||
?string $password = null,
|
||||
?int $port = null
|
||||
) {
|
||||
@@ -207,6 +209,7 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
public static function escapeIdentifier(string $segment, int $type) : string
|
||||
{
|
||||
switch($type) {
|
||||
default:
|
||||
case static::IDENTIFIER_SCHEMA:
|
||||
case static::IDENTIFIER_DATABASE:
|
||||
case static::IDENTIFIER_TABLE:
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
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\Repository;
|
||||
|
||||
class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
@@ -35,7 +38,9 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
public function __construct(
|
||||
?string $hostname = null,
|
||||
?string $database = null,
|
||||
#[\SensitiveParameter]
|
||||
?string $username = null,
|
||||
#[\SensitiveParameter]
|
||||
?string $password = null,
|
||||
?int $port = null,
|
||||
?string $charset = null
|
||||
@@ -147,6 +152,7 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
public static function escapeIdentifier(string $segment, int $type) : string
|
||||
{
|
||||
switch($type) {
|
||||
default:
|
||||
case static::IDENTIFIER_DATABASE:
|
||||
case static::IDENTIFIER_TABLE:
|
||||
case static::IDENTIFIER_FIELD:
|
||||
@@ -166,4 +172,12 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -28,4 +28,14 @@ class MySQLFieldMapper extends SqlFieldMapper
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
-5
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Collator;
|
||||
use Ulmus\Common\PdoObject;
|
||||
use Ulmus\ConnectionAdapter;
|
||||
|
||||
use Ulmus\Entity;
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder};
|
||||
@@ -13,7 +13,7 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
||||
use SqlAdapterTrait;
|
||||
|
||||
const ALLOWED_ATTRIBUTES = [
|
||||
'default', 'primary_key', 'auto_increment'
|
||||
'default', 'primary_key', 'auto_increment', 'collate nocase', 'collate binary', 'collate rtrim',
|
||||
];
|
||||
|
||||
const DSN_PREFIX = "sqlite";
|
||||
@@ -37,6 +37,7 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
||||
};
|
||||
|
||||
$this->exportFunctions($pdo);
|
||||
$this->exportCollations($pdo);
|
||||
$this->registerPragma($pdo, $this->pragmaBegin);
|
||||
}
|
||||
catch(\PDOException $ex){
|
||||
@@ -97,7 +98,6 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
||||
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) ) {
|
||||
@@ -115,6 +115,10 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
||||
break;
|
||||
|
||||
case "array":
|
||||
$type = "JSON";
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case "string":
|
||||
$type = "TEXT";
|
||||
$length = null;
|
||||
@@ -130,6 +134,10 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($type, [ 'JSON', 'TEXT', 'BLOB', 'GEOMETRY' ])) {
|
||||
unset($field->default);
|
||||
}
|
||||
|
||||
return $typeOnly ? $type : $type . ( $length ? "($length" . ")" : "" );
|
||||
}
|
||||
|
||||
@@ -184,11 +192,42 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
||||
|
||||
return (int) in_array($string, explode(',', $string_list));
|
||||
}, 2);
|
||||
$pdo->sqliteCreateFunction('day', fn($date) => ( new \DateTime($date) )->format('d'), 1);
|
||||
$pdo->sqliteCreateFunction('month', fn($date) => ( new \DateTime($date) )->format('m'), 1);
|
||||
$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();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\Entity;
|
||||
|
||||
class SqlFieldMapper
|
||||
{
|
||||
@@ -42,13 +43,17 @@ class SqlFieldMapper
|
||||
switch($type) {
|
||||
case "bool":
|
||||
$this->type = "TINYINT";
|
||||
$length = 1;
|
||||
$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 ) ) {
|
||||
@@ -71,17 +76,24 @@ class SqlFieldMapper
|
||||
|
||||
case "float":
|
||||
$this->type = "DOUBLE";
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->type = strtoupper($type);
|
||||
if ($length) {
|
||||
$this->length = $length;
|
||||
}
|
||||
|
||||
break;
|
||||
$this->type ??= strtoupper($type);
|
||||
}
|
||||
|
||||
$this->postProcess();
|
||||
}
|
||||
|
||||
public function render() : string
|
||||
{
|
||||
return $this->type . ( isset($this->length) ? "($this->length)" : "" );
|
||||
}
|
||||
|
||||
public function postProcess() : void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,7 @@ namespace Ulmus\Attribute\Obj;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Collation {
|
||||
|
||||
public function __construct(
|
||||
public string $name = ""
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Attribute\Property;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class Collation {
|
||||
public function __construct(
|
||||
public string $name = ""
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Field {
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public null|int $decimal = null,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Bigint extends \Ulmus\Attribute\Property\Field
|
||||
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 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 = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Bit extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Blob extends \Ulmus\Attribute\Property\Field
|
||||
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 = "",
|
||||
) {}
|
||||
}
|
||||
@@ -13,6 +13,5 @@ class CreatedAt extends \Ulmus\Attribute\Property\Field {
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -11,6 +11,5 @@ class Date extends \Ulmus\Attribute\Property\Field {
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -11,6 +11,5 @@ class Datetime extends \Ulmus\Attribute\Property\Field {
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Decimal extends \Ulmus\Attribute\Property\Field
|
||||
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 = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Float extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ class ForeignKey extends PrimaryKey {
|
||||
public null|string $relation = null,
|
||||
public ConstrainActionEnum $onDelete = ConstrainActionEnum::NoAction,
|
||||
public ConstrainActionEnum $onUpdate = ConstrainActionEnum::NoAction,
|
||||
public string $description = "",
|
||||
) {
|
||||
#$this->references = Attribute::handleArrayField($this->references, false);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ class Id extends \Ulmus\Attribute\Property\Field {
|
||||
],
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
public bool $readonly = true,
|
||||
) {}
|
||||
}
|
||||
@@ -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 = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Longblob extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Longtext extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Mediumblob extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Mediumint extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Mediumtext extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Numeric extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -16,6 +16,5 @@ class PrimaryKey extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Smallint extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Text extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -12,6 +12,5 @@ class Time extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -12,6 +12,5 @@ class Timestamp extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Blob extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Tinyint extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -14,6 +14,5 @@ class Tinytext extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = false,
|
||||
public mixed $default = null,
|
||||
public bool $readonly = false,
|
||||
public string $description = "",
|
||||
) {}
|
||||
}
|
||||
@@ -15,6 +15,5 @@ class UpdatedAt extends \Ulmus\Attribute\Property\Field
|
||||
public bool $nullable = true,
|
||||
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 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 = "",
|
||||
) {}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class OrWhere extends Where {
|
||||
public string $operator = Query\Where::OPERATOR_EQUAL,
|
||||
public string $condition = Query\Where::CONDITION_OR,
|
||||
public string|\Stringable|array|null $fieldValue = null,
|
||||
public null|array $generateValue = null,
|
||||
public null|array|\Closure $generateValue = null,
|
||||
) {
|
||||
parent::__construct($field, $value, $operator, $condition, $fieldValue, $generateValue);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class Where {
|
||||
public string $operator = Query\Where::OPERATOR_EQUAL,
|
||||
public string $condition = Query\Where::CONDITION_AND,
|
||||
public string|\Stringable|array|null $fieldValue = null,
|
||||
public null|array $generateValue = null,
|
||||
public null|array|\Closure $generateValue = null,
|
||||
) {
|
||||
$this->field = Attribute::handleArrayField($field);
|
||||
$this->fieldValue = Attribute::handleArrayField($fieldValue);
|
||||
@@ -29,6 +29,9 @@ class Where {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,6 @@ class EntityResolver {
|
||||
$key = strtolower($item->name);
|
||||
break;
|
||||
|
||||
|
||||
case static::KEY_ENTITY_NAME:
|
||||
$key = $item->name;
|
||||
break;
|
||||
|
||||
@@ -49,7 +49,12 @@ class PdoObject extends PDO {
|
||||
if (false !== ( $statement = $this->prepare($sql) )) {
|
||||
return $this->execute($statement, $parameters, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(\PDOException $pdo) {
|
||||
if ( substr($pdo->getMessage(), 0, 30) !== 'There is no active transaction' ) {
|
||||
throw $pdo;
|
||||
}
|
||||
}
|
||||
catch (\Throwable $e) {
|
||||
throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters));
|
||||
}
|
||||
|
||||
+7
-3
@@ -62,7 +62,7 @@ abstract class Sql {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static function raw(string $sql) : object
|
||||
{
|
||||
return static::identifier($sql);
|
||||
@@ -84,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
namespace Ulmus\Entity;
|
||||
|
||||
use Ulmus\Common\{ EntityField, EntityResolver };
|
||||
use Ulmus\{ ConnectionAdapter, EntityCollection, QueryBuilder\QueryBuilderInterface, Repository };
|
||||
use Ulmus\{ConnectionAdapter,
|
||||
EntityCollection,
|
||||
QueryBuilder\QueryBuilderInterface,
|
||||
Repository,
|
||||
SearchRequest\SearchableInterface};
|
||||
|
||||
interface EntityInterface /* extends \JsonSerializable */
|
||||
interface EntityInterface extends SearchableInterface /* extends \JsonSerializable */
|
||||
{
|
||||
public function fromArray(iterable $dataset) : static;
|
||||
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array;
|
||||
|
||||
@@ -84,11 +84,13 @@ class Column
|
||||
|
||||
public function matchFieldDefinition(ReflectedProperty $definition) : bool
|
||||
{
|
||||
if ($this->nullable === $definition->allowsNull()) {
|
||||
$nullable = $this->nullable === 'YES';
|
||||
|
||||
if ($nullable !== $definition->allowsNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($definition->value)) {
|
||||
if ( isset($definition->value) && $this->canHaveDefaultValue() ) {
|
||||
if ( $definition->value !== $this->defaultValue()) {
|
||||
return false;
|
||||
}
|
||||
@@ -115,4 +117,9 @@ class Column
|
||||
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
protected function canHaveDefaultValue() : bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace Ulmus\Entity\InformationSchema;
|
||||
use Ulmus\EntityCollection,
|
||||
Ulmus\Entity\Field\Datetime;
|
||||
|
||||
use Ulmus\{Attribute\Obj\Table as TableObj};
|
||||
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")]
|
||||
@@ -83,6 +83,12 @@ class Table
|
||||
public ? string $temporary;
|
||||
|
||||
#[Relation(type: "oneToMany", key: "name", foreignKey: [ Column::class, 'tableName' ], entity: Column::class)]
|
||||
#[Where('TABLE_SCHEMA', fieldValue: [ Column::class, 'tableSchema' ])]
|
||||
#[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
-2
@@ -235,7 +235,7 @@ trait EntityTrait {
|
||||
#[Ignore]
|
||||
public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField
|
||||
{
|
||||
$default = ( $alias === false ? '' : Repository::DEFAULT_ALIAS ); # bw compatibility, to be deprecated
|
||||
$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;
|
||||
|
||||
@@ -251,7 +251,7 @@ trait EntityTrait {
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
public static function searchRequest(...$arguments) : SearchRequestInterface
|
||||
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:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Event\Query;
|
||||
|
||||
use Ulmus\Repository\RepositoryInterface;
|
||||
|
||||
interface Alter {
|
||||
public function execute(RepositoryInterface $repository, array $fields) : void;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Event\Query;
|
||||
|
||||
use Ulmus\Repository\RepositoryInterface;
|
||||
|
||||
interface Create {
|
||||
public function execute(RepositoryInterface $repository) : void;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Event\Query;
|
||||
|
||||
use Ulmus\Entity\EntityInterface;
|
||||
use Ulmus\Repository\RepositoryInterface;
|
||||
|
||||
interface Delete {
|
||||
public function execute(RepositoryInterface $repository, EntityInterface $entity) : void;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Event\Query;
|
||||
|
||||
use Ulmus\Repository\RepositoryInterface;
|
||||
|
||||
interface Insert {
|
||||
public function execute(RepositoryInterface $repository, object|array $entity, ? array $dataset = null, bool $replace = false) : void;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Event\Query;
|
||||
|
||||
use Ulmus\Repository\RepositoryInterface;
|
||||
|
||||
interface Truncate {
|
||||
public function execute(RepositoryInterface $repository, ? string $table = null, ? string $alias = null, ? string $schema = null) : void;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Event\Query;
|
||||
|
||||
use Ulmus\Repository\RepositoryInterface;
|
||||
|
||||
interface Update {
|
||||
public function execute(RepositoryInterface $repository, object|array $entity, ? array $dataset = null, bool $replace = false) : void;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Event\Repository;
|
||||
|
||||
use Ulmus\EntityCollection;
|
||||
use Ulmus\Repository\RepositoryInterface;
|
||||
|
||||
interface CollectionFromQueryDatasetInterface {
|
||||
public function execute(RepositoryInterface $repository, array &$data) : void;
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Ulmus\Event\Repository;
|
||||
|
||||
use Ulmus\EntityCollection;
|
||||
use Ulmus\{ EntityCollection, Repository\RepositoryInterface };
|
||||
|
||||
interface CollectionFromQueryInterface {
|
||||
public function execute(EntityCollection $collection) : EntityCollection;
|
||||
public function execute(RepositoryInterface $repository, EntityCollection $collection) : EntityCollection;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Event\Repository;
|
||||
|
||||
use Ulmus\EntityCollection;
|
||||
|
||||
interface CollectionFromQueryItemInterface {
|
||||
public function execute(array &$data) : array;
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@ trait EventTrait
|
||||
return array_filter($this->eventList, fn($ev) => $ev instanceof $type);
|
||||
}
|
||||
|
||||
public function eventExecute(string $type, ...$arguments) : void
|
||||
public function eventExecute(string $type, &...$arguments) : void
|
||||
{
|
||||
foreach($this->eventFromType($type) as $event) {
|
||||
call_user_func_array([ $event, 'execute'], $arguments);
|
||||
|
||||
@@ -8,6 +8,7 @@ class OrderBy extends Fragment {
|
||||
public array $orderBy = [];
|
||||
|
||||
const SQL_TOKEN = "ORDER BY";
|
||||
const SQL_COLLATE = "COLLATE";
|
||||
|
||||
public function set(array $order) : self
|
||||
{
|
||||
@@ -16,7 +17,7 @@ class OrderBy extends Fragment {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function add(object|string $field, ? string $direction = null) : self
|
||||
public function add(string|\Stringable $field, ? string $direction = null) : self
|
||||
{
|
||||
$this->validateFieldType($field);
|
||||
|
||||
@@ -30,7 +31,7 @@ class OrderBy extends Fragment {
|
||||
$list = array_map(function($item) {
|
||||
list($field, $direction) = $item;
|
||||
|
||||
return $field . ( $direction ? " $direction" : "" );
|
||||
return implode(' ', array_filter([ $field , $direction ?? null ] ));
|
||||
}, $this->orderBy);
|
||||
|
||||
return $this->renderSegments([
|
||||
|
||||
+9
-2
@@ -15,6 +15,7 @@ class Where extends Fragment {
|
||||
const CONDITION_OR = "OR";
|
||||
const CONDITION_NOT = "NOT";
|
||||
const COMPARISON_IN = "IN";
|
||||
const COMPARISON_NOT_IN = "NOT IN";
|
||||
const COMPARISON_IS = "IS";
|
||||
const COMPARISON_NULL = "NULL";
|
||||
|
||||
@@ -100,11 +101,13 @@ class Where extends Fragment {
|
||||
{
|
||||
$value = $this->value();
|
||||
|
||||
$operator = $this->operator();
|
||||
|
||||
return $this->content ?: $this->content = implode(" ", array_filter([
|
||||
$this->condition,
|
||||
$this->not ? Where::CONDITION_NOT : "",
|
||||
$this->field,
|
||||
$this->operator(),
|
||||
$operator,
|
||||
$value,
|
||||
]));
|
||||
}
|
||||
@@ -112,7 +115,11 @@ class Where extends Fragment {
|
||||
protected function operator() : string
|
||||
{
|
||||
if ( is_array($this->value) ) {
|
||||
return Where::COMPARISON_IN;
|
||||
if (true === $not = $this->not) {
|
||||
$this->not = false;
|
||||
}
|
||||
|
||||
return $not ? Where::COMPARISON_NOT_IN : Where::COMPARISON_IN;
|
||||
}
|
||||
|
||||
# whitelisting operators
|
||||
|
||||
@@ -4,5 +4,5 @@ namespace Ulmus;
|
||||
|
||||
class QueryBuilder extends QueryBuilder\Sql\MysqlQueryBuilder
|
||||
{
|
||||
# Backward compatibility defaulting on MySQL/MariaDB query builder
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\QueryBuilder;
|
||||
|
||||
use Ulmus\Common\Sql;
|
||||
use Ulmus\Query;
|
||||
|
||||
trait JsonQueryBuilderTrait
|
||||
{
|
||||
public function whereJsonArray(string|\Stringable $field, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
|
||||
{
|
||||
$uid = uniqid("json_");
|
||||
|
||||
$extract = Sql::function("json_extract", $field, '$');
|
||||
|
||||
$each = Sql::function('json_each', $extract)->as($uid);
|
||||
|
||||
return $this->from( $each )->where(Sql::raw("{$uid}.value"), $value, $operator, $condition, $not);
|
||||
}
|
||||
|
||||
public function whereJsonKey(string|\Stringable $field, string|null $key, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
|
||||
{
|
||||
$uid = uniqid("json_");
|
||||
|
||||
$extract = Sql::function("json_extract", $field, $key ? "$.$key" : "$");
|
||||
|
||||
$each = Sql::function('json_each', $extract)->as($uid);
|
||||
|
||||
return $this->from( $each )->where(Sql::raw("{$uid}.value"), $value, $operator, $condition, $not);
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,5 @@ interface QueryBuilderInterface
|
||||
public function reset() : void;
|
||||
public function getFragment(string $class, int $index = 0) : ? QueryFragmentInterface;
|
||||
public function removeFragment(QueryFragmentInterface|array|\Stringable|string $fragment) : void;
|
||||
public function hashSerializedQuery() : string;
|
||||
}
|
||||
@@ -260,7 +260,7 @@ class MysqlQueryBuilder extends SqlQueryBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy(string|\Stringable $field, ? string $direction = null) : self
|
||||
public function orderBy(string|\Stringable $field, null|string|\Stringable $direction = null) : self
|
||||
{
|
||||
if ( null === $orderBy = $this->getFragment(Query\OrderBy::class) ) {
|
||||
$orderBy = new Query\OrderBy();
|
||||
@@ -272,7 +272,7 @@ class MysqlQueryBuilder extends SqlQueryBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function groupBy(string|object $field) : self
|
||||
public function groupBy(string|\Stringable $field) : self
|
||||
{
|
||||
if ( null === $groupBy = $this->getFragment(Query\GroupBy::class) ) {
|
||||
$groupBy = new Query\GroupBy();
|
||||
@@ -432,8 +432,12 @@ class MysqlQueryBuilder extends SqlQueryBuilder
|
||||
return array_shift($this->queryStack);
|
||||
}
|
||||
|
||||
public function render(bool $skipToken = false) /* : mixed */
|
||||
public function render(bool $skipToken = false) : mixed
|
||||
{
|
||||
if (isset($this->rendered)) {
|
||||
return $this->rendered;
|
||||
}
|
||||
|
||||
$sql = [];
|
||||
|
||||
usort($this->queryStack, function($q1, $q2) {
|
||||
@@ -444,7 +448,7 @@ class MysqlQueryBuilder extends SqlQueryBuilder
|
||||
$sql[] = $fragment->render($skipToken);
|
||||
}
|
||||
|
||||
return implode(" ", $sql);
|
||||
return $this->rendered = implode(" ", $sql);
|
||||
}
|
||||
|
||||
public function reset() : void
|
||||
@@ -453,8 +457,7 @@ class MysqlQueryBuilder extends SqlQueryBuilder
|
||||
$this->whereConditionOperator = Query\Where::CONDITION_AND;
|
||||
$this->havingConditionOperator = Query\Where::CONDITION_AND;
|
||||
$this->parameterIndex = 0;
|
||||
|
||||
unset($this->where, $this->having);
|
||||
unset($this->where, $this->having, $this->rendered, $this->hash);
|
||||
}
|
||||
|
||||
public function getFragment(string $class, int $index = 0) : ? QueryFragmentInterface
|
||||
|
||||
@@ -7,6 +7,9 @@ use Ulmus\Query\QueryFragmentInterface;
|
||||
# TODO -> Extract from MysqlQueryBuilder to build an ISO/IEC 9075:2023 compatible layer for a basic SQL QueryBuilder
|
||||
class SqlQueryBuilder implements QueryBuilderInterface
|
||||
{
|
||||
protected string $rendered;
|
||||
|
||||
protected string $hash;
|
||||
|
||||
public function push(QueryFragmentInterface $queryFragment): QueryBuilderInterface
|
||||
{
|
||||
@@ -18,7 +21,7 @@ class SqlQueryBuilder implements QueryBuilderInterface
|
||||
// TODO: Implement pull() method.
|
||||
}
|
||||
|
||||
public function render(bool $skipToken = false)
|
||||
public function render(bool $skipToken = false) : mixed
|
||||
{
|
||||
// TODO: Implement render() method.
|
||||
}
|
||||
@@ -37,4 +40,9 @@ class SqlQueryBuilder implements QueryBuilderInterface
|
||||
{
|
||||
// TODO: Implement removeFragment() method.
|
||||
}
|
||||
|
||||
public function hashSerializedQuery(): string
|
||||
{
|
||||
return $this->hash ??= md5(sprintf("%s:%s", $this->render(), serialize($this->parameters)));
|
||||
}
|
||||
}
|
||||
+79
-389
@@ -5,24 +5,27 @@ namespace Ulmus;
|
||||
use Ulmus\Attribute\Property\{
|
||||
Field, OrderBy, Where, Having, Relation, Filter, Join, FilterJoin, WithJoin
|
||||
};
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Ulmus\Common\EntityResolver;
|
||||
use Ulmus\Entity\EntityInterface;
|
||||
use Ulmus\Repository\RepositoryInterface;
|
||||
use Ulmus\Repository\WithOptionEnum;
|
||||
|
||||
class Repository
|
||||
class Repository implements RepositoryInterface
|
||||
{
|
||||
use EventTrait, Repository\ConditionTrait, Repository\EscapeTrait;
|
||||
use EventTrait, CacheTrait, Repository\ConditionTrait, Repository\EscapeTrait, Repository\QueryBuildingTrait;
|
||||
|
||||
const DEFAULT_ALIAS = "this";
|
||||
|
||||
public ? ConnectionAdapter $adapter;
|
||||
|
||||
public string $alias;
|
||||
|
||||
public string $entityClass;
|
||||
|
||||
public array $events = [];
|
||||
|
||||
protected QueryBuilder\QueryBuilderInterface $queryBuilder;
|
||||
public ? ConnectionAdapter $adapters;
|
||||
|
||||
public readonly QueryBuilder\QueryBuilderInterface $queryBuilder;
|
||||
|
||||
protected EntityResolver $entityResolver;
|
||||
|
||||
@@ -32,10 +35,21 @@ class Repository
|
||||
$this->entityClass = $entity;
|
||||
$this->alias = $alias;
|
||||
$this->entityResolver = Ulmus::resolveEntity($entity);
|
||||
$this->adapter = $adapter ?? $this->entityResolver->databaseAdapter();
|
||||
|
||||
$queryBuilder = $this->adapter->adapter()->queryBuilderClass();
|
||||
$this->queryBuilder = new $queryBuilder();
|
||||
if ($adapter) {
|
||||
$this->adapter = $adapter;
|
||||
|
||||
$qbClass = $adapter->adapter()->queryBuilderClass();
|
||||
$this->queryBuilder = new $qbClass();
|
||||
}
|
||||
else {
|
||||
$this->adapter = $this->entityResolver->databaseAdapter();
|
||||
$this->queryBuilder = $this->entityClass::queryBuilder();
|
||||
}
|
||||
|
||||
if ($this->adapter->cacheObject) {
|
||||
$this->attachCachingObject($this->adapter->cacheObject);
|
||||
}
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
@@ -100,6 +114,11 @@ class Repository
|
||||
return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0);
|
||||
}
|
||||
|
||||
public function exists(mixed $primaryKey) : bool
|
||||
{
|
||||
return $this->wherePrimaryKey($primaryKey)->count() !== 0;
|
||||
}
|
||||
|
||||
public function deleteOne()
|
||||
{
|
||||
return $this->limit(1)->deleteSqlQuery()->runDeleteQuery();
|
||||
@@ -107,6 +126,8 @@ class Repository
|
||||
|
||||
public function deleteAll()
|
||||
{
|
||||
$this->eventExecute(Event\Query\Delete::class, $this);
|
||||
|
||||
return $this->deleteSqlQuery()->runDeleteQuery();
|
||||
}
|
||||
|
||||
@@ -133,6 +154,8 @@ class Repository
|
||||
else {
|
||||
$pkField = key($primaryKeyDefinition);
|
||||
|
||||
$this->eventExecute(Event\Query\Delete::class, $this, $entity);
|
||||
|
||||
return $this->deleteFromPk($entity->$pkField);
|
||||
}
|
||||
}
|
||||
@@ -187,6 +210,8 @@ class Repository
|
||||
|
||||
$entity->entityFillFromDataset($dataset, true);
|
||||
|
||||
$this->eventExecute(Event\Query\Insert::class, $this, $entity, $dataset, $replace);
|
||||
|
||||
return (bool) ( $pkValue ?? $pdoObject->lastInsertId );
|
||||
}
|
||||
else {
|
||||
@@ -205,6 +230,9 @@ class Repository
|
||||
|
||||
$entity->entityFillFromDataset($dataset, true);
|
||||
|
||||
# $fieldsAndValue ??= &$dataset;
|
||||
$this->eventExecute(Event\Query\Update::class, $this, $entity, $dataset, $replace);
|
||||
|
||||
return $update ? (bool) $update->rowCount : false;
|
||||
}
|
||||
}
|
||||
@@ -223,64 +251,6 @@ class Repository
|
||||
return $changed;
|
||||
}
|
||||
|
||||
public function insertAll(EntityCollection|array $collection, int $size = 1000) : int
|
||||
{
|
||||
if ( empty($collection) ) {
|
||||
return 0;
|
||||
}
|
||||
elseif ( is_array($collection) ) {
|
||||
$collection = $this->entityClass::entityCollection($collection);
|
||||
}
|
||||
|
||||
foreach($collection as $entity) {
|
||||
if ( ! $this->matchEntity($entity) ) {
|
||||
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
|
||||
}
|
||||
}
|
||||
|
||||
$dataset = $entity->toArray();
|
||||
|
||||
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
||||
|
||||
if ( ! $entity->isLoaded() ) {
|
||||
# $dataset = array_filter($dataset, fn($item, $field) => ! ($this->entityResolver->searchFieldAnnotation($field, new Field, false)->readonly ?? false), \ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
$statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery();
|
||||
|
||||
if ( ( 0 !== $statement->lastInsertId ) &&
|
||||
( null !== $primaryKeyDefinition )) {
|
||||
|
||||
$pkField = key($primaryKeyDefinition);
|
||||
$dataset[$pkField] = $statement->lastInsertId;
|
||||
}
|
||||
|
||||
$entity->entityFillFromDataset($dataset, true);
|
||||
|
||||
return (bool) $statement->lastInsertId;
|
||||
}
|
||||
else {
|
||||
if ( $primaryKeyDefinition === null ) {
|
||||
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
|
||||
}
|
||||
|
||||
$diff = $fieldsAndValue ?? $this->generateWritableDataset($entity);
|
||||
|
||||
if ( [] !== $diff ) {
|
||||
$pkField = key($primaryKeyDefinition);
|
||||
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
|
||||
$this->where($pkFieldName, $dataset[$pkFieldName]);
|
||||
|
||||
$update = $this->updateSqlQuery($diff)->runUpdateQuery();
|
||||
|
||||
$entity->entityFillFromDataset($dataset, true);
|
||||
|
||||
return $update ? (bool) $update->rowCount : false;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function replace(object|array $entity, ? array $fieldsAndValue = null) : bool
|
||||
{
|
||||
return $this->save($entity, $fieldsAndValue, true);
|
||||
@@ -297,35 +267,34 @@ class Repository
|
||||
return $changed;
|
||||
}
|
||||
|
||||
public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self
|
||||
|
||||
public function createTable() : mixed
|
||||
{
|
||||
$schema = $schema ?: $this->entityResolver->schemaName();
|
||||
$this->eventExecute(Event\Query\Create::class, $this);
|
||||
|
||||
$this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $this->escapeIdentifier($alias ?: $this->alias), $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null);
|
||||
|
||||
$this->finalizeQuery();
|
||||
|
||||
# ??? $result = Ulmus::runSelectQuery($this->queryBuilder, $this->adapter);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createTable()
|
||||
{
|
||||
return $this->createSqlQuery()->runQuery();
|
||||
}
|
||||
|
||||
public function alterTable(array $fields)
|
||||
public function alterTable(array $fields) : mixed
|
||||
{
|
||||
$this->eventExecute(Event\Query\Alter::class, $this, $fields);
|
||||
|
||||
return $this->alterSqlQuery($fields)->runQuery();
|
||||
}
|
||||
|
||||
public function listTables(? string $database = null)
|
||||
public function truncateTable() : mixed
|
||||
{
|
||||
$this->eventExecute(Event\Query\Truncate::class, $this, $table, $alias, $schema);
|
||||
|
||||
return $this->truncate()->runQuery();
|
||||
}
|
||||
|
||||
public function listTables(? string $database = null) : mixed
|
||||
{
|
||||
return $this->showTablesSqlQuery($database)->runQuery();
|
||||
}
|
||||
|
||||
public function listColumns(? string $table = null)
|
||||
public function listColumns(? string $table = null) : EntityCollection
|
||||
{
|
||||
$table ??= $this->entityResolver->tableName();
|
||||
|
||||
@@ -352,11 +321,8 @@ class Repository
|
||||
|
||||
return (string) $e1 !== (string) $e2;
|
||||
});
|
||||
|
||||
# return array_diff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset );
|
||||
}
|
||||
|
||||
|
||||
public function generateWritableDataset(object $entity, bool $oldValues = false) : array
|
||||
{
|
||||
$intersect = [];
|
||||
@@ -364,7 +330,7 @@ class Repository
|
||||
$dataset = $this->generateDatasetDiff($entity, $oldValues);
|
||||
|
||||
foreach($dataset as $field => $value) {
|
||||
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, [ Field::class, Field::class ], false)->readonly ?? false ) ) {
|
||||
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, Field::class, false)->readonly ?? false ) ) {
|
||||
$intersect[$field] = $field;
|
||||
}
|
||||
}
|
||||
@@ -383,15 +349,6 @@ class Repository
|
||||
}
|
||||
}
|
||||
|
||||
public function removeQueryFragment(null|Query\QueryFragmentInterface|string|array $fragment) : self
|
||||
{
|
||||
foreach((array) $fragment as $item) {
|
||||
$this->queryBuilder->removeFragment($item);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function selectEntity(string $entity, string $alias, string $prependField = "") : self
|
||||
{
|
||||
$prependField and ($prependField .= "$");
|
||||
@@ -425,167 +382,6 @@ class Repository
|
||||
);
|
||||
}
|
||||
|
||||
public function select(array|string|\Stringable $fields, bool $distinct = false) : self
|
||||
{
|
||||
$this->queryBuilder->select($fields, $distinct);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function distinct(array|string|\Stringable $fields) : self
|
||||
{
|
||||
$this->queryBuilder->select($fields);
|
||||
$this->queryBuilder->getFragment(Query\Select::class)->distinct = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function insert(array $fieldlist, string $table, string $alias, ? string $schema, bool $replace = false) : self
|
||||
{
|
||||
$this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $this->escapeIdentifier($alias), $this->escapedDatabase(), $schema, $replace);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function values(array $dataset) : self
|
||||
{
|
||||
$this->queryBuilder->values($dataset);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function update(string $table, string $alias, ? string $schema) : self
|
||||
{
|
||||
$this->queryBuilder->update($this->escapeTable($table), $alias ? $this->escapeIdentifier($alias) : null, $this->escapedDatabase(), $schema);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set(array $dataset) : self
|
||||
{
|
||||
$keys = array_keys($dataset);
|
||||
$escapedFields = array_combine($keys, array_map([ $this, 'escapeField' ], $keys));
|
||||
|
||||
$this->queryBuilder->set($dataset, $escapedFields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete(...$args) : self
|
||||
{
|
||||
$this->queryBuilder->delete();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function from(string $table, ? string $alias, ? string $schema) : self
|
||||
{
|
||||
$this->queryBuilder->from($this->escapeTable($table), $alias ? $this->escapeIdentifier($alias) : null, $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function join(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
|
||||
{
|
||||
$join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $alias ? $this->escapeIdentifier($alias) : null);
|
||||
|
||||
if ( $callback ) {
|
||||
$callback($join);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function outerJoin(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
|
||||
{
|
||||
$join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $alias ? $this->escapeIdentifier($alias) : null);
|
||||
|
||||
if ( $callback ) {
|
||||
$callback($join);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function match() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function notMatch() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function between() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function notBetween() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function groupBy(string|\Stringable $field) : self
|
||||
{
|
||||
$this->queryBuilder->groupBy($field);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function groups(array $groups) : self
|
||||
{
|
||||
foreach($groups as $field ) {
|
||||
$this->groupBy($field);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy(string|object $field, ? string $direction = null) : self
|
||||
{
|
||||
$this->queryBuilder->orderBy($field, $direction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
# @UNTESTED
|
||||
public function randomizeOrder() : self
|
||||
{
|
||||
$this->queryBuilder->orderBy(Common\Sql::function('RAND', Sql::identifier('CURDATE()+0')));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orders(array $orderList) : self
|
||||
{
|
||||
foreach($orderList as $field => $direction) {
|
||||
if (is_numeric($field)) {
|
||||
$this->orderBy($direction);
|
||||
}
|
||||
else {
|
||||
# Associative array with direction
|
||||
$this->orderBy($field, $direction);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit(int $value) : self
|
||||
{
|
||||
$this->queryBuilder->limit($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function offset(int $value) : self
|
||||
{
|
||||
$this->queryBuilder->offset($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/* @TODO */
|
||||
public function commit() : self
|
||||
{
|
||||
@@ -680,23 +476,23 @@ class Repository
|
||||
$foreignKey = is_string($attribute->foreignKey) ? $entity::field($attribute->foreignKey, $alias) : $attribute->foreignKey;
|
||||
|
||||
$this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) {
|
||||
if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) {
|
||||
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) {
|
||||
if ( ! is_object($condition->field) ) {
|
||||
$field = $this->entityClass::field($condition->field);
|
||||
}
|
||||
else {
|
||||
$field = clone $condition->field;
|
||||
}
|
||||
if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) {
|
||||
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) {
|
||||
if ( ! is_object($condition->field) ) {
|
||||
$field = $this->entityClass::field($condition->field);
|
||||
}
|
||||
else {
|
||||
$field = clone $condition->field;
|
||||
}
|
||||
|
||||
# Adding directly
|
||||
if ( $field->entityClass === $entity ) {
|
||||
$field->alias = $alias;
|
||||
# Adding directly
|
||||
if ( $field->entityClass === $entity ) {
|
||||
$field->alias = $alias;
|
||||
|
||||
$join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->getValue(), $condition->operator);
|
||||
$join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->getValue(), $condition->operator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! in_array(WithOptionEnum::SkipJoinFilter, $options) ) {
|
||||
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ FilterJoin::class ]) as $filter) {
|
||||
@@ -798,7 +594,7 @@ class Repository
|
||||
|
||||
foreach ($where as $condition) {
|
||||
# $repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [$this]) : $condition->getValue(), $condition->operator, $condition->condition);
|
||||
$repository->where($condition->field, $condition->getValue($this), $condition->operator, $condition->condition);
|
||||
$repository->where($condition->field, $condition->getValue(/* why repository sent here ??? $this */), $condition->operator, $condition->condition);
|
||||
}
|
||||
|
||||
foreach ($order as $item) {
|
||||
@@ -867,15 +663,21 @@ class Repository
|
||||
$entityCollection = $entityClass::entityCollection();
|
||||
|
||||
$this->finalizeQuery();
|
||||
|
||||
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
|
||||
|
||||
$dataset = [];
|
||||
|
||||
$this->eventExecute(\Ulmus\Event\Repository\CollectionFromQueryDatasetInterface::class, $this, $dataset);
|
||||
|
||||
foreach($dataset ?: Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
|
||||
$this->eventExecute(\Ulmus\Event\Repository\CollectionFromQueryItemInterface::class, $entityData);
|
||||
|
||||
$entity = $this->instanciateEntity($entityClass);
|
||||
$entity->loadedFromAdapter = $this->adapter->name;
|
||||
|
||||
$entityCollection->append( $entity->resetVirtualProperties()->entityFillFromDataset($entityData) );
|
||||
$entityCollection->append( $entity->entityFillFromDataset($entityData) );
|
||||
}
|
||||
|
||||
$this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $entityCollection);
|
||||
$this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $this, $entityCollection);
|
||||
|
||||
return $entityCollection;
|
||||
}
|
||||
@@ -917,110 +719,6 @@ class Repository
|
||||
return Ulmus::runDeleteQuery($this->queryBuilder, $this->adapter);
|
||||
}
|
||||
|
||||
public function resetQuery() : self
|
||||
{
|
||||
$this->queryBuilder->reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function insertSqlQuery(array $dataset, bool $replace = false) : self
|
||||
{
|
||||
if ( null === $insert = $this->queryBuilder->getFragment(Query\Insert::class) ) {
|
||||
$this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName(), $replace);
|
||||
}
|
||||
else {
|
||||
$insert->replace = $replace;
|
||||
}
|
||||
|
||||
$this->values($dataset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function updateSqlQuery(array $dataset) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Update::class) ) {
|
||||
$this->update($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
$this->set($dataset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function selectSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
|
||||
$fields = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true);
|
||||
$this->select($this->entityClass::fields(array_map(fn($f) => $f->object->name ?? $f->name, $fields)));
|
||||
}
|
||||
|
||||
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
||||
$this->from($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function deleteSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Delete::class) ) {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
||||
$this->from($this->entityResolver->tableName(), null, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) {
|
||||
$this->queryBuilder->create($this->adapter->adapter(), $this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function alterSqlQuery(array $fields) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) {
|
||||
$this->queryBuilder->alter($this->adapter->adapter(), $fields, $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showDatabasesSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showDatabases();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showTablesSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showTables();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showColumnsSqlQuery(string $table) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showColumns($table);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function fromRow($row) : self
|
||||
{
|
||||
|
||||
@@ -1031,15 +729,6 @@ class Repository
|
||||
|
||||
}
|
||||
|
||||
public function getSqlQuery(bool $flush = true) : string
|
||||
{
|
||||
$result = $this->queryBuilder->render();
|
||||
|
||||
$flush and $this->queryBuilder->reset();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function instanciateEntityCollection(...$arguments) : EntityCollection
|
||||
{
|
||||
return $this->entityClass::entityCollection(...$arguments);
|
||||
@@ -1047,9 +736,10 @@ class Repository
|
||||
|
||||
public function instanciateEntity(? string $entityClass = null) : object
|
||||
{
|
||||
$entityClass ??= $this->entityClass;
|
||||
$entity = ( new \ReflectionClass($entityClass ?? $this->entityClass) )->newInstanceWithoutConstructor();
|
||||
$entity->initializeEntity();
|
||||
|
||||
return new $entityClass;
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function hasFilters() : bool
|
||||
|
||||
@@ -151,7 +151,7 @@ trait ConditionTrait
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeQueryFragment(Query\Fragment|\Stringable|string|array $fragment) : self
|
||||
public function removeQueryFragment(null|Query\QueryFragmentInterface|string|\Stringable|array $fragment) : self
|
||||
{
|
||||
foreach((array) $fragment as $item) {
|
||||
$this->queryBuilder->removeFragment($item);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\Query;
|
||||
|
||||
trait JsonConditionTrait
|
||||
{
|
||||
public function whereJsonArray(string|\Stringable $field, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, $condition = Query\Where::CONDITION_AND) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
||||
$this->from($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
$this->queryBuilder->whereJsonArray($field, $value, $operator, $condition);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\{Common\EntityResolver, ConnectionAdapter, MysqlQueryBuilder, Repository, Query, Ulmus};
|
||||
use Ulmus\{ Repository, Query, Ulmus};
|
||||
|
||||
class MysqlRepository extends Repository {
|
||||
use JsonConditionTrait;
|
||||
|
||||
public function pragma(/*object|Stringable*/ $pragma, $argument = null, bool $callable = false) : self
|
||||
{
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\{Common\EntityResolver, Query, Common};
|
||||
|
||||
trait QueryBuildingTrait
|
||||
{
|
||||
public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self
|
||||
{
|
||||
$schema = $schema ?: $this->entityResolver->schemaName();
|
||||
|
||||
$this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $this->escapeIdentifier($alias ?: $this->alias), $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function select(array|string|\Stringable $fields, bool $distinct = false) : self
|
||||
{
|
||||
$this->queryBuilder->select($fields, $distinct);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function distinct(array|string|\Stringable $fields) : self
|
||||
{
|
||||
$this->queryBuilder->select($fields);
|
||||
$this->queryBuilder->getFragment(Query\Select::class)->distinct = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function insert(array $fieldlist, string $table, string $alias, ? string $schema, bool $replace = false) : self
|
||||
{
|
||||
$this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $this->escapeIdentifier($alias), $this->escapedDatabase(), $schema, $replace);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function values(array $dataset) : self
|
||||
{
|
||||
$this->queryBuilder->values($dataset);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function update(string $table, string $alias, ? string $schema) : self
|
||||
{
|
||||
$this->queryBuilder->update($this->escapeTable($table), $alias ? $this->escapeIdentifier($alias) : null, $this->escapedDatabase(), $schema);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set(array $dataset) : self
|
||||
{
|
||||
$keys = array_keys($dataset);
|
||||
$escapedFields = array_combine($keys, array_map([ $this, 'escapeField' ], $keys));
|
||||
|
||||
$this->queryBuilder->set($dataset, $escapedFields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete(...$args) : self
|
||||
{
|
||||
$this->queryBuilder->delete();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function from(string $table, ? string $alias, ? string $schema) : self
|
||||
{
|
||||
$this->queryBuilder->from($this->escapeTable($table), $alias ? $this->escapeIdentifier($alias) : null, $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function join(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
|
||||
{
|
||||
$join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $alias ? $this->escapeIdentifier($alias) : null);
|
||||
|
||||
if ( $callback ) {
|
||||
$callback($join);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function outerJoin(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
|
||||
{
|
||||
$join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $alias ? $this->escapeIdentifier($alias) : null);
|
||||
|
||||
if ( $callback ) {
|
||||
$callback($join);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function match() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function notMatch() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function between() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function notBetween() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function groupBy(string|\Stringable $field) : self
|
||||
{
|
||||
$this->queryBuilder->groupBy($field);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function groups(array $groups) : self
|
||||
{
|
||||
foreach($groups as $field ) {
|
||||
$this->groupBy($field);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy(string|\Stringable $field, null|string|\Stringable $direction = null) : self
|
||||
{
|
||||
$this->queryBuilder->orderBy($field, $direction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
# @UNTESTED
|
||||
public function randomizeOrder() : self
|
||||
{
|
||||
$this->queryBuilder->orderBy(Common\Sql::function('RAND', Common\Sql::identifier('CURDATE()+0')));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orders(array $orderList) : self
|
||||
{
|
||||
foreach($orderList as $field => $direction) {
|
||||
if (is_numeric($field)) {
|
||||
$this->orderBy($direction);
|
||||
}
|
||||
else {
|
||||
# Associative array with direction
|
||||
$this->orderBy($field, $direction);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit(int $value) : self
|
||||
{
|
||||
$this->queryBuilder->limit($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function offset(int $value) : self
|
||||
{
|
||||
$this->queryBuilder->offset($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSqlQuery(bool $flush = true) : string
|
||||
{
|
||||
$result = $this->queryBuilder->render();
|
||||
|
||||
$flush and $this->queryBuilder->reset();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function resetQuery() : self
|
||||
{
|
||||
$this->queryBuilder->reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function insertSqlQuery(array $dataset, bool $replace = false) : self
|
||||
{
|
||||
if ( null === $insert = $this->queryBuilder->getFragment(Query\Insert::class) ) {
|
||||
$this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName(), $replace);
|
||||
}
|
||||
else {
|
||||
$insert->replace = $replace;
|
||||
}
|
||||
|
||||
$this->values($dataset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function updateSqlQuery(array $dataset) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Update::class) ) {
|
||||
$this->update($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
$this->set($dataset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function selectSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
|
||||
$fields = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true);
|
||||
$this->select($this->entityClass::fields(array_map(fn($f) => $f->object->name ?? $f->name, $fields)));
|
||||
}
|
||||
|
||||
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
||||
$this->from($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function deleteSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Delete::class) ) {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
||||
$this->from($this->entityResolver->tableName(), null, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) {
|
||||
$this->queryBuilder->create($this->adapter->adapter(), $this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function alterSqlQuery(array $fields) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) {
|
||||
$this->queryBuilder->alter($this->adapter->adapter(), $fields, $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showDatabasesSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showDatabases();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showTablesSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showTables();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showColumnsSqlQuery(string $table) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showColumns($table);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class RelationBuilder
|
||||
|
||||
$value = call_user_func([ $this->repository, $relation->function() ]) ?? null;
|
||||
|
||||
if ($value === null && $this->resolver->reflectedClass->getProperties(true)[$name]->allowsNull()) {
|
||||
if ($value === null && $this->fieldIsNullable($name) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ class RelationBuilder
|
||||
throw new \InvalidArgumentException("Unknown or no relation was provided as relation type.");
|
||||
}
|
||||
|
||||
protected function fetchFromDataset($name, ? array $data = null) : object|bool
|
||||
protected function fetchFromDataset($name, ? array $data = null) : object|bool|null
|
||||
{
|
||||
$attribute = $this->resolver->searchFieldAnnotation($name, [ Join::class ]) ?:
|
||||
$this->resolver->searchFieldAnnotation($name, [ Relation::class ]);
|
||||
@@ -221,10 +221,7 @@ class RelationBuilder
|
||||
$entity = $attribute->entity ?? $this->resolver->reflectedClass->getProperties()[$name]->getTypes()[0]->type;
|
||||
}
|
||||
|
||||
$name = strtolower($name);
|
||||
|
||||
foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) {
|
||||
|
||||
if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, strtolower($name)) ) {
|
||||
if ($value) {
|
||||
if ( null === ( $dataset = \json_decode($value, true) ) ) {
|
||||
@@ -250,7 +247,7 @@ class RelationBuilder
|
||||
return ( new $entity() )->fromArray($data)->resetVirtualProperties();
|
||||
}
|
||||
else {
|
||||
return new $entity();
|
||||
return $this->fieldIsNullable($name) ? null : new $entity();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,4 +340,9 @@ class RelationBuilder
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function fieldIsNullable(string $name) : bool
|
||||
{
|
||||
return $this->resolver->reflectedClass->getProperties(true)[$name]->allowsNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
interface RepositoryInterface
|
||||
{
|
||||
|
||||
}
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\{ConnectionAdapter, MysqlQueryBuilder, Repository, Query, Ulmus, Entity};
|
||||
use Ulmus\{EntityCollection, Repository, Entity};
|
||||
|
||||
class SqliteRepository extends Repository {
|
||||
use JsonConditionTrait;
|
||||
|
||||
public function pragma(\Stringable|string $pragma, $argument = null, bool $callable = false) : self
|
||||
{
|
||||
@@ -18,7 +19,7 @@ class SqliteRepository extends Repository {
|
||||
|
||||
}
|
||||
|
||||
public function listColumns(? string $table = null)
|
||||
public function listColumns(? string $table = null) : EntityCollection
|
||||
{
|
||||
$table ??= $this->entityResolver->tableName();
|
||||
$this->showColumnsSqlQuery($table);
|
||||
|
||||
@@ -74,8 +74,6 @@ trait SearchRequestFromRequestTrait
|
||||
case $attribute instanceof SearchWhere:
|
||||
case $attribute instanceof SearchLike:
|
||||
case $attribute instanceof SearchManual:
|
||||
|
||||
|
||||
if ($attribute->toggle) {
|
||||
$this->$propertyName = !empty($value);
|
||||
} elseif ($value !== null) {
|
||||
@@ -92,7 +90,8 @@ trait SearchRequestFromRequestTrait
|
||||
sprintf("Given value '%s' do not exists within enum '%s'. Try one of those values instead : %s", $value, $enum, $cases)
|
||||
);
|
||||
}
|
||||
} elseif ($type->builtIn) {
|
||||
}
|
||||
elseif ($type->builtIn || $value instanceof \Stringable ) {
|
||||
$this->$propertyName = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ trait SearchRequestPaginationTrait {
|
||||
|
||||
public function pageCount() : int
|
||||
{
|
||||
return $this->pageCount = ceil($this->count() / $this->limit());
|
||||
return $this->limit() ? $this->pageCount = ceil($this->count() / $this->limit()) : 1;
|
||||
}
|
||||
|
||||
public function hasPagination() : int
|
||||
|
||||
+2
-3
@@ -64,7 +64,7 @@ abstract class Ulmus
|
||||
}
|
||||
|
||||
public static function runSelectQuery(QueryBuilder\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null)
|
||||
{
|
||||
{
|
||||
$dataset = static::connector($adapter)->select($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? []));
|
||||
$queryBuilder->reset();
|
||||
|
||||
@@ -74,7 +74,6 @@ abstract class Ulmus
|
||||
public static function runQuery(QueryBuilder\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null)
|
||||
{
|
||||
$return = static::connector($adapter)->runQuery($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? []));
|
||||
|
||||
$queryBuilder->reset();
|
||||
|
||||
return $return;
|
||||
@@ -82,7 +81,7 @@ abstract class Ulmus
|
||||
|
||||
public static function runInsertQuery(QueryBuilder\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null)
|
||||
{
|
||||
$return = static::connector($adapter)->runInsertQuery($queryBuilder->render(),$queryBuilder->values ?? []);
|
||||
$return = static::connector($adapter)->runInsertQuery($queryBuilder->render(), $queryBuilder->values ?? []);
|
||||
$queryBuilder->reset();
|
||||
|
||||
return $return;
|
||||
|
||||
Reference in new issue
Block a user