- 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
This commit is contained in:
Dave Mc Nicoll 2020-02-07 16:36:38 -05:00
parent 3dc82b1f0d
commit 2f43fb4559
13 changed files with 169 additions and 58 deletions

View File

@ -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();
}

View File

@ -0,0 +1,8 @@
<?php
namespace Ulmus\Entity;
interface EntityObjectInterface {
public function load(...$arguments);
public function save();
}

View File

@ -2,13 +2,11 @@
namespace Ulmus\Entity\Field;
use DateTime;
class Date extends DateTime {
class Date extends Datetime {
public function __toString()
{
return $this->format("Y-m-d");
}
}

View File

@ -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");
}
}
}

View File

@ -2,9 +2,7 @@
namespace Ulmus\Entity\Field;
use DateTime;
class Time extends DateTime {
class Time extends Datetime {
public function __toString()
{

View File

@ -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;

View File

@ -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;

View File

@ -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 ),

View File

@ -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)
]);
}
}

View File

@ -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)
]);
}

View File

@ -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();

View File

@ -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( "<pre>", $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 {}
}

View File

@ -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
{