Merge branch 'master' of https://git.mcnd.ca/mcndave/ulmus
This commit is contained in:
		
						commit
						531908b04c
					
				| @ -84,7 +84,7 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { | ||||
|             $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); | ||||
|             $pdo->setAttribute(\PDO::SQLSRV_ATTR_ENCODING, \PDO::SQLSRV_ENCODING_UTF8); | ||||
|         } | ||||
|         catch(PDOException $ex){ | ||||
|         catch(\PDOException $ex){ | ||||
|             throw $ex; | ||||
|         } | ||||
|         finally { | ||||
|  | ||||
| @ -78,7 +78,7 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { | ||||
|             $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); | ||||
|             $pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); | ||||
|         } | ||||
|         catch(PDOException $ex){ | ||||
|         catch(\PDOException $ex){ | ||||
|             throw $ex; | ||||
|         } | ||||
|         finally { | ||||
|  | ||||
| @ -19,22 +19,11 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface | ||||
| 
 | ||||
|     const DSN_PREFIX = "sqlite"; | ||||
| 
 | ||||
|     public string $path; | ||||
| 
 | ||||
|     public array $pragma; | ||||
| 
 | ||||
|     public function __construct( | ||||
|         ? string $path = null, | ||||
|         ? array $pragma = null | ||||
|     ) { | ||||
|         if ($path !== null) { | ||||
|             $this->path = $path; | ||||
|         } | ||||
| 
 | ||||
|         if ($pragma !== null) { | ||||
|             $this->pragma = $pragma; | ||||
|         } | ||||
|     } | ||||
|         public null|string $path = null, | ||||
|         public null|array $pragmaBegin = null, | ||||
|         public null|array $pragmaClose = null, | ||||
|     ) { } | ||||
| 
 | ||||
|     public function connect() : PdoObject | ||||
|     { | ||||
| @ -44,9 +33,14 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface | ||||
|             $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); | ||||
|             $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); | ||||
| 
 | ||||
|             $pdo->onClose = function(PdoObject $obj) { | ||||
|                 static::registerPragma($obj, $this->pragmaClose); | ||||
|             }; | ||||
| 
 | ||||
