From 2f43fb455908d9f7c86a3f4d1142ac2b534839aa Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Fri, 7 Feb 2020 16:36:38 -0500 Subject: [PATCH] - More work done on Update, Insert and Delete clause - Added TOP property to select and delete to fit MSSQL limit's term. - Added a new interface allowing object's manipulation on save() and load() queries --- src/Common/PdoObject.php | 6 ++- src/Entity/EntityObjectInterface.php | 8 ++++ src/Entity/Field/Date.php | 6 +-- src/Entity/Field/Datetime.php | 23 +++++++++- src/Entity/Field/Time.php | 4 +- src/Entity/ObjectInstanciator.php | 12 +++++ src/EntityTrait.php | 35 ++++++++++++--- src/Query/Delete.php | 3 ++ src/Query/Limit.php | 7 ++- src/Query/Select.php | 6 +-- src/QueryBuilder.php | 45 ++++++++++++------- src/Repository.php | 67 ++++++++++++++++++++-------- src/Ulmus.php | 5 +++ 13 files changed, 169 insertions(+), 58 deletions(-) create mode 100644 src/Entity/EntityObjectInterface.php diff --git a/src/Common/PdoObject.php b/src/Common/PdoObject.php index eeb39fd..84c04f6 100644 --- a/src/Common/PdoObject.php +++ b/src/Common/PdoObject.php @@ -8,6 +8,8 @@ use PDO, class PdoObject extends PDO { public function select(string $sql, array $parameters = []): PDOStatement { + # var_dump($sql, $parameters); die(); + try { if (false !== ( $statement = $this->prepare($sql) )) { $statement = $this->execute($statement, $parameters, false); @@ -21,6 +23,8 @@ class PdoObject extends PDO { } public function runQuery(string $sql, array $parameters = []): PDOStatement { + var_dump($sql, $parameters); die(); + try { if (false !== ( $statement = $this->prepare($sql) )) { return $this->execute($statement, $parameters, true); @@ -32,7 +36,7 @@ class PdoObject extends PDO { public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true): ?PDOStatement { try { - if (!$this->inTransaction()) { + if ( ! $this->inTransaction() ) { $this->beginTransaction(); } diff --git a/src/Entity/EntityObjectInterface.php b/src/Entity/EntityObjectInterface.php new file mode 100644 index 0000000..71bc84b --- /dev/null +++ b/src/Entity/EntityObjectInterface.php @@ -0,0 +1,8 @@ +format("Y-m-d"); } - + } diff --git a/src/Entity/Field/Datetime.php b/src/Entity/Field/Datetime.php index 16682e7..a40fbeb 100644 --- a/src/Entity/Field/Datetime.php +++ b/src/Entity/Field/Datetime.php @@ -2,11 +2,30 @@ namespace Ulmus\Entity\Field; -class Datetime extends \DateTime { +use Ulmus\Entity\EntityObjectInterface; + +class Datetime extends \DateTime implements EntityObjectInterface { + + public function load(...$arguments) + { + $value = $arguments[0]; + + # From Timestamp + if ( is_numeric($value) ) { + return new static("@$value"); + } + + return new static($value); + } + + public function save() + { + return $this->getTimestamp(); + } public function __toString() { return $this->format("Y-m-d H:i:s"); } -} +} \ No newline at end of file diff --git a/src/Entity/Field/Time.php b/src/Entity/Field/Time.php index da54393..ecbdd11 100644 --- a/src/Entity/Field/Time.php +++ b/src/Entity/Field/Time.php @@ -2,9 +2,7 @@ namespace Ulmus\Entity\Field; -use DateTime; - -class Time extends DateTime { +class Time extends Datetime { public function __toString() { diff --git a/src/Entity/ObjectInstanciator.php b/src/Entity/ObjectInstanciator.php index 94e00aa..14dc105 100644 --- a/src/Entity/ObjectInstanciator.php +++ b/src/Entity/ObjectInstanciator.php @@ -11,11 +11,23 @@ class ObjectInstanciator { if ( isset($this->objectCallbackDefinition[$type]) ) { return $this->objectCallbackDefinition[$type](...$arguments); } + elseif ( ($obj = new $type() ) instanceof EntityObjectInterface ) { + return $obj->load(...$arguments); + } else { return new $type(...$arguments); } } + public function convert(object $obj) + { + if ( $obj instanceof EntityObjectInterface ) { + return $obj->save(); + } + + return (string) $obj; + } + public function registerObject(string $type, Callable $callback) : void { $this->objectCallbackDefinition[$type] = $callback; diff --git a/src/EntityTrait.php b/src/EntityTrait.php index c91928f..b842cc9 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -22,12 +22,17 @@ trait EntityTrait { */ protected array $unmatchedEntityDatasetFields = []; + /** + * @Ignore + */ + public array $datasetSource = []; + /** * @Ignore */ public function __get(string $name) { - $entityResolver= $this->resolveEntity(); + $entityResolver = $this->resolveEntity(); # Resolve relations here if one is called if ( null !== ( $relation = $entityResolver->searchFieldAnnotation($name, new Relation() ) ) ) { @@ -80,13 +85,15 @@ trait EntityTrait { */ public function entityFillFromDataset(iterable $dataset) : self { - $fields = $this->resolveEntity(); + $loaded = $this->isLoaded(); + + $entityResolver = $this->resolveEntity(); foreach($dataset as $key => $value) { - $field = $fields->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null; + $field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null; if ( $field === null ) { - $field = $fields->field($key, EntityResolver::KEY_ENTITY_NAME, false); + $field = $entityResolver->field($key, EntityResolver::KEY_ENTITY_NAME, false); } if ( $field === null ) { @@ -109,6 +116,15 @@ trait EntityTrait { elseif ( ! $field['builtin'] ) { $this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]); } + + # Keeping original data to diff on UPDATE query + if ( ! $loaded ) { + #if ( $field !== null ) { + # $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() ); + # $this->datasetSource[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!! + #} + $this->datasetSource = array_change_key_case($dataset, \CASE_LOWER); + } } return $this; @@ -119,16 +135,21 @@ trait EntityTrait { return $this->entityFillFromDataset($dataset); } - public function entityGetDataset() : array - { + public function entityGetDataset(bool $returnSource = false) : array + { + if ( $returnSource ) { + return $this->datasetSource; + } + $dataset = []; + $entityResolver = $this->resolveEntity(); foreach($entityResolver->fieldList() as $key => $field) { $annotation = $entityResolver->searchFieldAnnotation($key, new Field() ); if ( isset($this->$key) ) { - $dataset[ $annotation->name ?? $key ] = $this->$key; + $dataset[ $annotation->name ?? $key ] = is_object($this->$key) ? Ulmus::convertObject($this->$key) : $this->$key; } elseif ( $field['nullable'] ) { $dataset[ $annotation->name ?? $key ] = null; diff --git a/src/Query/Delete.php b/src/Query/Delete.php index 042a268..8c3fd23 100644 --- a/src/Query/Delete.php +++ b/src/Query/Delete.php @@ -10,12 +10,15 @@ class Delete extends Fragment { public bool $ignore = false; + public ? int $top = null; + public string $priority; public function render() : string { return $this->renderSegments([ 'DELETE', + ( $this->top ? sprintf('TOP (%s)', $this->top) : false ), ( $this->priority ?? false ), ( $this->quick ? 'QUICK' : false ), ( $this->ignore ? 'IGNORE' : false ), diff --git a/src/Query/Limit.php b/src/Query/Limit.php index 4f8a011..c855097 100644 --- a/src/Query/Limit.php +++ b/src/Query/Limit.php @@ -6,18 +6,21 @@ class Limit extends Fragment { public int $order = 80; - protected int $limit = 0; + public int $limit = 0; + + public string $keyword = "LIMIT %d"; public function set($limit) : self { $this->limit = $limit; + return $this; } public function render() : string { return $this->renderSegments([ - 'LIMIT', $this->limit, + sprintf($this->keyword, $this->limit) ]); } } diff --git a/src/Query/Select.php b/src/Query/Select.php index ecd4bd7..5846d5f 100644 --- a/src/Query/Select.php +++ b/src/Query/Select.php @@ -10,9 +10,9 @@ class Select extends Fragment { public bool $union = false; - public bool $top = false; + public ? int $top = null; - protected $fields = []; + protected array $fields = []; public function set($fields) : self { @@ -37,7 +37,7 @@ class Select extends Fragment { return $this->renderSegments([ ( $this->union ? 'UNION' : false ), 'SELECT', - ( $this->top ? 'TOP' : false ), + ( $this->top ? sprintf('TOP (%s)', $this->top) : false ), implode(', ', $this->fields) ]); } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 94cbc58..89efd76 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -25,7 +25,7 @@ class QueryBuilder public function select($field) : self { - if ( false !== ( $select = $this->has(Query\Select::class) ) ) { + if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) { $select->add($field); } else { @@ -39,7 +39,7 @@ class QueryBuilder public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self { - if ( ! $this->has(Query\Insert::class) ) { + if ( null === $this->getFragment(Query\Insert::class) ) { if ( $database ) { $table = "\"$database\".$table"; } @@ -61,8 +61,7 @@ class QueryBuilder public function values(array $dataset) : self { - - if ( false === ( $values = $this->has(Query\Values::class) ) ) { + if ( null === ( $values = $this->getFragment(Query\Values::class) ) ) { $values = new Query\Values($this); $this->push($values); } @@ -74,7 +73,7 @@ class QueryBuilder public function update(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self { - if ( ! $this->has(Query\Update::class) ) { + if ( ! $this->getFragment(Query\Update::class) ) { if ( $database ) { $table = "\"$database\".$table"; } @@ -96,7 +95,7 @@ class QueryBuilder public function set(array $dataset) : self { - if ( false === ( $values = $this->has(Query\Set::class) ) ) { + if ( null === ( $values = $this->getFragment(Query\Set::class) ) ) { $set = new Query\Set($this); $this->push($set); } @@ -108,7 +107,7 @@ class QueryBuilder public function delete() : self { - if ( ! $this->has(Query\Delete::class) ) { + if ( ! $this->getFragment(Query\Delete::class) ) { $this->push(new Query\Delete()); } @@ -125,14 +124,14 @@ class QueryBuilder $table = "\"$schema\".$table"; } - if ( false !== ( $from = $this->has(Query\From::class) ) ) { + if ( null !== ( $from = $this->getFragment(Query\From::class) ) ) { $from->add($alias ? [ $alias => $table ] : $table); } else { $from = new Query\From($this); $this->push($from); - $from->set($alias ? [ $alias => $table ] : $table); + $from->set($alias ? [ $alias => $table ] : [ $table ]); } return $this; @@ -140,7 +139,7 @@ class QueryBuilder public function open(string $condition = Query\Where::CONDITION_AND) : self { - if ( false !== ($this->where ?? false) ) { + if ( null !== ($this->where ?? false) ) { $this->where->conditionList[] = $new = new Query\Where($this, $condition); $this->where = $new; } @@ -150,7 +149,7 @@ class QueryBuilder public function close() : self { - if ( false !== ($this->where ?? false) && $this->where->parent ) { + if ( null !== ($this->where ?? false) && $this->where->parent ) { $this->where = $this->where->parent; } @@ -162,7 +161,7 @@ class QueryBuilder if ( $this->where ?? false ) { $where = $this->where; } - elseif ( false === ( $where = $this->has(Query\Where::class) ) ) { + elseif ( null === ( $where = $this->getFragment(Query\Where::class) ) ) { $this->where = $where = new Query\Where($this); $this->push($where); } @@ -186,29 +185,31 @@ class QueryBuilder public function limit(int $value) : self { - if ( false === $limit = $this->has(Query\Limit::class) ) { + if ( null === $limit = $this->getFragment(Query\Limit::class) ) { $limit = new Query\Limit(); $this->push($limit); } $limit->set($value); + return $this; } public function offset(int $value) : self { - if ( false === $offset = $this->has(Query\Offset::class) ) { + if ( null === $offset = $this->getFragment(Query\Offset::class) ) { $offset = new Query\Offset(); $this->push($offset); } $offset->set($value); + return $this; } public function orderBy(string $field, ? string $direction = null) : self { - if ( false === $orderBy = $this->has(Query\OrderBy::class) ) { + if ( null === $orderBy = $this->getFragment(Query\OrderBy::class) ) { $orderBy = new Query\OrderBy(); $this->push($orderBy); } @@ -240,16 +241,26 @@ class QueryBuilder return implode(" ", $sql); } - public function has($class) { + public function getFragment(string $class) : ? Query\Fragment + { foreach($this->queryStack as $item) { if ( get_class($item) === $class ) { return $item; } } - return false; + return null; } + public function removeFragment(Query\Fragment $fragment) : void + { + foreach($this->queryStack as $key => $item) { + if ( $item === $fragment ) { + unset($this->queryStack[$key]); + } + } + } + public function __toString() : string { return $this->render(); diff --git a/src/Repository.php b/src/Repository.php index 9004f54..a124993 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -48,9 +48,11 @@ class Repository public function count() : int { - $this->select("count(*) as totalItem"); + $this->select("count(*) as totalItem")->selectSqlQuery(); + + $this->finalizeQuery(); - foreach(Ulmus::iterateQueryBuilder($this->selectSqlQuery()->queryBuilder) as $entityData) { + foreach(Ulmus::iterateQueryBuilder($this->queryBuilder) as $entityData) { return $entityData['totalItem']; } @@ -58,7 +60,7 @@ class Repository } public function deleteOne() - { + { return $this->limit(1)->deleteSqlQuery()->runQuery(); } @@ -67,14 +69,19 @@ class Repository return $this->deleteSqlQuery()->runQuery(); } - public function deleteFromPk($value, $primaryKey = "id") + public function deleteFromPk($value) : bool { - return $this->where($primaryKey, $value)->deleteOne(); + if ( $value !== 0 && empty($value) ) { + throw new Exception\EntityPrimaryKeyUnknown("A primary key value has to be defined to delete an item."); + } + + return (bool) $this->wherePrimaryKey($value)->deleteOne()->rowCount(); } public function save(object $entity) : bool { $dataset = $entity->toArray(); + $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); if ( ! $entity->isLoaded() ) { @@ -85,18 +92,27 @@ class Repository $pkField = key($primaryKeyDefinition); $entity->$pkField = $statement->lastInsertId; } + + return true; } else { - if ($primaryKeyDefinition === null) { + if ( $primaryKeyDefinition === null ) { throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass)); } $pkField = key($primaryKeyDefinition); $pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField; $this->where($pkFieldName, $dataset[$pkFieldName]); - - # DATASET MUST BE DIFF OF MODIF ONLY ! - $update = $this->updateSqlQuery($dataset)->runQuery(); + + $diff = array_diff_assoc($dataset, $entity->entityGetDataset(true)); + + var_dump( "
", $diff, $dataset, $entity->entityGetDataset(true) );
+            
+            if ( [] !== $diff ) {
+                $update = $this->updateSqlQuery($diff)->runQuery();
+                
+                return (bool) $update->rowCount();
+            }   
         }
         
         return false;
@@ -156,7 +172,7 @@ class Repository
         return $this;
     }
 
