From c9d9281c63ed3394b1b6e92a9c8280f0ab1a3cca Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Mon, 30 Oct 2023 19:41:40 +0000 Subject: [PATCH 1/7] - Added a new clone() method to Repository() base class --- src/Annotation/Property/Field.php | 2 +- src/Repository.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Annotation/Property/Field.php b/src/Annotation/Property/Field.php index cfbf886..7b8dea0 100644 --- a/src/Annotation/Property/Field.php +++ b/src/Annotation/Property/Field.php @@ -8,7 +8,7 @@ class Field implements \Notes\Annotation { public string $name; - public int $length; + public int|string $length; public int $precision; diff --git a/src/Repository.php b/src/Repository.php index 8c8992d..2e30078 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -144,6 +144,15 @@ class Repository } } + public function clone(object|array $entity) : bool + { + foreach(Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() as $key => $field) { + unset($entity->$key); + } + + return $this->save($entity); + } + public function save(object|array $entity, ? array $fieldsAndValue = null, bool $replace = false) : bool { if ( is_array($entity) ) { From da5d203768fceddb8a6a028d84d8338e9d35ebd2 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 31 Oct 2023 14:17:56 +0000 Subject: [PATCH 2/7] - Small quick fix on espaceIdentifier whichi misses a type --- src/Adapter/SQLite.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Adapter/SQLite.php b/src/Adapter/SQLite.php index 3cae7fb..d200bfb 100644 --- a/src/Adapter/SQLite.php +++ b/src/Adapter/SQLite.php @@ -70,6 +70,7 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface public function escapeIdentifier(string $segment, int $type) : string { switch($type) { + default: case static::IDENTIFIER_DATABASE: case static::IDENTIFIER_TABLE: case static::IDENTIFIER_FIELD: From e641bc321da4bb8c74c4aefaec82ea6b0ccb0f9b Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 31 Oct 2023 14:37:46 +0000 Subject: [PATCH 3/7] - SQLite pragma handling was not applied ; fixed --- src/Adapter/SQLite.php | 38 ++++++++++++++++++++++---------------- src/Common/PdoObject.php | 9 +++++++++ 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/Adapter/SQLite.php b/src/Adapter/SQLite.php index 5a90d55..8eaf659 100644 --- a/src/Adapter/SQLite.php +++ b/src/Adapter/SQLite.php @@ -12,28 +12,18 @@ use Ulmus\{Repository, QueryBuilder, Ulmus}; class SQLite implements AdapterInterface { use DefaultAdapterTrait; + const ALLOWED_ATTRIBUTES = [ 'default', 'primary_key', 'auto_increment' ]; 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 { @@ -43,7 +33,12 @@ class SQLite implements AdapterInterface { $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){ throw $ex; @@ -62,7 +57,8 @@ class SQLite implements AdapterInterface { public function setup(array $configuration) : void { $this->path = $configuration['path'] ?? ""; - $this->pragma = $configuration['pragma'] ?? []; + $this->pragmaBegin = $configuration['pragma_begin'] ?? []; + $this->pragmaClose = $configuration['pragma_close'] ?? []; } # https://sqlite.org/lang_keywords.html @@ -185,6 +181,16 @@ class SQLite implements AdapterInterface { $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); + $pdo->query($builder->pragma($key, $value)->render()); + } + } + public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable { return implode(" ", [ diff --git a/src/Common/PdoObject.php b/src/Common/PdoObject.php index 94e5d76..15f86a2 100644 --- a/src/Common/PdoObject.php +++ b/src/Common/PdoObject.php @@ -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 ]); From 953fc3568040c359cc58b668c6d9f93ea2bab56c Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 31 Oct 2023 16:11:23 +0000 Subject: [PATCH 4/7] - Completed SQLite pragma's missing code --- src/Adapter/SQLite.php | 16 ++++++++++++---- src/Query/Sqlite/Pragma.php | 2 +- src/Repository.php | 6 ++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Adapter/SQLite.php b/src/Adapter/SQLite.php index 4be74f8..f603d7f 100644 --- a/src/Adapter/SQLite.php +++ b/src/Adapter/SQLite.php @@ -57,8 +57,8 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface public function setup(array $configuration) : void { $this->path = $configuration['path'] ?? ""; - $this->pragmaBegin = $configuration['pragma_begin'] ?? []; - $this->pragmaClose = $configuration['pragma_close'] ?? []; + $this->pragmaBegin = array_filter($configuration['pragma_begin'] ?? []); + $this->pragmaClose = array_filter($configuration['pragma_close'] ?? []); } # https://sqlite.org/lang_keywords.html @@ -187,8 +187,16 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface $builder = new QueryBuilder\SqliteQueryBuilder(); foreach($pragmaList as $pragma) { - list($key, $value) = explode('=', $pragma); - $pdo->query($builder->pragma($key, $value)->render()); + 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(); } } diff --git a/src/Query/Sqlite/Pragma.php b/src/Query/Sqlite/Pragma.php index 9c67f33..74b41c9 100644 --- a/src/Query/Sqlite/Pragma.php +++ b/src/Query/Sqlite/Pragma.php @@ -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([ diff --git a/src/Repository.php b/src/Repository.php index 4c5b352..9fc7f2b 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -144,13 +144,15 @@ class Repository } } - public function clone(object|array $entity) : bool + 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); + return $this->save($entity) ? $entity : false; } public function save(object|array $entity, ? array $fieldsAndValue = null, bool $replace = false) : bool From d2bd6c2f588ffd8a41ed082e5725536558ba6c6a Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 31 Oct 2023 16:34:45 +0000 Subject: [PATCH 5/7] - minor fixes --- src/Attribute/Property/Field/ForeignKey.php | 2 +- src/EntityTrait.php | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Attribute/Property/Field/ForeignKey.php b/src/Attribute/Property/Field/ForeignKey.php index 11970a9..0c5deeb 100644 --- a/src/Attribute/Property/Field/ForeignKey.php +++ b/src/Attribute/Property/Field/ForeignKey.php @@ -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 = [ diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 26a8723..26a2d92 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -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; } } @@ -261,10 +261,6 @@ trait EntityTrait { #[Ignore] public function __clone() { - foreach($this as $prop) { - - } - if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) { $key = key($pkField); From 667c1fe9abf12751ab1f55094d69d9043ee80e33 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 31 Oct 2023 18:04:49 +0000 Subject: [PATCH 6/7] - Fixed an alias problem within deleteFromPk() method --- src/EntityTrait.php | 7 +++++-- src/Repository.php | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 26a8723..ae86450 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -306,9 +306,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] diff --git a/src/Repository.php b/src/Repository.php index 9fc7f2b..1dcb8b4 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -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 @@ -599,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'"); @@ -607,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 From fed7d2e302c51aec31eee4a4380d0c244ad1a256 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Wed, 1 Nov 2023 09:55:19 -0400 Subject: [PATCH 7/7] - Some minors adjustments to match PHP 8.x coding styles --- src/EntityTrait.php | 10 ++++------ src/Query/Where.php | 8 ++++---- src/QueryBuilder.php | 6 +++--- src/Repository/RelationBuilder.php | 18 ++++++++++++------ 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/EntityTrait.php b/src/EntityTrait.php index b21efaf..fae60c8 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -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; @@ -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); } } } diff --git a/src/Query/Where.php b/src/Query/Where.php index 6f7e0ec..fd37cc6 100644 --- a/src/Query/Where.php +++ b/src/Query/Where.php @@ -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); } } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 1f43f81..f91e351 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -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; diff --git a/src/Repository/RelationBuilder.php b/src/Repository/RelationBuilder.php index a9cdc39..796da0d 100644 --- a/src/Repository/RelationBuilder.php +++ b/src/Repository/RelationBuilder.php @@ -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();