From 16d76470da4e9e4fa85ca60690b9d2ecce27a896 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Wed, 13 Apr 2022 03:31:48 +0000 Subject: [PATCH] - A lot of bugfixes and fitting to accomodate ldap adapter --- src/Adapter/AdapterInterface.php | 1 + src/Adapter/MsSQL.php | 22 +- src/Adapter/MySQL.php | 17 ++ src/Annotation/Property/Virtual.php | 2 + src/Common/PdoObject.php | 11 +- src/EntityTrait.php | 27 +- src/Migration/FieldDefinition.php | 1 + src/Query/Fragment.php | 5 + src/Query/Join.php | 38 +-- src/QueryBuilder.php | 130 +++++----- src/Repository.php | 240 ++++++++++-------- src/Repository/EscapeTrait.php | 51 ++++ src/Repository/MssqlRepository.php | 8 +- .../SearchRequestPaginationTrait.php | 2 +- src/Ulmus.php | 11 +- 15 files changed, 350 insertions(+), 216 deletions(-) create mode 100644 src/Repository/EscapeTrait.php diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php index d0ca171..52e755f 100644 --- a/src/Adapter/AdapterInterface.php +++ b/src/Adapter/AdapterInterface.php @@ -16,4 +16,5 @@ interface AdapterInterface { public function setup(array $configuration) : void; public function escapeIdentifier(string $segment, int $type) : string; public function defaultEngine() : ? string; + public function writableValue(/* mixed */ $value) /*: mixed*/; } diff --git a/src/Adapter/MsSQL.php b/src/Adapter/MsSQL.php index 4c01539..57ac5b7 100644 --- a/src/Adapter/MsSQL.php +++ b/src/Adapter/MsSQL.php @@ -5,6 +5,7 @@ namespace Ulmus\Adapter; use Ulmus\Common\PdoObject; use Ulmus\Exception\AdapterConfigurationException; +use Ulmus\Ulmus; class MsSQL implements AdapterInterface { @@ -93,7 +94,7 @@ class MsSQL implements AdapterInterface { $parts[] = "Encrypt=1"; } - if ( $this->failoverPartner ?? false ) { + if ( $this->failoverPartner ?? false ) { $parts[] = "Failover_Partner={$this->failoverPartner}"; } @@ -200,8 +201,25 @@ class MsSQL implements AdapterInterface { } } + + public function writableValue(/* mixed */ $value) /*: mixed*/ + { + switch (true) { + case is_object($value): + return Ulmus::convertObject($value); + + case is_array($value): + return json_encode($array); + + case is_bool($value): + return (int) $value; + } + + return $value; + } + public function defaultEngine(): ? string { return null; } -} \ No newline at end of file +} diff --git a/src/Adapter/MySQL.php b/src/Adapter/MySQL.php index 8b9113f..8868410 100644 --- a/src/Adapter/MySQL.php +++ b/src/Adapter/MySQL.php @@ -5,6 +5,7 @@ namespace Ulmus\Adapter; use Ulmus\Common\PdoObject; use Ulmus\Exception\AdapterConfigurationException; +use Ulmus\Ulmus; class MySQL implements AdapterInterface { @@ -137,6 +138,22 @@ class MySQL implements AdapterInterface { } } + public function writableValue(/* mixed */ $value) /*: mixed*/ + { + switch (true) { + case is_object($value): + return Ulmus::convertObject($value); + + case is_array($value): + return json_encode($value); + + case is_bool($value): + return (int) $value; + } + + return $value; + } + public function defaultEngine(): ? string { return "InnoDB"; diff --git a/src/Annotation/Property/Virtual.php b/src/Annotation/Property/Virtual.php index e6a1d78..295c938 100644 --- a/src/Annotation/Property/Virtual.php +++ b/src/Annotation/Property/Virtual.php @@ -4,4 +4,6 @@ namespace Ulmus\Annotation\Property; class Virtual extends Field { public Closure $closure; + + public bool $readonly = true; } diff --git a/src/Common/PdoObject.php b/src/Common/PdoObject.php index 78ce731..a354c6a 100644 --- a/src/Common/PdoObject.php +++ b/src/Common/PdoObject.php @@ -28,6 +28,9 @@ class PdoObject extends PDO { } } + /** + * @deprecated + */ public function runQuery(string $sql, array $parameters = []): ? PDOStatement { static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]); @@ -44,14 +47,14 @@ class PdoObject extends PDO { return null; } - public function runInsertQuery(array $filter, array $dataset) + public function runInsertQuery(string $sql, array $parameters = []) { - return $this->runQuery($filter, $dataset); + return $this->runQuery($sql, $parameters); } - public function runUpdateQuery(array $filter, array $dataset) + public function runUpdateQuery(string $sql, array $parameters = []) { - return $this->runQuery($filter, $dataset); + return $this->runQuery($sql, $parameters); } public function runDeleteQuery(string $sql, array $parameters = []): ? PDOStatement diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 4487ed1..8cd57f1 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -143,27 +143,10 @@ trait EntityTrait { $annotation = $entityResolver->searchFieldAnnotation($key, new Field() ); if ( isset($this->$key) ) { - $realKey = $annotation->name ?? $key; - - switch (true) { - case is_object($this->$key): - $dataset[$realKey] = Ulmus::convertObject($this->$key); - break; - - case is_array($this->$key): - $dataset[$realKey] = Ulmus::encodeArray($this->$key); - break; - - case is_bool($this->$key): - $dataset[$realKey] = (int) $this->$key; - break; - - default: - $dataset[$realKey] = $this->$key; - } + $dataset[$annotation->name ?? $key] = static::repository()->adapter->adapter()->writableValue($this->$key); } elseif ( $field['nullable'] ) { - $dataset[ $annotation->name ?? $key ] = null; + $dataset[$annotation->name ?? $key] = null; } } @@ -197,9 +180,11 @@ trait EntityTrait { /** * @Ignore */ - public function toArray($includeRelations = false) : array + public function toArray($includeRelations = false, array $filterFields = null) : array { - return $this->entityGetDataset($includeRelations); + $dataset = $this->entityGetDataset($includeRelations); + + return $filterFields ? array_intersect_key($dataset, array_flip($filterFields)) : $dataset; } /** diff --git a/src/Migration/FieldDefinition.php b/src/Migration/FieldDefinition.php index ad35d5d..9602228 100644 --- a/src/Migration/FieldDefinition.php +++ b/src/Migration/FieldDefinition.php @@ -109,6 +109,7 @@ class FieldDefinition { return implode(' ', array_filter([ $this->isUnsigned() ? "UNSIGNED" : null, $this->nullable ? "NULL" : "NOT NULL", + $this->update ? "ON UPDATE {$this->update}" : null, $default ? "DEFAULT $default" : null, $this->isAutoIncrement() ? "AUTO_INCREMENT" : null, $this->isPrimaryKey() ? "PRIMARY KEY" : null, diff --git a/src/Query/Fragment.php b/src/Query/Fragment.php index e8bc199..0c55b2e 100644 --- a/src/Query/Fragment.php +++ b/src/Query/Fragment.php @@ -8,6 +8,11 @@ abstract class Fragment { public abstract function render() /*: mixed*/; + public function order() : float + { + return (float) $this->order; + } + protected function renderSegments(array $segments, string $glue = " ") : string { return implode($glue, array_filter($segments, function($i) { return ! is_null($i) && $i !== false && $i !== ""; })); diff --git a/src/Query/Join.php b/src/Query/Join.php index 77a5e95..85771f6 100644 --- a/src/Query/Join.php +++ b/src/Query/Join.php @@ -5,10 +5,10 @@ namespace Ulmus\Query; use Ulmus\QueryBuilder; use Ulmus\Repository\ConditionTrait; -class Join extends Fragment +class Join extends Fragment { use ConditionTrait; - + const SQL_OUTER = "OUTER"; const SQL_TOKEN = "JOIN"; @@ -20,32 +20,35 @@ class Join extends Fragment const TYPE_NATURAL = "NATURAL"; public bool $outer = false; - + public array $joins = []; - + public string $attachment = "ON"; - + public string $side; - + public /*string|QueryBuilder*/ $table; - + public ? string $alias; - + public string $field; - + public /*string|QueryBuilder*/ $value; - + public /* QueryBuilder */ $queryBuilder; public int $order = 40; - public function __construct(QueryBuilderInterface $queryBuilder) + public int $joinOrder = 0; + + public function __construct(QueryBuilderInterface $queryBuilder) { - $this->queryBuilder = new QueryBuilder(); + $cls = get_class($queryBuilder); + $this->queryBuilder = new $cls(); $this->queryBuilder->parent = $queryBuilder; } - - public function set(string $side, /* QueryBuilder|string */ $table, string $field, /* QueryBuilder|string */ $value) + + public function set(string $side, /* QueryBuilder|string */ $table, string $field, /* QueryBuilder|string */ $value) { $this->side = $side; $this->table = $table; @@ -53,9 +56,14 @@ class Join extends Fragment $this->where($this->field = $field, $this->value = $value); } + public function order() : float + { + return $this->order + ( $this->joinOrder / 100 ); + } + public function render() : string { - return $this->renderSegments([ + return $this->renderSegments([ strtoupper($this->side), $this->outer ? static::SQL_OUTER : "", static::SQL_TOKEN, $this->table, $this->alias ?? "", $this->attachment, $this->queryBuilder->render(true) ?? "" ]); } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 2b9ada3..8802c09 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -9,45 +9,45 @@ class QueryBuilder implements Query\QueryBuilderInterface public Query\Where $where; public Query\Having $having; - + public QueryBuilderInterface $parent; - + /** * 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 $whereConditionOperator = Query\Where::CONDITION_AND; - + public string $havingConditionOperator = Query\Where::CONDITION_AND; - + protected int $parameterIndex = 0; protected array $queryStack = []; - public function __clone() + public function __clone() { if ($this->where ?? false) { #$this->where = clone $this->where; #$this->where->queryBuilder = $this; } - + if ($this->having ?? false) { #$this->having = clone $this->having; #$this->having->queryBuiler = $this; } - + if ($this->parent ?? false) { #$this->parent = clone $this->parent; } } - + public function select($field, bool $distinct = false) : self { if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) { @@ -63,18 +63,18 @@ class QueryBuilder implements Query\QueryBuilderInterface return $this; } - + public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null, bool $replace = false) : self { - if ( null === $this->getFragment(Query\Insert::class) ) { + if ( null === $this->getFragment(Query\Insert::class) ) { if ( $schema ) { $table = "$schema.$table"; } - + if ( $database ) { $table = "$database.$table"; } - + $insert = new Query\Insert(); $this->push($insert); @@ -87,32 +87,32 @@ class QueryBuilder implements Query\QueryBuilderInterface return $this; } - public function values(array $dataset) : self + public function values(array $dataset) : self { if ( null === ( $values = $this->getFragment(Query\Values::class) ) ) { $values = new Query\Values($this); $this->push($values); } - + $values->add($dataset); - + return $this; } - + public function update(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self { if ( ! $this->getFragment(Query\Update::class) ) { if ( $schema ) { $table = "$schema.$table"; } - + if ( $database ) { $table = "$database.$table"; } $update = new Query\Update(); $this->push($update); - + $update->alias = $alias; $update->table = $table; } @@ -122,17 +122,16 @@ class QueryBuilder implements Query\QueryBuilderInterface public function set(array $dataset, ? array $escapedFields = null) : self { - if ( null === ( $set = $this->getFragment(Query\Set::class) ) ) { $set = new Query\Set($this); $this->push($set); } - + $set->set($dataset, $escapedFields); - + return $this; } - + public function delete() : self { if ( ! $this->getFragment(Query\Delete::class) ) { @@ -147,7 +146,7 @@ class QueryBuilder implements Query\QueryBuilderInterface if ( $schema ) { $table = "$schema.$table"; } - + if ( $database ) { $table = "$database.$table"; } @@ -158,7 +157,7 @@ class QueryBuilder implements Query\QueryBuilderInterface else { $from = new Query\From($this); $this->push($from); - + $from->set($alias ? [ $alias => $table ] : [ $table ]); } @@ -184,12 +183,12 @@ class QueryBuilder implements Query\QueryBuilderInterface public function close() : self { if ( null !== ($this->where ?? null) && $this->where->parent ) { - + # if an enclosure was opened, and nothing done, we must remove the unused node if ( empty($this->where->conditionList) && (count($this->where->parent->conditionList) === 1) ) { unset($this->where->parent->conditionList); } - + $this->where = $this->where->parent; } @@ -202,7 +201,7 @@ class QueryBuilder implements Query\QueryBuilderInterface if ( [] === $value ) { return $this; } - + if ( $this->where ?? false ) { $where = $this->where; } @@ -214,7 +213,7 @@ class QueryBuilder implements Query\QueryBuilderInterface $this->whereConditionOperator = $operator; $where->add($field, $value, $operator, $condition, $not); - + return $this; } @@ -235,10 +234,10 @@ class QueryBuilder implements Query\QueryBuilderInterface $this->havingConditionOperator = $operator; $having->add($field, $value, $operator, $condition, $not); - + return $this; } - + public function limit(int $value) : self { if ( null === $limit = $this->getFragment(Query\Limit::class) ) { @@ -247,7 +246,7 @@ class QueryBuilder implements Query\QueryBuilderInterface } $limit->set($value); - + return $this; } @@ -259,7 +258,7 @@ class QueryBuilder implements Query\QueryBuilderInterface } $offset->set($value); - + return $this; } @@ -271,7 +270,7 @@ class QueryBuilder implements Query\QueryBuilderInterface } $orderBy->add($field, $direction); - + return $this; } @@ -283,48 +282,50 @@ class QueryBuilder implements Query\QueryBuilderInterface } $groupBy->add($field, $direction); - + return $this; } public function join(string $type, /*string | QueryBuilder*/ $table, $field, $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 { $join = new Query\Join($this); - + $this->push($join); - + $join->set($type, $table, $field, $value); - + $join->outer = $outer; - + $join->alias = $alias; - + + $join->joinOrder = $this->nextJoinOrder(); + return $join; } - + public function truncate(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self { if ( $schema ) { $table = "$schema.$table"; } - + if ( $database ) { $table = "$database.$table"; } $truncate = new Query\Truncate($this); - + $this->push($truncate); $truncate->set($table); - + return $this; } @@ -334,7 +335,7 @@ class QueryBuilder implements Query\QueryBuilderInterface if ( $schema ) { $table = "$schema.$table"; } - + if ( $database ) { $table = "$database.$table"; } @@ -387,14 +388,14 @@ class QueryBuilder implements Query\QueryBuilderInterface return $this; } - + public function push(Query\Fragment $queryFragment) : self { $this->queryStack[] = $queryFragment; - + return $this; } - + public function pull(Query\Fragment $queryFragment) : self { return array_shift($this->queryStack); @@ -405,7 +406,7 @@ class QueryBuilder implements Query\QueryBuilderInterface $sql = []; usort($this->queryStack, function($q1, $q2) { - return $q1->order <=> $q2->order; + return $q1->order() <=> $q2->order(); }); foreach($this->queryStack as $fragment) { @@ -414,7 +415,7 @@ class QueryBuilder implements Query\QueryBuilderInterface return implode(" ", $sql); } - + public function reset() : void { $this->parameters = $this->values = $this->queryStack = []; @@ -456,33 +457,44 @@ class QueryBuilder implements Query\QueryBuilderInterface } } - public function getFragments() : array + public function getFragments(/*? Query\Fragment|array*/ $fragment = null) : array { - return $this->queryStack; + return $fragment === null ? array_filter($this->queryStack, fn($e) => get_class($e) === $fragment) : $this->queryStack; } - + public function __toString() : string { return $this->render(); } - public function addParameter($value, string $key = null) : string + public function addParameter($value, string $key = null) : string { if ( $this->parent ?? false ) { return $this->parent->addParameter($value, $key); } - + if ( $key === null ) { $key = ":p" . $this->parameterIndex++; } - $this->parameters[$key] = $value; + $this->parameters[$key] = $value; return $key; } - + public function addValues(array $values) : void { $this->values = $values; } + + protected function nextJoinOrder() : float + { + $next = 0; + + foreach($this->getFragments(Query\Join::class) as $join) { + $next = max($next, $join->joinOrder); + } + + return $next + 1; + } } diff --git a/src/Repository.php b/src/Repository.php index 267efe8..cda498d 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -7,7 +7,7 @@ use Ulmus\Common\EntityResolver; class Repository { - use EventTrait, Repository\ConditionTrait; + use EventTrait, Repository\ConditionTrait, Repository\EscapeTrait; const DEFAULT_ALIAS = "this"; @@ -30,7 +30,7 @@ class Repository $this->adapter = $adapter ?? $this->entityResolver->databaseAdapter(); $this->queryBuilder = new QueryBuilder(); } - + public function __clone() { #$this->queryBuilder = clone $this->queryBuilder; @@ -74,7 +74,7 @@ class Repository $this->select( "DISTINCT COUNT(*) OVER ()" ); } else { - $this->select(Common\Sql::function("COUNT", "*")); + $this->select(Common\Sql::function("COUNT", '*')); } $this->selectSqlQuery(); @@ -144,13 +144,13 @@ class Repository $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); - if ( ! $entity->isLoaded() ) { + if ( $replace || ! $entity->isLoaded() ) { # $dataset = array_filter($dataset, fn($item, $field) => ! ($this->entityResolver->searchFieldAnnotation($field, new Field, false)->readonly ?? false), \ARRAY_FILTER_USE_BOTH); $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery(); - - if ( ( 0 !== $statement->lastInsertId ) && - ( null !== $primaryKeyDefinition )) { + + if ( ( 0 !== $statement->lastInsertId ) && + ( null !== $primaryKeyDefinition )) { $pkField = key($primaryKeyDefinition); $dataset[$pkField] = $statement->lastInsertId; @@ -192,65 +192,64 @@ class Repository } return $changed; - } - public function loadCollectionRelation(EntityCollection $collection, /*array|string*/ $fields) : void + public function insertAll(/* EntityCollection | array */ $collection, int $size = 1000) : int { - foreach ((array)$fields as $name) { - if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, new Annotation\Property\Relation()))) { - $relationType = strtolower(str_replace(['-', '_', ' '], '', $relation->type)); + if ( empty($collection) ) { + return 0; + } + elseif ( is_array($collection) ) { + $collection = $this->entityClass::entityCollection($collection); + } - $order = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\OrderBy()); - $where = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\Where()); - - $baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type']; - $baseEntityResolver = $baseEntity::resolveEntity(); - - $property = ($baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02))['name']; - $entityProperty = ($this->entityResolver->field($relation->key, 01, false) ?: $this->entityResolver->field($relation->key, 02))['name']; - - $repository = $baseEntity::repository(); - - foreach ($where as $condition) { - $repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [$this]) : $condition->value, $condition->operator, $condition->condition); - } - - foreach ($order as $item) { - $repository->orderBy($item->field, $item->order); - } - - $field = $relation->key; - - $values = []; - - $key = is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey); - - foreach ($collection as $item) { - $values[] = is_callable($field) ? $field($item) : $item->$entityProperty; - } - - $repository->where($key, $values); - - switch ($relationType) { - case 'onetoone': - $results = call_user_func([$repository, "loadOne"]); - $item->$name = $results ?: new $baseEntity(); - - break; - - case 'onetomany': - $results = call_user_func([$repository, $relation->function]); - - foreach ($collection as $item) { - $item->$name = $baseEntity::entityCollection(); - $item->$name->mergeWith($results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty)); - } - - break; - } + foreach($collection as $entity) { + if ( ! $this->matchEntity($entity) ) { + throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`"); } } + + $dataset = $entity->toArray(); + + $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); + + if ( $replace || ! $entity->isLoaded() ) { + # $dataset = array_filter($dataset, fn($item, $field) => ! ($this->entityResolver->searchFieldAnnotation($field, new Field, false)->readonly ?? false), \ARRAY_FILTER_USE_BOTH); + + $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery(); + + if ( ( 0 !== $statement->lastInsertId ) && + ( null !== $primaryKeyDefinition )) { + + $pkField = key($primaryKeyDefinition); + $dataset[$pkField] = $statement->lastInsertId; + } + + $entity->entityFillFromDataset($dataset, true); + + return (bool) $statement->lastInsertId; + } + else { + if ( $primaryKeyDefinition === null ) { + throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass)); + } + + $diff = $fieldsAndValue ?? $this->generateWritableDataset($entity); + + if ( [] !== $diff ) { + $pkField = key($primaryKeyDefinition); + $pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField; + $this->where($pkFieldName, $dataset[$pkFieldName]); + + $update = $this->updateSqlQuery($diff)->runUpdateQuery(); + + $entity->entityFillFromDataset($dataset, true); + + return $update ? (bool) $update->rowCount() : false; + } + } + + } public function replace(/*object|array*/ $entity, ? array $fieldsAndValue = null) : bool @@ -258,11 +257,15 @@ class Repository return $this->save($entity, $fieldsAndValue, true); } - public function replaceAll(/*EntityCollection|array*/ $collection) : void + public function replaceAll(/*EntityCollection|array*/ $collection) : int { + $changed = 0; + foreach($collection as $entity) { - $this->replace($entity); + $this->replace($entity) && $changed++; } + + return $changed; } public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self @@ -323,7 +326,9 @@ class Repository public function removeQueryFragment(/*? Query\Fragment|string*/ $fragment) : self { - $fragment && $this->queryBuilder->removeFragment($fragment); + foreach((array) $fragment as $item) { + $this->queryBuilder->removeFragment($item); + } return $this; } @@ -407,7 +412,7 @@ class Repository return $this; } - public function delete() : self + public function delete(...$args) : self { $this->queryBuilder->delete($this->alias); @@ -489,10 +494,10 @@ class Repository public function randomizeOrder() : self { $this->queryBuilder->orderBy(Common\Sql::function('RAND', Sql::identifier('CURDATE()+0'))); - + return $this; } - + public function orders(array $orderList) : self { foreach($orderList as $field => $direction) { @@ -678,6 +683,64 @@ class Repository return $this; } + public function loadCollectionRelation(EntityCollection $collection, /*array|string*/ $fields) : void + { + foreach ((array)$fields as $name) { + if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, new Annotation\Property\Relation()))) { + $relationType = strtolower(str_replace(['-', '_', ' '], '', $relation->type)); + + $order = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\OrderBy()); + $where = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\Where()); + + $baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type']; + $baseEntityResolver = $baseEntity::resolveEntity(); + + $property = ($baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02))['name']; + $entityProperty = ($this->entityResolver->field($relation->key, 01, false) ?: $this->entityResolver->field($relation->key, 02))['name']; + + $repository = $baseEntity::repository(); + + foreach ($where as $condition) { + $repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [$this]) : $condition->value, $condition->operator, $condition->condition); + } + + foreach ($order as $item) { + $repository->orderBy($item->field, $item->order); + } + + $field = $relation->key; + + $values = []; + + $key = is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey); + + foreach ($collection as $item) { + $values[] = is_callable($field) ? $field($item) : $item->$entityProperty; + } + + $repository->where($key, $values); + + switch ($relationType) { + case 'onetoone': + $results = call_user_func([$repository, "loadOne"]); + $item->$name = $results ?: new $baseEntity(); + + break; + + case 'onetomany': + $results = call_user_func([$repository, $relation->function]); + + foreach ($collection as $item) { + $item->$name = $baseEntity::entityCollection(); + $item->$name->mergeWith($results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty)); + } + + break; + } + } + } + } + public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self { if ($count) { @@ -859,7 +922,7 @@ class Repository return $result; } - + public function instanciateEntityCollection(...$arguments) : EntityCollection { return $this->entityClass::entityCollection(...$arguments); @@ -870,48 +933,7 @@ class Repository return new $this->entityClass(); } - public function escapeField(string $identifier) : string - { - return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_FIELD); - } - - public function escapeFieldList(array $fieldList) : array - { - foreach($fieldList as &$list) { - $list['name'] = $this->escapeField($list['name']); - - $fieldTag = array_filter($list['tags'] ?? [], fn($item) => $item['object'] instanceof Field)[0]['object'] ?? null; - - if ( $fieldTag && isset($fieldTag->name) ) { - $fieldTag->name = $this->escapeField($fieldTag->name); - } - } - - return $fieldList; - } - - public function escapeTable(string $identifier) : string - { - return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_TABLE); - } - - public function escapeDatabase(string $identifier) : string - { - return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_DATABASE); - } - - public function escapedDatabase() : ? string - { - $name = $this->entityResolver->tableAnnotation()->database ?? $this->adapter->adapter()->database ?? null; - - return $name ? static::escapeDatabase($name) : null; - } - - public function escapeSchema(string $identifier) : string - { - return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_SCHEMA); - } - + public function hasFilters() : bool { return isset($this->queryBuilder->where); diff --git a/src/Repository/EscapeTrait.php b/src/Repository/EscapeTrait.php new file mode 100644 index 0000000..eb69ab7 --- /dev/null +++ b/src/Repository/EscapeTrait.php @@ -0,0 +1,51 @@ +adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_FIELD); + } + + public function escapeFieldList(array $fieldList) : array + { + foreach($fieldList as &$list) { + $list['name'] = $this->escapeField($list['name']); + + $fieldTag = array_filter($list['tags'] ?? [], fn($item) => $item['object'] instanceof Field)[0]['object'] ?? null; + + if ( $fieldTag && isset($fieldTag->name) ) { + $fieldTag->name = $this->escapeField($fieldTag->name); + } + } + + return $fieldList; + } + + public function escapeTable(string $identifier) : string + { + return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_TABLE); + } + + public function escapeDatabase(string $identifier) : string + { + return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_DATABASE); + } + + public function escapedDatabase() : ? string + { + $name = $this->entityResolver->tableAnnotation()->database ?? $this->adapter->adapter()->database ?? null; + + return $name ? static::escapeDatabase($name) : null; + } + + public function escapeSchema(string $identifier) : string + { + return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_SCHEMA); + } +} \ No newline at end of file diff --git a/src/Repository/MssqlRepository.php b/src/Repository/MssqlRepository.php index 590fb47..0da9730 100644 --- a/src/Repository/MssqlRepository.php +++ b/src/Repository/MssqlRepository.php @@ -2,7 +2,7 @@ namespace Ulmus\Repository; -use Ulmus\{ Repository, Query }; +use Ulmus\{Repository, Query, Ulmus, Common}; class MssqlRepository extends Repository { @@ -16,7 +16,7 @@ class MssqlRepository extends Repository { # an order by is mandatory for mssql offset/limit if ( null === $order = $this->queryBuilder->getFragment(Query\OrderBy::class) ) { $this->orderBy("(SELECT 0)"); - } + } $mssqlOffset = new \Ulmus\Query\MsSQL\Offset(); @@ -40,4 +40,8 @@ class MssqlRepository extends Repository { } } + protected function serverRequestCountRepository() : Repository + { + return new static($this->entityClass, $this->alias, $this->adapter); + } } diff --git a/src/SearchRequest/SearchRequestPaginationTrait.php b/src/SearchRequest/SearchRequestPaginationTrait.php index 912b862..50a3e96 100644 --- a/src/SearchRequest/SearchRequestPaginationTrait.php +++ b/src/SearchRequest/SearchRequestPaginationTrait.php @@ -23,7 +23,7 @@ trait SearchRequestPaginationTrait { public function offset(): int { - return abs( ( $this->page - 1 ) * $this->limit() ); + return (int) ( abs( ( $this->page - 1 ) * $this->limit() ) ); } public function pagination(int $page, int $itemCount) : void diff --git a/src/Ulmus.php b/src/Ulmus.php index 07fa13c..5094771 100644 --- a/src/Ulmus.php +++ b/src/Ulmus.php @@ -21,7 +21,6 @@ abstract class Ulmus public static function iterateQueryBuilder(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null) : Generator { $rendered = $queryBuilder->render(); - $statement = ( $adapter ?: static::$defaultAdapter )->connector()->select($rendered, $queryBuilder->parameters ?? []); $i = 0; @@ -81,12 +80,18 @@ abstract class Ulmus public static function runInsertQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null) { - return static::runQuery($queryBuilder, $adapter); + $return = static::connector($adapter)->runInsertQuery($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? [])); + $queryBuilder->reset(); + + return $return; } public static function runUpdateQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null) { - return static::runQuery($queryBuilder, $adapter); + $return = static::connector($adapter)->runUpdateQuery($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? [])); + $queryBuilder->reset(); + + return $return; } public static function runDeleteQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null)