From 0453e9cbc5efdb6afe0930df3939f23c3da48cb0 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Mon, 2 Aug 2021 15:13:17 +0000 Subject: [PATCH 1/3] - Added the replace keyword --- src/Query/Insert.php | 14 +++++++++----- src/Query/Where.php | 2 +- src/QueryBuilder.php | 5 +++-- src/Repository.php | 41 ++++++++++++++++++++++++++++++++--------- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/Query/Insert.php b/src/Query/Insert.php index 867d488..d5ce00e 100644 --- a/src/Query/Insert.php +++ b/src/Query/Insert.php @@ -4,12 +4,16 @@ namespace Ulmus\Query; class Insert extends Fragment { - const SQL_TOKEN = "INSERT"; - + const SQL_INSERT_TOKEN = "INSERT"; + + const SQL_REPLACE_TOKEN = "REPLACE"; + public int $order = -100; public bool $quick = false; - + + public bool $replace = false; + public bool $ignore = false; public array $fieldlist = []; @@ -19,11 +23,11 @@ class Insert extends Fragment { public string $table; public ? string $alias = null; - + public function render() : string { return $this->renderSegments([ - static::SQL_TOKEN, + $this->replace ? static::SQL_REPLACE_TOKEN : static::SQL_INSERT_TOKEN, ( $this->priority ?? false ), ( $this->ignore ? 'IGNORE' : false ), 'INTO', $this->table, diff --git a/src/Query/Where.php b/src/Query/Where.php index 851ec1d..f9e81db 100644 --- a/src/Query/Where.php +++ b/src/Query/Where.php @@ -114,7 +114,7 @@ class Where extends Fragment { } # whitelisting operators - return in_array(strtoupper($this->operator), [ '=', '!=', '<>', 'LIKE', 'IS', 'IS NOT' ]) ? $this->operator : "="; + return in_array(strtoupper($this->operator), [ '=', '!=', '>', '>=', '<', '<=', '<>', 'LIKE', 'IS', 'IS NOT' ]) ? $this->operator : "="; } protected function value() diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 35742d6..2b9ada3 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -64,7 +64,7 @@ class QueryBuilder implements Query\QueryBuilderInterface return $this; } - public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self + public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null, bool $replace = false) : self { if ( null === $this->getFragment(Query\Insert::class) ) { if ( $schema ) { @@ -77,7 +77,8 @@ class QueryBuilder implements Query\QueryBuilderInterface $insert = new Query\Insert(); $this->push($insert); - + + $insert->replace = $replace; $insert->fieldlist = $fieldlist; $insert->alias = $alias; $insert->table = $table; diff --git a/src/Repository.php b/src/Repository.php index d768547..11cd72d 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -125,8 +125,12 @@ class Repository } } - public function save(object $entity, ? array $fieldsAndValue = null) : bool + public function save(/*object|array*/ $entity, ? array $fieldsAndValue = null, bool $replace = false) : bool { + if ( is_array($entity) ) { + $entity = ( new $this->entityClass() )->fromArray($entity); + } + if ( ! $this->matchEntity($entity) ) { throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`"); } @@ -136,7 +140,7 @@ class Repository $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); if ( ! $entity->isLoaded() ) { - $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset)->runQuery(); + $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runQuery(); if ( ( 0 !== $statement->lastInsertId ) && ( null !== $primaryKeyDefinition )) { @@ -170,6 +174,11 @@ class Repository return false; } + public function replace(/*object|array*/ $entity, ? array $fieldsAndValue = null) : bool + { + return $this->save($entity, $fieldsAndValue, true); + } + public function saveAll(EntityCollection $collection) : void { foreach($collection as $entity) { @@ -177,6 +186,13 @@ class Repository } } + public function replaceAll(EntityCollection $collection) : void + { + foreach($collection as $entity) { + $this->replace($entity); + } + } + public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self { $schema = $schema ?: $this->entityResolver->schemaName(); @@ -195,9 +211,13 @@ class Repository return $this->createSqlQuery()->runQuery(); } - public function generateDatasetDiff(object $entity) : array + public function generateDatasetDiff(object $entity, bool $oldValues = false) : array { - return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(false, true)) ); + $array = array_change_key_case($entity->toArray()); + + $dataset = array_change_key_case($entity->entityGetDataset(false, true)); + + return array_diff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset ); } public function yield() : \Generator @@ -261,9 +281,9 @@ class Repository return $this; } - public function insert(array $fieldlist, string $table, string $alias, ? string $schema) : self + public function insert(array $fieldlist, string $table, string $alias, ? string $schema, bool $replace = false) : self { - $this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $alias, $this->escapedDatabase(), $schema); + $this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $alias, $this->escapedDatabase(), $schema, $replace); return $this; } @@ -604,10 +624,13 @@ class Repository return $this; } - protected function insertSqlQuery(array $dataset) : self + protected function insertSqlQuery(array $dataset, bool $replace = false) : self { - if ( null === $this->queryBuilder->getFragment(Query\Insert::class) ) { - $this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName()); + 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); From 58afe841e7a8a7ea04016f2648e3e7bb6763b485 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Fri, 27 Aug 2021 18:04:36 +0000 Subject: [PATCH 2/3] - Fixed a double negative --- src/Repository/ConditionTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Repository/ConditionTrait.php b/src/Repository/ConditionTrait.php index b904235..89447df 100644 --- a/src/Repository/ConditionTrait.php +++ b/src/Repository/ConditionTrait.php @@ -53,7 +53,7 @@ trait ConditionTrait return $this; } - public function notWhere($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self + public function notWhere($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self { $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND, true); From dc5e0885512a39387b59e8a2af37c87ff0269931 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 19 Oct 2021 12:41:04 +0000 Subject: [PATCH 3/3] - Some bugfixes were made, added a new diff before saving results. Also, a new method was added for insert, update and delete respectively for other drivers to override as needed --- src/Annotation/Property/Field.php | 4 ++- src/Common/EntityResolver.php | 14 ++++---- src/Common/PdoObject.php | 17 ++++++++- src/EntityTrait.php | 23 ++++++------ src/Repository.php | 58 +++++++++++++++++++++++++----- src/Repository/RelationBuilder.php | 6 ++-- src/Ulmus.php | 18 ++++++++++ 7 files changed, 111 insertions(+), 29 deletions(-) diff --git a/src/Annotation/Property/Field.php b/src/Annotation/Property/Field.php index 25d1d93..30e9519 100644 --- a/src/Annotation/Property/Field.php +++ b/src/Annotation/Property/Field.php @@ -15,7 +15,9 @@ class Field implements \Ulmus\Annotation\Annotation { public array $attributes = []; public bool $nullable; - + + public bool $readonly = false; + public function __construct(? string $type = null, ? int $length = null) { if ( $type !== null ) { diff --git a/src/Common/EntityResolver.php b/src/Common/EntityResolver.php index 9936711..8aef889 100644 --- a/src/Common/EntityResolver.php +++ b/src/Common/EntityResolver.php @@ -125,18 +125,20 @@ class EntityResolver { return null; } - public function searchFieldAnnotation(string $field, Annotation $annotationType) : ? Annotation + public function searchFieldAnnotation(string $field, Annotation $annotationType, bool $caseSensitive = true) : ? Annotation { - $found = $this->searchFieldAnnotationList($field, $annotationType); + $found = $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive); return $found ? $found[0] : null; } - public function searchFieldAnnotationList(string $field, Annotation $annotationType) : array + public function searchFieldAnnotationList(string $field, Annotation $annotationType, bool $caseSensitive = true) : array { $list = []; - - if ( null !== ( $this->properties[$field] ?? null ) ) { - foreach($this->properties[$field]['tags'] ?? [] as $tag) { + + $search = $caseSensitive ? $this->properties : array_change_key_case($this->properties, \CASE_LOWER); + + if ( null !== ( $search[$field] ?? null ) ) { + foreach($search[$field]['tags'] ?? [] as $tag) { if ( $tag['object'] instanceof $annotationType ) { $list[] = $tag['object']; } diff --git a/src/Common/PdoObject.php b/src/Common/PdoObject.php index 22197b6..462c1a8 100644 --- a/src/Common/PdoObject.php +++ b/src/Common/PdoObject.php @@ -29,7 +29,7 @@ class PdoObject extends PDO { public function runQuery(string $sql, array $parameters = []): ? PDOStatement { static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]); - + # \debogueur([ $sql, $parameters ]); try { if (false !== ( $statement = $this->prepare($sql) )) { return $this->execute($statement, $parameters, true); @@ -42,6 +42,21 @@ class PdoObject extends PDO { return null; } + public function runInsertQuery(array $filter, array $dataset) + { + return $this->runQuery($filter, $dataset); + } + + public function runUpdateQuery(array $filter, array $dataset) + { + return $this->runQuery($filter, $dataset); + } + + public function runDeleteQuery(string $sql, array $parameters = []): ? PDOStatement + { + return $this->runQuery($sql, $parameters); + } + public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true): ? PDOStatement { try { diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 475fd6d..23f93fe 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -62,19 +62,19 @@ trait EntityTrait { /** * @Ignore */ - public function entityFillFromDataset(iterable $dataset) : self + public function entityFillFromDataset(iterable $dataset, bool $overwriteDataset = false) : self { $loaded = $this->isLoaded(); - + $entityResolver = $this->resolveEntity(); - + foreach($dataset as $key => $value) { $field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null; - + if ( $field === null ) { $field = $entityResolver->field(strtolower($key), EntityResolver::KEY_LC_ENTITY_NAME, false); } - + if ( $field === null ) { if ($this->entityStrictFieldsDeclaration ) { throw new \Exception("Field `$key` can not be found within your entity ".static::class); @@ -98,9 +98,9 @@ trait EntityTrait { if ( $field['type'] === 'string' ) { $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() ); - + if ( $annotation->length ?? null ) { - $value = substr($value, 0, $annotation->length); + $value = mb_substr($value, 0, $annotation->length); } } elseif ( $field['type'] === 'bool' ) { @@ -112,17 +112,20 @@ trait EntityTrait { elseif ( ! $field['builtin'] ) { $this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]); } - + # Keeping original data to diff on UPDATE query - if ( ! $loaded || $isLoadedDataset ) { + if ( ! $loaded ) { #if ( $field !== null ) { # $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() ); # $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!! #} $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER); } + elseif ($overwriteDataset) { + $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset; + } } - + return $this; } diff --git a/src/Repository.php b/src/Repository.php index 11cd72d..5c6067f 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -56,6 +56,11 @@ class Repository return $this->collectionFromQuery(); } + public function loadAllFromField($field, $value) : EntityCollection + { + return $this->loadFromField($field, $value); + } + public function loadFromField($field, $value) : EntityCollection { return $this->where($field, $value)->collectionFromQuery(); @@ -81,12 +86,12 @@ class Repository public function deleteOne() { - return $this->limit(1)->deleteSqlQuery()->runQuery(); + return $this->limit(1)->deleteSqlQuery()->runDeleteQuery(); } public function deleteAll() { - return $this->deleteSqlQuery()->runQuery(); + return $this->deleteSqlQuery()->runDeleteQuery(); } public function deleteFromPk($value) : bool @@ -140,7 +145,7 @@ class Repository $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); if ( ! $entity->isLoaded() ) { - $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runQuery(); + $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery(); if ( ( 0 !== $statement->lastInsertId ) && ( null !== $primaryKeyDefinition )) { @@ -156,16 +161,16 @@ class Repository throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass)); } - $diff = $fieldsAndValue ?? $this->generateDatasetDiff($entity); + $diff = $fieldsAndValue ?? $this->generateWritableDataset($entity); if ( [] !== $diff ) { $pkField = key($primaryKeyDefinition); $pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField; $this->where($pkFieldName, $dataset[$pkFieldName]); - $update = $this->updateSqlQuery($diff)->runQuery(); + $update = $this->updateSqlQuery($diff)->runUpdateQuery(); - $entity->entityFillFromDataset($dataset); + $entity->entityFillFromDataset($dataset, true); return $update ? (bool) $update->rowCount() : false; } @@ -220,6 +225,22 @@ class Repository return array_diff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset ); } + + public function generateWritableDataset(object $entity, bool $oldValues = false) : array + { + $intersect = []; + + $dataset = $this->generateDatasetDiff($entity, $oldValues); + + foreach($dataset as $field => $value) { + if ( false === ( $this->entityResolver->searchFieldAnnotation($field, new Field, false)->readonly ?? false ) ) { + $intersect[$field] = $field; + } + } + + return array_intersect_key($dataset, $intersect); + } + public function yield() : \Generator { $class = $this->entityClass; @@ -482,7 +503,7 @@ class Repository } foreach($this->entityResolver->searchFieldAnnotationList($item, new Filter() ) as $filter) { - call_user_func_array([ $this->entityClass, $filter->method ], [ $this, $item ]); + call_user_func_array([ $this->entityClass, $filter->method ], [ $this, $item, true ]); } $this->close(); @@ -508,7 +529,7 @@ class Repository } foreach($this->entityResolver->searchFieldAnnotationList($item, new FilterJoin() ) as $filter) { - call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item ]); + call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item, true ]); } }); } @@ -617,6 +638,27 @@ class Repository return Ulmus::runQuery($this->queryBuilder, $this->adapter); } + public function runInsertQuery() /* : mixed */ + { + $this->finalizeQuery(); + + return Ulmus::runInsertQuery($this->queryBuilder, $this->adapter); + } + + public function runUpdateQuery() /* : mixed */ + { + $this->finalizeQuery(); + + return Ulmus::runUpdateQuery($this->queryBuilder, $this->adapter); + } + + public function runDeleteQuery() /* : mixed */ + { + $this->finalizeQuery(); + + return Ulmus::runDeleteQuery($this->queryBuilder, $this->adapter); + } + public function resetQuery() : self { $this->queryBuilder->reset(); diff --git a/src/Repository/RelationBuilder.php b/src/Repository/RelationBuilder.php index 6df3d15..2d5470c 100644 --- a/src/Repository/RelationBuilder.php +++ b/src/Repository/RelationBuilder.php @@ -106,7 +106,7 @@ class RelationBuilder protected function applyFilter(Repository $repository, string $name) : Repository { foreach($this->filters ?? [] as $filter) { - $repository = call_user_func_array([ $this->entity, $filter->method ], [ $repository, $name ]); + $repository = call_user_func_array([ $this->entity, $filter->method ], [ $repository, $name, false ]); } return $repository; @@ -156,7 +156,7 @@ class RelationBuilder } foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) { - if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, $name) ) { + if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, strtolower($name)) ) { if ($value) { if ( null === ( $dataset = \json_decode($value, true) ) ) { throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value)); @@ -168,7 +168,7 @@ class RelationBuilder return $entity::entityCollection(); } } - elseif ( substr($key, 0, $len ) === sprintf(static::JOIN_FIELD_SEPARATOR, $name) ) { + elseif ( substr($key, 0, $len ) === sprintf(static::JOIN_FIELD_SEPARATOR, strtolower($name)) ) { $vars[substr($key, $len)] = $value; } } diff --git a/src/Ulmus.php b/src/Ulmus.php index 9636c2b..07fa13c 100644 --- a/src/Ulmus.php +++ b/src/Ulmus.php @@ -79,6 +79,24 @@ abstract class Ulmus return $return; } + public static function runInsertQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null) + { + return static::runQuery($queryBuilder, $adapter); + } + + public static function runUpdateQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null) + { + return static::runQuery($queryBuilder, $adapter); + } + + public static function runDeleteQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null) + { + $return = static::connector($adapter)->runDeleteQuery($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? [])); + $queryBuilder->reset(); + + return $return; + } + public static function resolveEntity(string $entityClass) : Common\EntityResolver { return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass);