- WIP on SQLite Adapter and migration

This commit is contained in:
Dave M. 2023-03-27 18:38:31 +00:00
parent cee978ecfd
commit 9514a46ae7
14 changed files with 98 additions and 40 deletions

View File

@ -28,4 +28,5 @@ interface AdapterInterface {
public function repositoryClass() : string;
public function queryBuilderClass() : string;
public function tableSyntax() : array;
public function whitelistAttributes(array &$parameters) : void;
}

View File

@ -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,9 @@ 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));
}
}

View File

@ -14,6 +14,10 @@ use Ulmus\{Entity\InformationSchema\Table, Repository, QueryBuilder};
class MsSQL implements AdapterInterface {
use DefaultAdapterTrait;
const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment',
];
const DSN_PREFIX = "sqlsrv";
public int $port;

View File

@ -14,6 +14,10 @@ use Ulmus\Migration\FieldDefinition;
class MySQL implements AdapterInterface {
use DefaultAdapterTrait;
const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment', 'update',
];
const DSN_PREFIX = "mysql";
public string $hostname;

View File

@ -3,6 +3,7 @@
namespace Ulmus\Adapter;
use Ulmus\Common\PdoObject;
use Ulmus\ConnectionAdapter;
use Ulmus\Entity\Sqlite\Table;
use Ulmus\Exception\AdapterConfigurationException;
@ -11,6 +12,9 @@ use Ulmus\{Repository, QueryBuilder, Ulmus};
class SQLite implements AdapterInterface {
use DefaultAdapterTrait;
const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment'
];
const DSN_PREFIX = "sqlite";
@ -87,9 +91,11 @@ 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 Table::repository(Repository::DEFAULT_ALIAS, $adapter)
->select(\Ulmus\Common\Sql::raw('this.*'))
->loadOneFromField(Table::field('tableName'), $tableName);
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string

View File

@ -9,7 +9,7 @@ class Relation {
public function __construct(
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);
@ -54,17 +54,17 @@ class Relation {
public function isOneToOne() : bool
{
return $this->type === Relation\RelationTypeEnum::oneToOne || $this->normalizeType() === 'onetoone';
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::oneToOne : $this->normalizeType() === 'onetoone';
}
public function isOneToMany() : bool
{
return $this->type === Relation\RelationTypeEnum::oneToMany || $this->normalizeType() === 'onetomany';
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::oneToMany : $this->normalizeType() === 'onetomany';
}
public function isManyToMany() : bool
{
return $this->type === Relation\RelationTypeEnum::manyToMany || $this->normalizeType() === 'manytomany';
return $this->type instanceof Relation\RelationTypeEnum ? $this->type === Relation\RelationTypeEnum::manyToMany : $this->normalizeType() === 'manytomany';
}
public function function() : string
@ -73,7 +73,7 @@ class Relation {
return $this->function;
}
elseif ($this->isOneToOne()) {
return 'load';
return 'loadOne';
}
return 'loadAll';

View File

@ -13,13 +13,23 @@ 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,
) {
$this->field = Attribute::handleArrayField($field);
$this->fieldValue = Attribute::handleArrayField($fieldValue);
}
public function getValue() : mixed
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));
}
}
return $this->fieldValue ?? $this->value;
}
}

View File

