- WIP on SQLite Adapter and migration
This commit is contained in:
		
							parent
							
								
									cee978ecfd
								
							
						
					
					
						commit
						9514a46ae7
					
				| @ -28,4 +28,5 @@ interface AdapterInterface { | ||||
|     public function repositoryClass() : string; | ||||
|     public function queryBuilderClass() : string; | ||||
|     public function tableSyntax() : array; | ||||
|     public function whitelistAttributes(array &$parameters) : void; | ||||
| } | ||||
|  | ||||
| @ -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)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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 | ||||
| @ -197,4 +203,4 @@ class SQLite implements AdapterInterface { | ||||
|         $pdo->sqliteCreateFunction('month', fn($date) => ( new \DateTime($date) )->format('m'), 1); | ||||
|         $pdo->sqliteCreateFunction('year', fn($date) => ( new \DateTime($date) )->format('Y'), 1); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -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'; | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -12,7 +12,7 @@ class Column | ||||
| { | ||||
|     use \Ulmus\EntityTrait; | ||||
| 
 | ||||
|     #[Id]
 | ||||
|     #[Field\Id]
 | ||||
|     public int $cid; | ||||
| 
 | ||||
|     #[Field]
 | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
| @ -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(); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -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 ); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user