-    public function from(string $table, string $alias, ? string $schema) : self
+    public function from(string $table, ? string $alias, ? string $schema) : self
     {
         $this->queryBuilder->from($table, $alias, null, $schema);
         
@@ -296,7 +312,7 @@ class Repository
         return $this;
     }
 
-    public function orderBy(string $field, ? string $direction = null) : self
+    public function orderBy($field, ? string $direction = null) : self
     {
         $this->queryBuilder->orderBy($field, $direction);
         
@@ -332,15 +348,24 @@ class Repository
         if ( null === $primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() ) {
             throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'");
         }
+        
+        $pkField = key($primaryKeyField);
+        
+        return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value);
     }
     
     protected function collectionFromQuery() : EntityCollection
     {
+        
         $class = $this->entityClass;
 
         $entityCollection = new EntityCollection();
+        
+        $this->selectSqlQuery();
+        
+        $this->finalizeQuery();
 
-        foreach(Ulmus::iterateQueryBuilder($this->selectSqlQuery()->queryBuilder) as $entityData) {
+        foreach(Ulmus::iterateQueryBuilder($this->queryBuilder) as $entityData) {
             $entityCollection->append( ( new $class() )->entityFillFromDataset($entityData) );
         }
 
@@ -349,12 +374,14 @@ class Repository
 
     public function runQuery() : \PDOStatement
     {
+        $this->finalizeQuery();
+        
         return Ulmus::runQuery($this->queryBuilder);
     }
     
     protected function insertSqlQuery(array $dataset) : self
     {
-        if ( ! $this->queryBuilder->has(Query\Insert::class) ) {
+        if ( null === $this->queryBuilder->getFragment(Query\Insert::class) ) {
             $this->insert(array_keys($dataset), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
         }
 
@@ -365,7 +392,7 @@ class Repository
     
     protected function updateSqlQuery(array $dataset) : self
     {
-        if ( ! $this->queryBuilder->has(Query\Update::class) ) {
+        if ( null === $this->queryBuilder->getFragment(Query\Update::class) ) {
             $this->update($dataset, $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
         }
         
@@ -376,11 +403,11 @@ class Repository
     
     protected function selectSqlQuery() : self
     {
-        if ( ! $this->queryBuilder->has(Query\Select::class) ) {
+        if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
             $this->select("{$this->alias}.*");
         }
 
-        if ( ! $this->queryBuilder->has(Query\From::class) ) {
+        if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
             $this->from($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
         }
 
@@ -389,12 +416,12 @@ class Repository
 
     protected function deleteSqlQuery() : self
     {
-        if ( ! $this->queryBuilder->has(Query\Delete::class) ) {
+        if ( null === $this->queryBuilder->getFragment(Query\Delete::class) ) {
             $this->delete();
         }
 
-        if ( ! $this->queryBuilder->has(Query\From::class) ) {
-            $this->from($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
+        if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
+            $this->from($this->entityResolver->tableName(), null, $this->entityResolver->schemaName());
         }
 
         return $this;
@@ -414,4 +441,6 @@ class Repository
     {
         return "REFLECT TABLE";
     }
+    
+    protected function finalizeQuery() : void {}
 }
diff --git a/src/Ulmus.php b/src/Ulmus.php
index 2132cae..f80b4fe 100644
--- a/src/Ulmus.php
+++ b/src/Ulmus.php
@@ -61,6 +61,11 @@ abstract class Ulmus
     {
         return ( static::$objectInstanciator ?? static::$objectInstanciator = new Entity\ObjectInstanciator() )->instanciate($type, $arguments);
     }
+        
+    public static function convertObject(object $obj)
+    {
+        return ( static::$objectInstanciator ?? static::$objectInstanciator = new Entity\ObjectInstanciator() )->convert($obj);
+    }
     
     protected static function fetchQueryBuilder(QueryBuilder $queryBuilder, ?ConnectionAdapter $adapter = null) : array
     {