- Work done on many-to-many relations - code must still be refactored out of the EntityTrait.

- Fixed Entity::field("propertyname") function to look for name into @Field() annotation and returns it if it exists.
- A lot of bug fixes made into SQL fragments.
This commit is contained in:
Dave Mc Nicoll 2020-04-09 09:50:09 -04:00
parent 6e84fc7195
commit c64c7778bd
20 changed files with 220 additions and 105 deletions

View File

@ -19,6 +19,8 @@ class Relation implements \Ulmus\Annotation\Annotation {
public string $bridgeForeignKey;
public string $entity;
public string $function = "loadAll";
public function __construct(string $type = null)
{
@ -32,4 +34,10 @@ class Relation implements \Ulmus\Annotation\Annotation {
return new $e();
}
public function bridge() {
$e = $this->bridge;
return new $e();
}
}

View File

@ -2,7 +2,8 @@
namespace Ulmus\Common;
use Ulmus\Ulmus;
use Ulmus\Ulmus,
Ulmus\Annotation\Property\Field;
class EntityField
{
@ -14,20 +15,19 @@ class EntityField
protected EntityResolver $entityResolver;
public function __construct(string $entityClass, string $name, string $alias)
public function __construct(string $entityClass, string $name, string $alias, EntityResolver $resolver)
{
$this->entityClass = $entityClass;
$this->name = $name;
$this->alias = $alias;
$this->entityResolver = Ulmus::resolveEntity(static::class);
}
$this->entityResolver = $resolver;
}
public function name($useAlias = true) : string
{
# Must use REFLECTION before throwing this value.
# Should first check if it's a relation field, and if it is,
# it's real key must be returned (PK usually)
return $useAlias ? "{$this->alias}.\"{$this->name}\"" : "\"{$this->name}\"";
$name = $this->entityResolver->searchFieldAnnotation($this->name, new Field() )->name ?? $this->name;
return $useAlias ? "{$this->alias}.\"{$name}\"" : "\"{$name}\"";
}
public static function isScalarType($type) : bool

View File

@ -94,9 +94,9 @@ class EntityResolver {
return [];
}
catch(\Throwable $e) {
if ( $throwException) {
# if ( $throwException) {
throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}");
}
# }
}
return null;

View File

