From 828daffec85d732e7943d2e3ba92bc08e69f1a0f Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Wed, 5 Feb 2020 16:19:57 -0500 Subject: [PATCH] - WIP on insert/update statement --- src/Common/EntityResolver.php | 22 ++++++++- src/Entity/Field/Date.php | 14 ++++++ src/Entity/Field/Datetime.php | 12 +++++ src/Entity/Field/Time.php | 14 ++++++ src/EntityCollection.php | 13 ++++- src/EntityTrait.php | 43 +++++++++++++++-- src/Exception/EntityPrimaryKeyUnknown.php | 5 ++ src/Query/Delete.php | 9 ++-- src/Query/From.php | 14 ++---- src/Query/Insert.php | 29 ++++++++++- src/Query/Values.php | 59 +++++++++++++++++++++++ src/QueryBuilder.php | 59 ++++++++++++++++++++--- src/Repository.php | 54 ++++++++++++++++++++- 13 files changed, 311 insertions(+), 36 deletions(-) create mode 100644 src/Entity/Field/Date.php create mode 100644 src/Entity/Field/Datetime.php create mode 100644 src/Entity/Field/Time.php create mode 100644 src/Exception/EntityPrimaryKeyUnknown.php create mode 100644 src/Query/Values.php diff --git a/src/Common/EntityResolver.php b/src/Common/EntityResolver.php index 45de764..c08f9ce 100644 --- a/src/Common/EntityResolver.php +++ b/src/Common/EntityResolver.php @@ -149,9 +149,27 @@ class EntityResolver { return $table->schema ?? null; } - public function primaryKeys() : array + public function getPrimaryKeyField() : ? array + { + foreach($this->fieldList() as $key => $value) { + if ( null !== ( $field = $this->searchFieldAnnotation($key, new Field() ) ) ) { + if (false !== $field->attributes['primary_key'] ?? false ) { + return [ $key => $field ]; + } + } + } + + return null; + } + + public function getCompoundKeyFields() : ? array { - + return null; + } + + public function getUniqueFields() : ? array + { + return null; } /** diff --git a/src/Entity/Field/Date.php b/src/Entity/Field/Date.php new file mode 100644 index 0000000..25c5d49 --- /dev/null +++ b/src/Entity/Field/Date.php @@ -0,0 +1,14 @@ +format("Y-m-d"); + } + +} diff --git a/src/Entity/Field/Datetime.php b/src/Entity/Field/Datetime.php new file mode 100644 index 0000000..16682e7 --- /dev/null +++ b/src/Entity/Field/Datetime.php @@ -0,0 +1,12 @@ +format("Y-m-d H:i:s"); + } + +} diff --git a/src/Entity/Field/Time.php b/src/Entity/Field/Time.php new file mode 100644 index 0000000..da54393 --- /dev/null +++ b/src/Entity/Field/Time.php @@ -0,0 +1,14 @@ +format("H:i:s"); + } + +} diff --git a/src/EntityCollection.php b/src/EntityCollection.php index aae5cba..1fdfcec 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -34,12 +34,21 @@ class EntityCollection extends \ArrayObject { } } - public function buildArray(string $keyColumn, string $valueColumn) : array + public function buildArray(string $keyColumn, /* string|callable */ $value) : array { + $list = []; foreach($this as $key => $item) { - $list[$item->$keyColumn] = $item->$valueColumn; + switch (true) { + case is_string($value): + $list[$item->$keyColumn] = $item->$value; + break; + + case is_callable($value): + $list[$item->$keyColumn] = $value($item); + break; + } } return $list; diff --git a/src/EntityTrait.php b/src/EntityTrait.php index eeeb1a9..95bb377 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -6,7 +6,7 @@ use Ulmus\Repository, Ulmus\Common\EntityResolver, Ulmus\Common\EntityField; -use Ulmus\Annotation\Classes\{ Method, Table, Collation as Test, }; +use Ulmus\Annotation\Classes\{ Method, Table, Collation, }; use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, }; use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, }; @@ -53,7 +53,7 @@ trait EntityTrait { switch($relation->type) { case 'oneToMany': - $repository->where( $baseEntity->field($relation->foreignKey), 2166); # <<<<<<<<< CHANGE $THIS->ID WITH PROPER NOMENCLATURE + $repository->where( $baseEntity->field($relation->foreignKey), $this->$field); # <<<<<<<<< CHANGE $THIS->ID WITH PROPER NOMENCLATURE return $this->$name = $repository->loadAll(); @@ -71,19 +71,25 @@ trait EntityTrait { return; } + + throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name)); } /** * @Ignore */ - public function entityFillFromDataset($dataset) : self + public function entityFillFromDataset(iterable $dataset) : self { $fields = $this->resolveEntity(); foreach($dataset as $key => $value) { - $key = strtolower($key); + $field = $fields->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null; - if ( null === $field = $fields->field($key, EntityResolver::KEY_COLUMN_NAME, false) ?? null ) { + if ( $field === null ) { + $field = $fields->field($key, EntityResolver::KEY_ENTITY_NAME, false); + } + + if ( $field === null ) { if ($this->strictEntityFieldsDeclaration ) { throw new \Exception("Field `$key` can not be found within your entity ".static::class); } @@ -108,6 +114,11 @@ trait EntityTrait { return $this; } + public function fromArray(iterable $dataset) : self + { + return $this->entityFillFromDataset($dataset); + } + public function entityGetDataset() : array { $fields = array_keys($this->resolveEntity()->fieldList()); @@ -117,6 +128,28 @@ trait EntityTrait { }, $fields)); } + public function toArray() : array + { + return $this->entityGetDataset(); + } + + public function toCollection() : array + { + return new EntityCollection($this->toArray()); + } + + public function isLoaded() : bool + { + if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) { + throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'"); + } + + $key = key($pkField); + + return isset($this->$key); + } + + public function __sleep() { return array_keys($this->resolveEntity()->fieldList()); diff --git a/src/Exception/EntityPrimaryKeyUnknown.php b/src/Exception/EntityPrimaryKeyUnknown.php new file mode 100644 index 0000000..55464d0 --- /dev/null +++ b/src/Exception/EntityPrimaryKeyUnknown.php @@ -0,0 +1,5 @@ +renderSegments([ 'DELETE', - ( $this->alias ?? false ), - ( $this->lowPriority ? 'LOW_PRIORITY' : false ), + ( $this->priority ?? false ), ( $this->quick ? 'QUICK' : false ), ( $this->ignore ? 'IGNORE' : false ), ]); diff --git a/src/Query/From.php b/src/Query/From.php index 6d0220c..3b93a8d 100644 --- a/src/Query/From.php +++ b/src/Query/From.php @@ -2,28 +2,20 @@ namespace Ulmus\Query; -use Ulmus\QueryBuilder; - class From extends Fragment { public int $order = -80; - protected $tables = []; - - public QueryBuilder $queryBuilder; + public array $tables = []; - public function __construct(QueryBuilder $queryBuilder) - { - $this->queryBuilder = $queryBuilder; - } - public function set(array $tables) : self { $this->tables = $tables; + return $this; } - public function add($table) : self + public function add(/* array|string */ $table) : self { foreach((array) $table as $alias => $name) { $this->tables[$alias] = $name; diff --git a/src/Query/Insert.php b/src/Query/Insert.php index 042229c..9c851ab 100644 --- a/src/Query/Insert.php +++ b/src/Query/Insert.php @@ -4,6 +4,33 @@ namespace Ulmus\Query; class Insert extends Fragment { - public bool $ignore = false; + public int $order = -100; + public bool $quick = false; + + public bool $ignore = false; + + public array $fieldlist = []; + + public string $priority; + + public string $table; + + public ? string $alias = null; + + public function render() : string + { + return $this->renderSegments([ + 'INSERT', + ( $this->priority ?? false ), + ( $this->ignore ? 'IGNORE' : false ), + 'INTO', $this->renderTable(), + "(" . implode(',', $this->fieldlist) . ")", + ]); + } + + protected function renderTable() : string + { + return $this->table . ( $this->alias ? " {$this->alias}" : "" ); + } } diff --git a/src/Query/Values.php b/src/Query/Values.php new file mode 100644 index 0000000..a0f9663 --- /dev/null +++ b/src/Query/Values.php @@ -0,0 +1,59 @@ +queryBuilder = $queryBuilder; + } + + public function add(array $row) : self + { + $this->rows[] = $row; + + return $this; + } + + public function render() : string + { + $this->queryBuilder->addParameter($this->flattenRowsArray()); + + return $this->renderSegments([ + 'VALUES', '(' . $this->renderParameterPlaceholders() . ')', + ]); + } + + public function renderParameterPlaceholders() : string + { + $return = []; + $count = null; + + foreach($this->rows as $row) { + if ($count === null) { + $count = count($row); + } + else { + if (count($row) !== $count) { + throw new \Exception("Insert statement must contains the same number of values for each rows."); + } + } + + $return[] = implode(',', array_fill(0, $this->parameterCount, '?')); + } + + return "(" . implode('),(', $return) . ")"; + } + + public function flattenRowsArray() : array + { + return iterator_to_array(new RecursiveIteratorIterator(new RecursiveArrayIterator($this->rows))); + } +} \ No newline at end of file diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index f339516..8b45f5c 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -10,6 +10,12 @@ class QueryBuilder * Those are the parameters we are going to bind to PDO. */ public array $parameters = []; + + /** + * + * Those values are to be inserted or updated + */ + public array $values = []; public string $conditionOperator = Query\Where::CONDITION_AND; @@ -31,13 +37,45 @@ class QueryBuilder return $this; } - public function delete(string $alias = "") : self + public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self + { + if ( ! $this->has(Query\Insert::class) ) { + if ( $database ) { + $table = "\"$database\".$table"; + } + + if ( $schema ) { + $table = "\"$schema\".$table"; + } + + $insert = new Query\Insert(); + $this->push($insert); + + $insert->fieldist = $fieldlist; + $insert->alias = $alias; + $insert->table = $table; + } + + return $this; + } + + public function values(array $dataset) : self + { + + if ( false === ( $values = $this->has(Query\Values::class) ) ) { + $values = new Query\Values(); + $this->push($values); + } + + $values->add($dataset); + + return $this; + } + + public function delete() : self { if ( ! $this->has(Query\Delete::class) ) { - $delete = new Query\Delete(); - - $delete->alias = $alias; - $this->push($delete); + $this->push(new Query\Delete()); } return $this; @@ -45,8 +83,6 @@ class QueryBuilder public function from(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self { - // $table = "\"$table\""; - if ( $database ) { $table = "\"$database\".$table"; } @@ -61,6 +97,7 @@ class QueryBuilder else { $from = new Query\From($this); $this->push($from); + $from->set($alias ? [ $alias => $table ] : $table); } @@ -98,6 +135,7 @@ class QueryBuilder $this->conditionOperator = $operator; $where->add($field, $value, $operator, $condition, $not); + return $this; } @@ -181,7 +219,8 @@ class QueryBuilder return $this->render(); } - public function addParameter($value, $key = null) { + public function addParameter($value, string $key = null) : string + { if ( $key === null ) { $key = ":p" . $this->parameterIndex++; } @@ -189,4 +228,8 @@ class QueryBuilder $this->parameters[$key] = $value; return $key; } + + public function addValues(array $values) { + $this->values = $values; + } } diff --git a/src/Repository.php b/src/Repository.php index b4e75b8..5b93626 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -74,12 +74,23 @@ class Repository public function save(object $entity) : bool { + $dataset = $entity->toArray(); + + if ( ! $entity->isLoaded() ) { + $save = $this->insertSqlQuery($dataset)->runQuery(); + } + else { + $save = $this->updateSqlQuery($dataset)->runQuery(); + } + return false; } public function saveAll(EntityCollection $collection) : bool { - + foreach($collection as $entity) { + $this->save($entity); + } } public function yieldAll() : \Generator @@ -94,6 +105,20 @@ class Repository return $this; } + public function insert(array $fieldlist, string $table, string $alias, ? string $schema) : self + { + $this->queryBuilder->insert($fieldlist, $table, $alias, $schema); + + return $this; + } + + public function values(array $dataset) : self + { + $this->queryBuilder->values($dataset); + + return $this; + } + public function delete() : self { $this->queryBuilder->delete($this->alias); @@ -269,6 +294,13 @@ class Repository return $this; } + public function wherePrimaryKey($value) : self + { + if ( null === $primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() ) { + throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'"); + } + } + protected function collectionFromQuery() : EntityCollection { $class = $this->entityClass; @@ -287,6 +319,26 @@ class Repository return Ulmus::runQuery($this->queryBuilder); } + protected function insertSqlQuery(array $dataset) : self + { + if ( ! $this->queryBuilder->has(Query\Insert::class) ) { + $this->insert(array_keys($dataset)); + } + + $this->values(count($dataset)); + + return $this; + } + + protected function updateSqlQuery(array $dataset) : self + { + if ( ! $this->queryBuilder->has(Query\Update::class) ) { + # $this->insert(); + } + + return $this; + } + protected function selectSqlQuery() : self { if ( ! $this->queryBuilder->has(Query\Select::class) ) {