From 70021d829dba25f719844fe500b25910200700b0 Mon Sep 17 00:00:00 2001
From: Dave Mc Nicoll <dave.mcnicoll@cslsj.qc.ca>
Date: Thu, 26 Jan 2023 18:47:53 +0000
Subject: [PATCH] - WIP on attributes

---
 src/Attribute/Obj/Table.php                |  2 +-
 src/Attribute/Property/Field/Bit.php       | 18 ++++++++++++
 src/Attribute/Property/Field/Decimal.php   | 18 ++++++++++++
 src/Attribute/Property/Field/Float.php     | 18 ++++++++++++
 src/Attribute/Property/Field/Mediumint.php | 18 ++++++++++++
 src/Attribute/Property/Field/Numeric.php   | 18 ++++++++++++
 src/Attribute/Property/Field/Smallint.php  | 18 ++++++++++++
 src/Attribute/Property/Field/Timestamp.php | 12 ++++++++
 src/Attribute/Property/Field/Tinytext.php  | 18 ++++++++++++
 src/Attribute/Property/Join.php            |  2 +-
 src/QueryBuilder.php                       | 16 +++++------
 src/Repository.php                         | 20 +++++++-------
 src/Repository/ConditionTrait.php          | 32 +++++++++++-----------
 13 files changed, 174 insertions(+), 36 deletions(-)
 create mode 100644 src/Attribute/Property/Field/Bit.php
 create mode 100644 src/Attribute/Property/Field/Decimal.php
 create mode 100644 src/Attribute/Property/Field/Float.php
 create mode 100644 src/Attribute/Property/Field/Mediumint.php
 create mode 100644 src/Attribute/Property/Field/Numeric.php
 create mode 100644 src/Attribute/Property/Field/Smallint.php
 create mode 100644 src/Attribute/Property/Field/Timestamp.php
 create mode 100644 src/Attribute/Property/Field/Tinytext.php