|             $this->exportFunctions($pdo); | ||||
|             $this->registerPragma($pdo, $this->pragmaBegin); | ||||
|         } | ||||
|         catch(PDOException $ex){ | ||||
|         catch(\PDOException $ex){ | ||||
|             throw $ex; | ||||
|         } | ||||
| 
 | ||||
| @ -63,13 +57,15 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface | ||||
|     public function setup(array $configuration) : void | ||||
|     { | ||||
|         $this->path = $configuration['path'] ?? ""; | ||||
|         $this->pragma = $configuration['pragma'] ?? []; | ||||
|         $this->pragmaBegin = array_filter($configuration['pragma_begin'] ?? []); | ||||
|         $this->pragmaClose = array_filter($configuration['pragma_close'] ?? []); | ||||
|     } | ||||
| 
 | ||||
|     # https://sqlite.org/lang_keywords.html
 | ||||
|     public function escapeIdentifier(string $segment, int $type) : string  | ||||
|     { | ||||
|         switch($type) { | ||||
|             default: | ||||
|             case static::IDENTIFIER_DATABASE: | ||||
|             case static::IDENTIFIER_TABLE: | ||||
|             case static::IDENTIFIER_FIELD: | ||||
| @ -194,6 +190,24 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface | ||||
|         $pdo->sqliteCreateFunction('year', fn($date) => ( new \DateTime($date) )->format('Y'), 1); | ||||
|     } | ||||
| 
 | ||||
|     public static function registerPragma(PdoObject $pdo, array $pragmaList) : void | ||||
|     { | ||||
|         $builder = new QueryBuilder\SqliteQueryBuilder(); | ||||
| 
 | ||||
|         foreach($pragmaList as $pragma) { | ||||
|             list($key, $value) = explode('=', $pragma) + [ null, null ]; | ||||
| 
 | ||||
|             $sql = $builder->pragma($key, $value)->render(); | ||||
|             $query = $pdo->query($sql); | ||||
| 
 | ||||
|             if ( ! $query->execute() ) { | ||||
|                 throw new \InvalidArgumentException(sprintf("Pragma query could not be executed : %s", $sql)); | ||||
|             } | ||||
| 
 | ||||
|             $builder->reset(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable | ||||
|     { | ||||
|         return implode(" ", [ | ||||
|  | ||||
| @ -8,7 +8,7 @@ class Field implements \Notes\Annotation { | ||||
| 
 | ||||
|     public string $name; | ||||
|      | ||||
|     public int $length; | ||||
|     public int|string $length; | ||||
| 
 | ||||
|     public int $precision; | ||||
| 
 | ||||
|  | ||||
| @ -13,7 +13,7 @@ use Ulmus\Attribute\ConstrainActionEnum; | ||||
| class ForeignKey extends PrimaryKey { | ||||
|     public function __construct( | ||||
|         public ? string $name = null, | ||||
|         public ? string $type = 'bigint', | ||||
|         public ? string $type = null, | ||||
|         public null|int|string $length = null, | ||||
|         public ? int $precision = null, | ||||
|         public array $attributes = [ | ||||
|  | ||||
| @ -15,6 +15,8 @@ class PdoObject extends PDO { | ||||
| 
 | ||||
|     public mixed $lastInsertId = null; | ||||
| 
 | ||||
|     public \Closure $onClose; | ||||
| 
 | ||||
|     public function select(string $sql, array $parameters = []): PDOStatement | ||||
|     { | ||||
|         static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]); | ||||
| @ -32,6 +34,13 @@ class PdoObject extends PDO { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function __destruct() | ||||
|     { | ||||
|         if ($this->onClose ?? null) { | ||||
|             call_user_func($this->onClose, $this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function runQuery(string $sql, array $parameters = []): ? static | ||||
|     { | ||||
|         static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]); | ||||
|  | ||||
| @ -62,7 +62,7 @@ trait EntityTrait { | ||||
|                         $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 with field %s", json_last_error_msg(), $value, json_encode($field))); | ||||
|                             throw new \Exception(sprintf("JSON error while decoding in EntityTrait : '%s' given %s", json_last_error_msg(), $value)); | ||||
|                         } | ||||
| 
 | ||||
|                         $this->{$field['name']} = $data; | ||||
| @ -112,7 +112,7 @@ trait EntityTrait { | ||||
|                 $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER); | ||||
|             } | ||||
|             elseif ($overwriteDataset) { | ||||
|                 $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset; | ||||
|                 $this->entityLoadedDataset = iterator_to_array(array_change_key_case($dataset, \CASE_LOWER)) + $this->entityLoadedDataset; | ||||
|             } | ||||
|         } | ||||
|          | ||||
| @ -123,11 +123,9 @@ trait EntityTrait { | ||||
|     public function resetVirtualProperties() : self | ||||
|     { | ||||
|         foreach($this->resolveEntity()->properties as $prop => $property) { | ||||
|             if ( empty($property['builtin']) ) { | ||||
|                 foreach($property['tags'] as $tag) { | ||||
|                     if ( in_array(strtolower($tag['tag']), [ 'relation', 'join', 'virtual' ] ) ) { | ||||
|                         unset($this->$prop); | ||||
|                     } | ||||
|             foreach($property['tags'] as $tag) { | ||||
|                 if ( in_array(strtolower($tag['tag']), [ 'relation', 'join', 'virtual' ] ) ) { | ||||
|                     unset($this->$prop); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -264,10 +262,6 @@ trait EntityTrait { | ||||
|     #[Ignore]
 | ||||
|     public function __clone() | ||||
|     { | ||||
|         foreach($this as $prop) { | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) { | ||||
|             $key = key($pkField); | ||||
| 
 | ||||
| @ -309,9 +303,12 @@ trait EntityTrait { | ||||
|     } | ||||
| 
 | ||||
|     #[Ignore]
 | ||||
|     public static function field($name, null|string|bool $alias = Repository::DEFAULT_ALIAS) : EntityField | ||||
|     public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField | ||||
|     { | ||||
|         return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : ( $alias === false ? '' : Repository::DEFAULT_ALIAS ), Ulmus::resolveEntity(static::class)); | ||||
| 
 | ||||
|         $default = ( $alias === false ? '' : Repository::DEFAULT_ALIAS ); # bw compatibility, to be deprecated
 | ||||
| 
 | ||||
|         return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : $default, Ulmus::resolveEntity(static::class)); | ||||
|     } | ||||
| 
 | ||||
|     #[Ignore]
 | ||||
|  | ||||
| @ -30,7 +30,7 @@ class Pragma extends \Ulmus\Query\Fragment { | ||||
|     public function render() : string | ||||
|     { | ||||
|         if ( isset($this->value) ) { | ||||
|             $value = sprintf($this->callable ? " (%s)" : " = %s", $this->value); | ||||
|             $value = sprintf($this->callable ? " (%s)" : "=%s", $this->value); | ||||
|         } | ||||
| 
 | ||||
|         return $this->renderSegments([ | ||||
|  | ||||
| @ -16,7 +16,7 @@ class Where extends Fragment { | ||||
|     const COMPARISON_IS = "IS"; | ||||
|     const COMPARISON_NULL = "NULL"; | ||||
|      | ||||
|     const SQL_TOKEN = "WHERE";  | ||||
|     const SQL_TOKEN = "WHERE"; | ||||
| 
 | ||||
|     public int $order = 50; | ||||
| 
 | ||||
| @ -35,7 +35,7 @@ class Where extends Fragment { | ||||
|         $this->parent = $queryBuilder->where ?? null; | ||||
|     } | ||||
|      | ||||
|     public function add($field, $value, string $operator, string $condition, bool $not = false) : self | ||||
|     public function add($field, mixed $value, string $operator, string $condition, bool $not = false) : self | ||||
|     { | ||||
|         $this->validateFieldType($field); | ||||
|         # $this->validateValueType($value);
 | ||||
| @ -73,7 +73,7 @@ class Where extends Fragment { | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     protected function whereCondition($field, $value, string $operator = self::OPERATOR_EQUAL, string $condition = self::CONDITION_AND, bool $not = false) { | ||||
|     protected function whereCondition($field, mixed $value, string $operator = self::OPERATOR_EQUAL, string $condition = self::CONDITION_AND, bool $not = false) { | ||||
|         return new class($this->queryBuilder, $field, $value, $operator, $condition, $not) { | ||||
| 
 | ||||
|             public mixed $value; | ||||
| @ -123,7 +123,7 @@ class Where extends Fragment { | ||||
|                     $stack = []; | ||||
| 
 | ||||
|                     if ($this->value) { | ||||
|                         foreach ($this->value as $item) { | ||||
|                         foreach($this->value as $item) { | ||||
|                             $stack[] = $this->filterValue($item); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
| @ -198,9 +198,9 @@ class QueryBuilder implements Query\QueryBuilderInterface | ||||
|     public function where(string|\Stringable $field, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self | ||||
|     { | ||||
|         # Empty IN case
 | ||||
|         #if ( [] === $value ) {
 | ||||
|         #    return $this;
 | ||||
|         #}
 | ||||
|         # if ( [] === $value ) {
 | ||||
|         #     return $this;
 | ||||
|         # }
 | ||||
| 
 | ||||
|         if ( $this->where ?? false ) { | ||||
|             $where = $this->where; | ||||
| @ -259,6 +259,11 @@ class QueryBuilder implements Query\QueryBuilderInterface | ||||
|         if ( null === $offset = $this->getFragment(Query\Offset::class) ) { | ||||
|             $offset = new Query\Offset(); | ||||
|             $this->push($offset); | ||||
| 
 | ||||
|             # A limit is required to match an offset
 | ||||
|             if ( null === $limit = $this->getFragment(Query\Limit::class) ) { | ||||
|                 $this->limit(\PHP_INT_MAX); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $offset->set($value); | ||||
|  | ||||
| @ -114,7 +114,7 @@ class Repository | ||||
|             throw new Exception\EntityPrimaryKeyUnknown("A primary key value has to be defined to delete an item."); | ||||
|         } | ||||
| 
 | ||||
|         return (bool) $this->wherePrimaryKey($value)->deleteOne()->rowCount; | ||||
|         return (bool) $this->wherePrimaryKey($value, null)->deleteOne()->rowCount; | ||||
|     } | ||||
| 
 | ||||
|     public function destroy(object $entity) : bool | ||||
| @ -144,6 +144,17 @@ class Repository | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function clone(object|array $entity) : object|false | ||||
|     { | ||||
|         $entity = is_object($entity) ? clone $entity : $entity; | ||||
| 
 | ||||
|         foreach(Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() as $key => $field) { | ||||
|             unset($entity->$key); | ||||
|         } | ||||
| 
 | ||||
|         return $this->save($entity) ? $entity : false; | ||||
|     } | ||||
|      | ||||
|     public function save(object|array $entity, ? array $fieldsAndValue = null, bool $replace = false) : bool | ||||
|     { | ||||
|         if ( is_array($entity) ) { | ||||
| @ -588,7 +599,7 @@ class Repository | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function wherePrimaryKey(mixed $value) : self | ||||
|     public function wherePrimaryKey(mixed $value, null|string|bool $alias = self::DEFAULT_ALIAS) : self | ||||
|     { | ||||
|         if ( null === $primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() ) { | ||||
|             throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'"); | ||||
| @ -596,7 +607,7 @@ class Repository | ||||
| 
 | ||||
|         $pkField = key($primaryKeyField); | ||||
| 
 | ||||
|         return $this->where($this->entityClass::field($primaryKeyField[$pkField]->name ?? $pkField), $value); | ||||
|         return $this->where($this->entityClass::field($primaryKeyField[$pkField]->name ?? $pkField, false), $value); | ||||
|     } | ||||
| 
 | ||||
|     public function withJoin(string|array $fields, array $options = []) : self | ||||
|  | ||||
| @ -47,7 +47,7 @@ class RelationBuilder | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function searchRelation(string $name) : object|bool | ||||
|     public function searchRelation(string $name) : mixed | ||||
|     { | ||||
|         # Resolve relations here if one is called
 | ||||
|         if ( $this->entity->isLoaded() ) { | ||||
| @ -55,7 +55,13 @@ class RelationBuilder | ||||
|                 return $dataset; | ||||
|             } | ||||
| 
 | ||||
|             return $this->resolveRelation($name) ?: $this->resolveVirtual($name) ?: false; | ||||
|             if ( false !== $value = $this->resolveRelation($name) ) { | ||||
|             return $value; | ||||
|             } | ||||
|             elseif (  false !== $value = $this->resolveVirtual($name) ) { | ||||
|                                             return $value; | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|         else { | ||||
|             if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class , Relation::class ] ) ) { | ||||
| @ -69,7 +75,7 @@ class RelationBuilder | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     protected function resolveVirtual(string $name) : bool|object | ||||
|     protected function resolveVirtual(string $name) : mixed | ||||
|     { | ||||
|         if (null !== ($virtual = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Virtual::class, Annotation\Property\Virtual::class ]))) { | ||||
|             if ($virtual->closure ?? false) { | ||||
| @ -82,7 +88,7 @@ class RelationBuilder | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     protected function resolveRelation(string $name) : bool|object | ||||
|     protected function resolveRelation(string $name) : mixed | ||||
|     { | ||||
|         if ( null !== ( $relation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ] ) ) ) { | ||||
|             $this->orders = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\OrderBy::class, OrderBy::class ] ); | ||||
| @ -109,14 +115,14 @@ class RelationBuilder | ||||
| 
 | ||||
|                     $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, 'loadAll' ]); | ||||
|                     $results = call_user_func([ $this->repository, $relationRelation->function() ]); | ||||
| 
 | ||||
|                     if ($relation->bridgeField ?? false) { | ||||
|                         $collection = $relation->bridge::entityCollection(); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user