@ -16,6 +16,7 @@ class PdoObject extends PDO {
return $statement;
}
} catch (\PDOException $e) {
debogueur($sql, $parameters);
throw $e;
}
}
@ -26,6 +27,7 @@ class PdoObject extends PDO {
return $this->execute($statement, $parameters, true);
}
} catch (\PDOException $e) {
debogueur($sql, $parameters);
throw $e;
}
@ -41,7 +43,7 @@ class PdoObject extends PDO {
if (empty($parameters) ? $statement->execute() : $statement->execute($parameters)) {
$statement->lastInsertId = $this->lastInsertId();
if ($commit) {
if ( $commit ) {
$this->commit();
}

View File

@ -3,6 +3,7 @@
namespace Ulmus;
use Ulmus\Repository,
Ulmus\Query,
Ulmus\Common\EntityResolver,
Ulmus\Common\EntityField;
@ -35,43 +36,85 @@ trait EntityTrait {
$entityResolver = $this->resolveEntity();
# Resolve relations here if one is called
# @TODO REFACTOR THIS CODE URGENTLY !
if ( null !== ( $relation = $entityResolver->searchFieldAnnotation($name, new Relation() ) ) ) {
$relationType = strtolower(str_replace(['-', '_'], '', $relation->type));
$order = $entityResolver->searchFieldAnnotationList($name, new OrderBy() );
$where = $entityResolver->searchFieldAnnotationList($name, new Where() );
$baseEntity = $relation->entity();
$repository = $baseEntity->repository();
foreach($where as $condition) {
$repository->where($condition->field, $condition->value, $condition->operator);
}
foreach($order as $item) {
$repository->orderBy($item->field, $item->order);
}
if ( $relation->entity ?? false ) {
$baseEntity = $relation->entity();
$field = $relation->key;
if ( method_exists($this, $filterMethod = "filterRelation$name") ) {
$this->$filterMethod($repository);
$repository = $baseEntity->repository();
foreach($where as $condition) {
$repository->where($condition->field, $condition->value, $condition->operator);
}
foreach($order as $item) {
$repository->orderBy($item->field, $item->order);
}
$field = $relation->key;
if ( method_exists($this, $filterMethod = "filterRelation$name") ) {
$this->$filterMethod($repository);
}
}
switch($relation->type) {
case 'oneToMany':
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field); # <<<<<<<<< CHANGE $THIS->ID WITH PROPER NOMENCLATURE
return $this->$name = $repository->loadAll();
case 'oneToOne':
switch( $relationType ) {
case 'onetoone':
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field );
$result = $repository->loadAll();
$result = call_user_func([$repository, $relation->function]);
if ( count($result) === 0 ) {
return $baseEntity;
}
return $this->$name = $result[0];
case 'onetomany':
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field); # <<<<<<<<< CHANGE $THIS->ID WITH PROPER NOMENCLATURE
return $this->$name = call_user_func([$repository, $relation->function]);
case 'manytomany':
if ( false === $relation->bridge ?? false ) {
throw new \Exception("Your many-to-many @Relation() from variable `$name` is missing a 'bridge' value.");
}
$bridgeEntity = Ulmus::resolveEntity($relation->bridge);
$bridgeRelation = $bridgeEntity->searchFieldAnnotation($relation->field, new Relation() );
$relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, new Relation() );
$repository = $relationRelation->entity()->repository();
$bridgeAlias = uniqid("bridge_");
$relationAlias = uniqid("relation_");
$repository->select("{$repository->alias}.*")
->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName() . " $bridgeAlias", $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey))
->join(Query\Join::TYPE_INNER, $this->resolveEntity()->tableName() . " $relationAlias", $relation->bridge::field($bridgeRelation->key, $bridgeAlias), static::field($bridgeRelation->foreignKey, $relationAlias))
->where( static::field($bridgeRelation->foreignKey, $relationAlias), $this->{$bridgeRelation->foreignKey} );
$this->$name = call_user_func([ $repository, $relationRelation->function ]);
if ($relation->bridgeField ?? false) {
$repository = $relationRelation->entity::repository();
$repository->select("$bridgeAlias.*")
->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName() . " $bridgeAlias", $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey))
->join(Query\Join::TYPE_INNER, $this->resolveEntity()->tableName() . " $relationAlias", $relation->bridge::field($bridgeRelation->key, $bridgeAlias), static::field($bridgeRelation->foreignKey, $relationAlias))
->where( static::field($bridgeRelation->foreignKey, $relationAlias), $this->{$bridgeRelation->foreignKey} );
$bridgeName = $relation->bridgeField;
$this->$bridgeName = $repository->collectionFromQuery($relation->bridge);
}
return $this->$name;
}
return;
@ -225,16 +268,16 @@ trait EntityTrait {
*/
public static function field($name, ? string $alias = null) : EntityField
{
return new EntityField(static::class, $name, $alias ?: Repository::DEFAULT_ALIAS);
return new EntityField(static::class, $name, $alias ?: Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class));
}
/**
* @Ignore
*/
public static function fields(...$fields) : string
public static function fields(array $fields, ? string $alias = null) : string
{
return implode(', ', array_map(function($name) {
return static::field($name);
return implode(', ', array_map(function($item) use ($alias){
return static::field($item, $alias);
}, $fields));
}
}

View File

@ -4,6 +4,8 @@ namespace Ulmus\Query;
class Delete extends Fragment {
const SQL_TOKEN = "DELETE";
public int $order = -100;
public bool $quick = false;
@ -17,7 +19,7 @@ class Delete extends Fragment {
public function render() : string
{
return $this->renderSegments([
'DELETE',
static::SQL_TOKEN,
( $this->top ? sprintf('TOP (%s)', $this->top) : false ),
( $this->priority ?? false ),
( $this->quick ? 'QUICK' : false ),

View File

@ -4,6 +4,8 @@ namespace Ulmus\Query;
class Explain extends Fragment {
const SQL_TOKEN = "EXPLAIN";
public int $order = -1500;
public bool $extended = false;
@ -11,7 +13,7 @@ class Explain extends Fragment {
public function render() : string
{
return $this->renderSegments([
"EXPLAIN", $this->extended ? "EXTENDED" : ""
static::SQL_TOKEN, $this->extended ? "EXTENDED" : ""
]);
}
}

View File

@ -4,6 +4,8 @@ namespace Ulmus\Query;
class From extends Fragment {
const SQL_TOKEN = "FROM";
public int $order = -80;
public array $tables = [];
@ -27,7 +29,7 @@ class From extends Fragment {
public function render() : string
{
return $this->renderSegments([
'FROM', $this->renderTables(),
static::SQL_TOKEN, $this->renderTables(),
]);
}

View File

@ -3,6 +3,9 @@
namespace Ulmus\Query;
class GroupBy extends Fragment {
const SQL_TOKEN = "GROUP BY";
public int $order = 70;
public array $groupBy = [];
@ -22,7 +25,7 @@ class GroupBy extends Fragment {
public function render() : string
{
return $this->renderSegments([
'GROUP BY', implode(", ", $this->groupBy)
static::SQL_TOKEN, implode(", ", $this->groupBy)
]);
}

View File

@ -3,5 +3,6 @@
namespace Ulmus\Query;
class Having extends Where {
const SQL_TOKEN = "HAVING";
public int $order = 75;
}

View File

@ -4,6 +4,8 @@ namespace Ulmus\Query;
class Insert extends Fragment {
const SQL_TOKEN = "INSERT";
public int $order = -100;
public bool $quick = false;
@ -21,7 +23,7 @@ class Insert extends Fragment {
public function render() : string
{
return $this->renderSegments([
'INSERT',
static::SQL_TOKEN,
( $this->priority ?? false ),
( $this->ignore ? 'IGNORE' : false ),
'INTO', $this->renderTable(),

View File

@ -2,7 +2,11 @@
namespace Ulmus\Query;
use Ulmus\QueryBuilder;
class Join extends Fragment {
const SQL_TOKEN = "JOIN";
const TYPE_LEFT = "LEFT";
const TYPE_RIGHT = "RIGHT";
@ -14,24 +18,33 @@ class Join extends Fragment {
public bool $outer = false;
public array $joins = [];
public string $attachment = "ON";
public string $side;
public /*string|QueryBuilder*/ $table;
public string $field;
public /*string|QueryBuilder*/ $value;
public /* QueryBuilder */ $queryBuilder;
public function add(string $side, string $table, string $field, $value)
public function __construct(QueryBuilder $queryBuilder) {
$this->queryBuilder = $queryBuilder;
}
public function set(string $side, /* QueryBuilder|string */ $table, string $field, /* QueryBuilder|string */ $value)
{
$this->joins[] = [
'side' => $side,
'table' => $table,
'field' => $field,
'value' => $value,
];
$this->side = $side;
$this->table = $table;
$this->field = $field;
$this->value = $value;
}
public function render() : string
{
return $this->renderSegments([
#'JOIN',
# table,
# 'ON',
# WHERE ! ,
]);
return $this->renderSegments([ $this->side, static::SQL_TOKEN, $this->table, $this->attachment, $this->field, "=", $this->value ]);
}
}

View File

@ -4,12 +4,12 @@ namespace Ulmus\Query;
class Limit extends Fragment {
public int $order = 80;
const SQL_TOKEN = "LIMIT";
public int $order = 90;
public int $limit = 0;
public string $keyword = "LIMIT %d";
public function set($limit) : self
{
$this->limit = $limit;
@ -20,7 +20,7 @@ class Limit extends Fragment {
public function render() : string
{
return $this->renderSegments([
sprintf($this->keyword, $this->limit)
static::SQL_TOKEN, $this->limit
]);
}
}

View File

@ -3,8 +3,10 @@
namespace Ulmus\Query;
class Offset extends Fragment {
public int $order = 81;
const SQL_TOKEN = "OFFSET";
public int $order = 95;
protected int $offset = 0;
@ -17,7 +19,7 @@ class Offset extends Fragment {
public function render() : string
{
return $this->renderSegments([
'OFFSET', $this->offset,
static::SQL_TOKEN, $this->offset,
]);
}
}

View File

@ -3,9 +3,11 @@
namespace Ulmus\Query;
class OrderBy extends Fragment {
public int $order = 70;
public int $order = 80;
public array $orderBy = [];
const SQL_TOKEN = "ORDER BY";
public function set(array $order) : self
{
@ -27,7 +29,7 @@ class OrderBy extends Fragment {
}, $this->orderBy);
return $this->renderSegments([
'ORDER BY', implode(", ", $list)
static::SQL_TOKEN, implode(", ", $list)
]);
}

View File

@ -14,6 +14,8 @@ class Select extends Fragment {
protected array $fields = [];
const SQL_TOKEN = "SELECT";
public function set($fields) : self
{
$this->fields = is_array($fields) ? $fields : [ $fields ];
@ -36,7 +38,7 @@ class Select extends Fragment {
{
return $this->renderSegments([
( $this->union ? 'UNION' : false ),
'SELECT',
static::SQL_TOKEN,
( $this->top ? sprintf('TOP (%s)', $this->top) : false ),
implode(', ', $this->fields)
]);

View File

@ -17,6 +17,8 @@ class Where extends Fragment {
const COMPARISON_IN = "IN";
const COMPARISON_IS = "IS";
const COMPARISON_NULL = "NULL";
const SQL_TOKEN = "WHERE";
public int $order = 50;
@ -65,7 +67,7 @@ class Where extends Fragment {
}
return $this->renderSegments([
! $this->parent ? "WHERE" : "",
! $this->parent ? static::SQL_TOKEN : "",
implode(" ", $stack)
]);
}

View File

@ -6,6 +6,8 @@ class QueryBuilder
{
public Query\Where $where;
public Query\Having $having;
/**
* Those are the parameters we are going to bind to PDO.
*/
@ -17,7 +19,9 @@ class QueryBuilder
*/
public array $values = [];
public string $conditionOperator = Query\Where::CONDITION_AND;
public string $whereConditionOperator = Query\Where::CONDITION_AND;
public string $havingConditionOperator = Query\Having::CONDITION_AND;
protected int $parameterIndex = 0;
@ -166,12 +170,29 @@ class QueryBuilder
$this->push($where);
}
$this->conditionOperator = $operator;
$this->whereConditionOperator = $operator;
$where->add($field, $value, $operator, $condition, $not);
return $this;
}
public function having($field, $value, string $operator = Query\Having::OPERATOR_EQUAL, string $condition = Query\Having::CONDITION_AND, bool $not = false) : self
{
if ( $this->having ?? false ) {
$having = $this->having;
}
elseif ( null === ( $having = $this->getFragment(Query\Having::class) ) ) {
$this->having = $having = new Query\Having($this);
$this->push($having);
}
$this->havingConditionOperator = $operator;
$having->add($field, $value, $operator, $condition, $not);
return $this;
}
public function notWhere($field, $value, string $operator = Query\Where::CONDITION_AND) : self
{
return $this->where($field, $value, $operator, true);
@ -179,7 +200,7 @@ class QueryBuilder
public function groupBy() : self
{
//$this->queryBuilder->groupBy();
return $this;
}
@ -219,28 +240,19 @@ class QueryBuilder
return $this;
}
public function join(string $type, $table, $field, $value, bool $outer = false) : self
public function join(string $type, /*string | QueryBuilder*/ $table, $field, $value, bool $outer = false) : self
{
if ( null === $join = $this->getFragment(Query\Join::class) ) {
$join = new Query\Join();
$this->push($join);
}
#if ( null === $join = $this->getFragment(Query\Join::class) ) {#}
$join->add($type, $table, $field, $value);
$join = new Query\Join($this);
$this->push($join);
$join->set($type, $table, $field, $value);
$join->outer = $outer;
return $this;
}
/*
"LEFT JOIN PAI_DOS ON PAI_DOS.MATR = PAI_DOS_EMPL.MATR"
"LEFT JOIN PAI_DOS_2 ON PAI_DOS_2.MATR = PAI_DOS.MATR"
"LEFT JOIN PAI_CAND ON PAI_CAND.MATR = PAI_DOS.MATR"
"LEFT JOIN PAI_TAB_CORP_EMPL ON PAI_TAB_CORP_EMPL.CORP_EMPL = PAI_DOS_EMPL.CORP_EMPL",
"LEFT JOIN PAI_TAB_LIEU_TRAV ON PAI_TAB_LIEU_TRAV.LIEU_TRAV = PAI_DOS_EMPL.LIEU_TRAV",
"LEFT JOIN PAI_TAB_BAT ON PAI_TAB_BAT.BAT = PAI_TAB_LIEU_TRAV.BAT"
*/
public function push(Query\Fragment $queryFragment) : self
{
$this->queryStack[] = $queryFragment;
@ -266,7 +278,8 @@ class QueryBuilder
public function reset() : void
{
$this->parameters = $this->values = $this->queryStack = [];
$this->conditionOperator = Query\Where::CONDITION_AND;
$this->whereConditionOperator = Query\Where::CONDITION_AND;
$this->havingConditionOperator = Query\Having::CONDITION_AND;
$this->parameterIndex = 0;
unset($this->where);

View File

@ -53,15 +53,11 @@ class Repository
public function count() : int
{
$this->select("count(*) as totalItem")->selectSqlQuery();
$this->select("COUNT(*)")->selectSqlQuery();
$this->finalizeQuery();
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
return $entityData['totalItem'];
}
return 0;
return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0);
}
public function deleteOne()
@ -157,7 +153,7 @@ class Repository
return $this;
}
public function update(array $fieldlist, string $table, string $alias, ? string $schema) : self
public function update(string $table, string $alias, ? string $schema) : self
{
$this->queryBuilder->update($table, $alias, $schema);
@ -239,21 +235,39 @@ class Repository
public function orNot($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
{
$this->queryBuilder->notWhere($condition, Query\Where::CONDITION_OR, true);
$this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR, true);
return $this;
}
public function having() : self
public function having($field, $value, string $operator = Query\Having::OPERATOR_EQUAL) : self
{
$this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_AND);
return $this;
}
public function notHaving() : self
public function orHaving($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
{
$this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_OR);
return $this;
}
public function notHaving($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self
{
$this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_AND, true);
return $this;
}
public function orNotHaving($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self
{
$this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_OR, true);
return $this;
}
public function in($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
{
$this->queryBuilder->where($field, $value, $operator);
@ -362,10 +376,10 @@ class Repository
return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value);
}
protected function collectionFromQuery() : EntityCollection
public function collectionFromQuery(? string $entityClass = null) : EntityCollection
{
$class = $this->entityClass;
$class = $entityClass ?: $this->entityClass;
$entityCollection = new EntityCollection();
@ -401,7 +415,7 @@ class Repository
protected function updateSqlQuery(array $dataset) : self
{
if ( null === $this->queryBuilder->getFragment(Query\Update::class) ) {
$this->update($dataset, $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
$this->update($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
}
$this->set($dataset);

View File

@ -41,12 +41,14 @@ abstract class Ulmus
return ( $adapter ?: static::$defaultAdapter )->pdo();
}
public static function runSelectQuery(QueryBuilder $queryBuilder, ? ConnectionAdapter $adapter = null)
{
return static::pdo($adapter)->select($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? []));
}
public static function runQuery(QueryBuilder $queryBuilder, ? ConnectionAdapter $adapter = null)
{
$result = static::pdo($adapter)->runQuery($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? []));
$queryBuilder->reset();
return $result;
{
return static::pdo($adapter)->runQuery($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? []));
}
public static function resolveEntity(string $entityClass) : Common\EntityResolver