@ -12,7 +12,7 @@ class Column
{
use \Ulmus\EntityTrait;
#[Id]
#[Field\Id]
public int $cid;
#[Field]

View File

@ -12,7 +12,7 @@ class Schema
{
use \Ulmus\EntityTrait;
#[Id]
#[Field\Id]
public ? string $name;
#[Field]
@ -27,6 +27,6 @@ class Schema
#[Field]
public ? string $sql;
#[Relation("oneToMany", key: "tableName", foreignKey: "tableName", entity: "Schema")]
#[Relation("oneToMany", key: "tableName", foreignKey: "tableName", entity: Column::class)]
public EntityCollection $columns;
}

View File

@ -123,14 +123,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) {
@ -140,7 +140,7 @@ 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
{
$collection = new static();
@ -155,7 +155,7 @@ class EntityCollection extends \ArrayObject {
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());
@ -214,7 +214,7 @@ class EntityCollection extends \ArrayObject {
return $list;
}
public function unique(\Stringable|callable $field, bool $strict = false) : self
public function unique(\Stringable|callable|string $field, bool $strict = false) : self
{
$list = [];
$obj = new static();

View File

@ -34,7 +34,6 @@ trait EntityTrait {
$entityResolver = $this->resolveEntity();
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);
@ -51,7 +50,18 @@ trait EntityTrait {
}
elseif ( $field['type'] === 'array' ) {
if ( is_string($value)) {
$this->{$field['name']} = substr($value, 0, 1) === "a" ? unserialize($value) : json_decode($value, true);
if (substr($value, 0, 1) === "a") {
$this->{$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));
}
$this->{$field['name']} = $data;
}
}
elseif ( is_array($value) ) {
$this->{$field['name']} = $value;

View File

@ -23,7 +23,7 @@ class FieldDefinition {
public ? int $precision;
public ? int $length;
public null|int|string $length;
public ? string $update;
@ -38,6 +38,8 @@ class FieldDefinition {
$this->tags = $data['tags'];
$field = $this->getFieldTag();
$adapter->whitelistAttributes($field->attributes);
$this->type = $field->type ?? $data['type'];
$this->length = $field->length ?? null;
$this->precision = $field->precision ?? null;

View File

@ -39,7 +39,9 @@ class Repository
$this->alias = $alias;
$this->entityResolver = Ulmus::resolveEntity($entity);
$this->adapter = $adapter ?? $this->entityResolver->databaseAdapter();
$this->queryBuilder = Ulmus::queryBuilder($entity);
$queryBuilder = $this->adapter->adapter()->queryBuilderClass();
$this->queryBuilder = new $queryBuilder();
}
public function __clone()
@ -49,7 +51,7 @@ class Repository
public function loadOne() : ? object
{
return $this->limit(1)->selectSqlQuery()->collectionFromQuery()[0] ?? null;
return $this->limit(1)->selectSqlQuery()->collectionFromQuery()[0];
}
public function loadOneFromField($field, $value) : ? object
@ -606,7 +608,7 @@ class Repository
$isRelation = ( $annotation instanceof Relation ) || ($annotation instanceof Attribute\Property\Relation);
if ($isRelation && ( $annotation->normalizeType() === 'manytomany' )) {
if ($isRelation && ( $annotation->isManyToMany() )) {
throw new Exception("Many-to-many relation can not be preloaded within joins.");
}
@ -618,10 +620,12 @@ class Repository
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ]) ) {
$escAlias = $this->escapeIdentifier($alias);
$fieldName = $this->escapeIdentifier($key);
$name = $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Field::class, Field::class ])->name ?? $field['name'];
$this->select("$escAlias.$key as $alias\${$name}");
$this->select("$escAlias.$fieldName as $alias\${$name}");
}
}
@ -761,7 +765,8 @@ 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, 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);
}
foreach ($order as $item) {
@ -778,17 +783,21 @@ class Repository
$values[] = is_callable($field) ? $field($item) : $item->$entityProperty;
}
$values = array_unique($values);
$repository->where($key, $values);
$results = call_user_func([ $repository, $relation->function() ]);
$results = $repository->loadAll();
if ($relation->isOneToOne()) {
$item->$name = $results ?: new $baseEntity();
foreach ($collection as $item) {
$item->$name = $results->searchOne($item->$entityProperty, $property) ?: new $baseEntity();
}
}
elseif ($relation->isOneToMany()) {
foreach ($collection as $item) {
$item->$name = $baseEntity::entityCollection();
$item->$name->mergeWith($results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty));
$item->$name->mergeWith($results->searchAll($item->$entityProperty, $property));
}
}
}

View File

@ -102,23 +102,21 @@ class RelationBuilder
$this->entity->eventExecute(Event\EntityRelationLoadInterface::class, $name, $this->repository);
$result = call_user_func([ $this->repository, $relation->function ]);
return count($result) === 0 ? $this->instanciateEmptyEntity($name, $relation): $result[0];
return call_user_func([ $this->repository, $relation->function() ]) ?? $this->instanciateEmptyEntity($name, $relation);
case $relation->isOneToMany():
$this->oneToMany($name, $relation);
$this->entity->eventExecute(Event\EntityRelationLoadInterface::class, $name, $this->repository);
return call_user_func([ $this->repository, $relation->function ]);
return call_user_func([ $this->repository, $relation->function() ]);
case $relation->isManyToMany():
$this->manyToMany($name, $relation, $relationRelation);
$this->entity->eventExecute(Event\EntityRelationLoadInterface::class, $name, $this->repository);
$results = call_user_func([ $this->repository, $relationRelation->function ]);
$results = call_user_func([ $this->repository, $relationRelation->function() ]);
if ($relation->bridgeField ?? false) {
$collection = $relation->bridge::entityCollection();
@ -152,7 +150,7 @@ class RelationBuilder
$this->repository->open();
foreach($this->wheres as $condition) {
$this->repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [ $this->entity ]) : $condition->getValue(), $condition->operator);
$this->repository->where($condition->field, $condition->getValue($this->entity), $condition->operator);
}
$this->repository->close();
@ -201,7 +199,7 @@ class RelationBuilder
$vars = [];
$len = strlen( $name ) + 1;
$isRelation = ( $annotation instanceof Relation ) || ($annotation instanceof Attribute\Property\Relation);
$isRelation = ( $annotation instanceof Relation ) || ( $annotation instanceof Attribute\Property\Relation );
if ( $isRelation && $annotation->isManyToMany() ) {
$entity = $this->relationAnnotations($name, $annotation)['relationRelation']->entity;
@ -262,7 +260,13 @@ class RelationBuilder
$field = $relation->key;
if ($relation->foreignKey) {
$value = ! is_string($field) && is_callable($field) ? $field($this->entity) : $this->entity->$field;
if ( $relation->generateKey ) {
$value = call_user_func_array($relation->generateKey, [ $this->entity ]);
}
else {
$value = $this->entity->$field;
}
$this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey), $value );
}