diff --git a/src/Attribute/Obj/Table.php b/src/Attribute/Obj/Table.php
index 0cb734a..4c8e1b2 100644
--- a/src/Attribute/Obj/Table.php
+++ b/src/Attribute/Obj/Table.php
@@ -5,7 +5,7 @@ namespace Ulmus\Attribute\Obj;
 #[\Attribute(\Attribute::TARGET_CLASS)]
 class Table {
     public function __construct(
-        public string $name,
+        public ? string $name = null,
         public ? string $database = null,
         public ? string $schema = null,
         public ? string $adapter = null,
diff --git a/src/Attribute/Property/Field/Bit.php b/src/Attribute/Property/Field/Bit.php
new file mode 100644
index 0000000..5b94424
--- /dev/null
+++ b/src/Attribute/Property/Field/Bit.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Ulmus\Attribute\Property\Field;
+
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
+class Bit extends \Ulmus\Attribute\Property\Field
+{
+    public function __construct(
+        public ? string $name = null,
+        public ? string $type = "bit",
+        public ? int $length = null,
+        public ? int $precision = null,
+        public array $attributes = [],
+        public bool $nullable = false,
+        public mixed $default = null,
+        public bool $readonly = false,
+    ) {}
+}
\ No newline at end of file
diff --git a/src/Attribute/Property/Field/Decimal.php b/src/Attribute/Property/Field/Decimal.php
new file mode 100644
index 0000000..de6f285
--- /dev/null
+++ b/src/Attribute/Property/Field/Decimal.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Ulmus\Attribute\Property\Field;
+
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
+class Decimal extends \Ulmus\Attribute\Property\Field
+{
+    public function __construct(
+        public ? string $name = null,
+        public ? string $type = "decimal",
+        public ? int $length = null,
+        public ? int $precision = null,
+        public array $attributes = [],
+        public bool $nullable = false,
+        public mixed $default = null,
+        public bool $readonly = false,
+    ) {}
+}
\ No newline at end of file
diff --git a/src/Attribute/Property/Field/Float.php b/src/Attribute/Property/Field/Float.php
new file mode 100644
index 0000000..ed56102
--- /dev/null
+++ b/src/Attribute/Property/Field/Float.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Ulmus\Attribute\Property\Field;
+
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
+class Float extends \Ulmus\Attribute\Property\Field
+{
+    public function __construct(
+        public ? string $name = null,
+        public ? string $type = "float",
+        public ? int $length = null,
+        public ? int $precision = null,
+        public array $attributes = [],
+        public bool $nullable = false,
+        public mixed $default = null,
+        public bool $readonly = false,
+    ) {}
+}
\ No newline at end of file
diff --git a/src/Attribute/Property/Field/Mediumint.php b/src/Attribute/Property/Field/Mediumint.php
new file mode 100644
index 0000000..78c1fb4
--- /dev/null
+++ b/src/Attribute/Property/Field/Mediumint.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Ulmus\Attribute\Property\Field;
+
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
+class Mediumint extends \Ulmus\Attribute\Property\Field
+{
+    public function __construct(
+        public ? string $name = null,
+        public ? string $type = "mediumint",
+        public ? int $length = null,
+        public ? int $precision = null,
+        public array $attributes = [],
+        public bool $nullable = false,
+        public mixed $default = null,
+        public bool $readonly = false,
+    ) {}
+}
\ No newline at end of file
diff --git a/src/Attribute/Property/Field/Numeric.php b/src/Attribute/Property/Field/Numeric.php
new file mode 100644
index 0000000..ac2f4dc
--- /dev/null
+++ b/src/Attribute/Property/Field/Numeric.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Ulmus\Attribute\Property\Field;
+
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
+class Numeric extends \Ulmus\Attribute\Property\Field
+{
+    public function __construct(
+        public ? string $name = null,
+        public ? string $type = "numeric",
+        public ? int $length = null,
+        public ? int $precision = null,
+        public array $attributes = [],
+        public bool $nullable = false,
+        public mixed $default = null,
+        public bool $readonly = false,
+    ) {}
+}
\ No newline at end of file
diff --git a/src/Attribute/Property/Field/Smallint.php b/src/Attribute/Property/Field/Smallint.php
new file mode 100644
index 0000000..1f0578d
--- /dev/null
+++ b/src/Attribute/Property/Field/Smallint.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Ulmus\Attribute\Property\Field;
+
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
+class Smallint extends \Ulmus\Attribute\Property\Field
+{
+    public function __construct(
+        public ? string $name = null,
+        public ? string $type = "smallint",
+        public ? int $length = null,
+        public ? int $precision = null,
+        public array $attributes = [],
+        public bool $nullable = false,
+        public mixed $default = null,
+        public bool $readonly = false,
+    ) {}
+}
\ No newline at end of file
diff --git a/src/Attribute/Property/Field/Timestamp.php b/src/Attribute/Property/Field/Timestamp.php
new file mode 100644
index 0000000..5436053
--- /dev/null
+++ b/src/Attribute/Property/Field/Timestamp.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Ulmus\Attribute\Property\Field;
+
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
+class Timestamp extends \Ulmus\Attribute\Property\Field
+{
+    public function __construct(? string $type = "timestamp", ? int $length = null)
+    {
+        parent::__construct($type, $length);
+    }
+}
diff --git a/src/Attribute/Property/Field/Tinytext.php b/src/Attribute/Property/Field/Tinytext.php
new file mode 100644
index 0000000..81b111c
--- /dev/null
+++ b/src/Attribute/Property/Field/Tinytext.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Ulmus\Attribute\Property\Field;
+
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
+class Tinytext extends \Ulmus\Attribute\Property\Field
+{
+    public function __construct(
+        public ? string $name = null,
+        public ? string $type = "tinytext",
+        public ? int $length = null,
+        public ? int $precision = null,
+        public array $attributes = [],
+        public bool $nullable = false,
+        public mixed $default = null,
+        public bool $readonly = false,
+    ) {}
+}
\ No newline at end of file
diff --git a/src/Attribute/Property/Join.php b/src/Attribute/Property/Join.php
index ab8bd43..d6e1ddd 100644
--- a/src/Attribute/Property/Join.php
+++ b/src/Attribute/Property/Join.php
@@ -8,7 +8,7 @@ use Ulmus\Attribute\Attribute;
 class Join {
     public function __construct(
         public string $type,
-        public null|string|\Stringable $key  = null,
+        public null|string|\Stringable|array $key  = null,
         public null|string|\Stringable $foreignKey = null,
         public null|string $entity = null,
         public null|string $alias = null,
diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php
index ed7004e..90d7a1f 100644
--- a/src/QueryBuilder.php
+++ b/src/QueryBuilder.php
@@ -48,7 +48,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
         }
     }
 
-    public function select($field, bool $distinct = false) : self
+    public function select(string|\Stringable|array $field, bool $distinct = false) : self
     {
         if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) {
             $select->add($field);
@@ -195,7 +195,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
         return $this;
     }
 
-    public function where(/* stringable*/ $field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
+    public function where(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
     {
         # Empty IN case
         if ( [] === $value ) {
@@ -217,12 +217,12 @@ class QueryBuilder implements Query\QueryBuilderInterface
         return $this;
     }
 
-    public function notWhere($field, $value, string $operator = Query\Where::CONDITION_AND) : self
+    public function notWhere(string|\Stringable $field, $value, string $operator = Query\Where::CONDITION_AND) : self
     {
         return $this->where($field, $value, $operator, true);
     }
 
-    public function having($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
+    public function having(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
     {
         if ( $this->having ?? false ) {
             $having = $this->having;
@@ -266,7 +266,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
         return $this;
     }
 
-    public function orderBy(string $field, ? string $direction = null) : self
+    public function orderBy(string|\Stringable $field, ? string $direction = null) : self
     {
         if ( null === $orderBy = $this->getFragment(Query\OrderBy::class) ) {
             $orderBy = new Query\OrderBy();
@@ -278,7 +278,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
         return $this;
     }
 
-    public function groupBy(string $field, ? string $direction = null) : self
+    public function groupBy(string|\Stringable $field, ? string $direction = null) : self
     {
         if ( null === $groupBy = $this->getFragment(Query\GroupBy::class) ) {
             $groupBy = new Query\GroupBy();
@@ -290,14 +290,14 @@ class QueryBuilder implements Query\QueryBuilderInterface
         return $this;
     }
 
-    public function join(string $type, /*string | QueryBuilder*/ $table, $field, $value, bool $outer = false, ? string $alias = null) : self
+    public function join(string $type, /*string | QueryBuilder*/ $table, mixed $field, mixed $value, bool $outer = false, ? string $alias = null) : self
     {
         $this->withJoin(...func_get_args());
 
         return $this;
     }
 
-    public function withJoin(string $type, $table, $field, $value, bool $outer = false, ? string $alias = null) : Query\Join
+    public function withJoin(string $type, $table, mixed $field, mixed $value, bool $outer = false, ? string $alias = null) : Query\Join
     {
         $join = new Query\Join($this);
 
diff --git a/src/Repository.php b/src/Repository.php
index ee5b6e2..01d5420 100644
--- a/src/Repository.php
+++ b/src/Repository.php
@@ -316,7 +316,7 @@ class Repository
         $array = array_change_key_case($entity->toArray());
 
         $dataset = array_change_key_case($entity->entityGetDataset(false, true));
-# dump($array, $dataset);
+
         return array_udiff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset, function($e1, $e2) {
             if ( is_array($e1) ) {
                 if (is_array($e2)) {
@@ -464,7 +464,7 @@ class Repository
         return $this;
     }
 
-    public function join(string $type, $table, $field, $value, ? string $alias = null, ? callable $callback = null) : self
+    public function join(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
     {
         $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $alias ? $this->escapeIdentifier($alias) : null);
 
@@ -475,7 +475,7 @@ class Repository
         return $this;
     }
 
-    public function outerJoin(string $type, $table, $field, $value, ? string $alias = null, ? callable $callback = null) : self
+    public function outerJoin(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
     {
         $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $alias ? $this->escapeIdentifier($alias) : null);
 
@@ -506,7 +506,7 @@ class Repository
 
     }
 
-    public function groupBy($field) : self
+    public function groupBy(string|\Stringable $field) : self
     {
         $this->queryBuilder->groupBy($field);
 
@@ -522,7 +522,7 @@ class Repository
         return $this;
     }
 
-    public function orderBy($field, ? string $direction = null) : self
+    public function orderBy(string|\Stringable $field, ? string $direction = null) : self
     {
         $this->queryBuilder->orderBy($field, $direction);
 
@@ -572,7 +572,7 @@ class Repository
         return $this;
     }
 
-    public function wherePrimaryKey($value) : self
+    public function wherePrimaryKey(mixed $value) : self
     {
         if ( null === $primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() ) {
             throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'");
@@ -835,28 +835,28 @@ class Repository
         return Ulmus::datasetQueryBuilder($this->queryBuilder, $this->adapter);
     }
 
-    public function runQuery() /* : mixed */
+    public function runQuery() : mixed
     {
         $this->finalizeQuery();
 
         return Ulmus::runQuery($this->queryBuilder, $this->adapter);
     }
 
-    public function runInsertQuery() /* : mixed */
+    public function runInsertQuery() : mixed
     {
         $this->finalizeQuery();
 
         return Ulmus::runInsertQuery($this->queryBuilder, $this->adapter);
     }
 
-    public function runUpdateQuery() /* : mixed */
+    public function runUpdateQuery() : mixed
     {
         $this->finalizeQuery();
 
         return Ulmus::runUpdateQuery($this->queryBuilder, $this->adapter);
     }
 
-    public function runDeleteQuery() /* : mixed */
+    public function runDeleteQuery() : mixed
     {
         $this->finalizeQuery();
 
diff --git a/src/Repository/ConditionTrait.php b/src/Repository/ConditionTrait.php
index 38366c0..27847e6 100644
--- a/src/Repository/ConditionTrait.php
+++ b/src/Repository/ConditionTrait.php
@@ -25,7 +25,7 @@ trait ConditionTrait
         return $this;
     }
 
-    public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, $condition = Query\Where::CONDITION_AND) : self
+    public function where(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_EQUAL, $condition = Query\Where::CONDITION_AND) : self
     {
         $this->queryBuilder->where($field, $value, $operator, $condition);
         
@@ -41,101 +41,101 @@ trait ConditionTrait
         return $this;
     }
     
-    public function and($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    public function and(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
     {
         return $this->where($field, $value, $operator);
     }
 
-    public function or($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    public function or(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
     {
         $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR);
         
         return $this;
     }
 
-    public function notWhere($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    public function notWhere(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
     {
         $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND, true);
         
         return $this;
     }
 
-    public function orNot($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self
+    public function orNot(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self
     {
         $this->queryBuilder->notWhere($field, $value, $operator, Query\Where::CONDITION_OR, true);
         
         return $this;
     }
 
-    public function having($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, $condition = Query\Where::CONDITION_AND) : self
+    public function having(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_EQUAL, $condition = Query\Where::CONDITION_AND) : self
     {
         $this->queryBuilder->having($field, $value, $operator, $condition);
         
         return $this;
     }
 
-    public function orHaving($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    public function orHaving(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
     {
         $this->queryBuilder->having($field, $value, $operator, Query\Where::CONDITION_OR);
         
         return $this;
     }
 
-    public function notHaving($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self
+    public function notHaving(string|\Stringable $field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self
     {
         $this->queryBuilder->having($field, $value, $operator, Query\Where::CONDITION_AND, true);
         
         return $this;
     }
 
-    public function orNotHaving($field, $value) : self
+    public function orNotHaving(string|\Stringable $field, $value) : self
     {
         $this->queryBuilder->having($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR, true);
         
         return $this;
     }
     
-    public function in($field, $value, string $operator = Query\Where::COMPARISON_IN) : self
+    public function in(string|\Stringable $field, $value, string $operator = Query\Where::COMPARISON_IN) : self
     {
         $this->queryBuilder->where($field, $value, $operator);
         
         return $this;
     }
 
-    public function orIn($field, $value, string $operator = Query\Where::COMPARISON_IN) : self
+    public function orIn(string|\Stringable $field, $value, string $operator = Query\Where::COMPARISON_IN) : self
     {
         $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR);
         
         return $this;
     }
 
-    public function notIn($field, $value) : self
+    public function notIn(string|\Stringable $field, $value) : self
     {
         $this->queryBuilder->where($field, $value, Query\Where::COMPARISON_IN, true);
         
         return $this;
     }
 
-    public function orNotIn($field, $value) : self
+    public function orNotIn(string|\Stringable $field, $value) : self
     {
         return $this->orNot($field, $value, Query\Where::COMPARISON_IN, Query\Where::CONDITION_OR, true);
     }
 
-    public function like($field, $value) : self
+    public function like(string|\Stringable $field, $value) : self
     {
         $this->where($field, $value, Query\Where::OPERATOR_LIKE);
         
         return $this;
     }
 
-    public function orLike($field, $value) : self
+    public function orLike(string|\Stringable $field, $value) : self
     {
         $this->or($field, $value, Query\Where::OPERATOR_LIKE);
         
         return $this;
     }
 
-    public function notLike($field, $value) : self
+    public function notLike(string|\Stringable $field, $value) : self
     {
         $this->notWhere($field, $value, Query\Where::OPERATOR_LIKE);