diff --git a/src/Annotation/Property/Relation.php b/src/Annotation/Property/Relation.php index 76df8a8..5abf696 100644 --- a/src/Annotation/Property/Relation.php +++ b/src/Annotation/Property/Relation.php @@ -30,7 +30,11 @@ class Relation implements \Ulmus\Annotation\Annotation { } public function entity() { - $e = $this->entity; + try { + $e = $this->entity; + } catch (\Throwable $ex) { + throw new \Exception("Your @Relation annotation seems to be missing an `entity` entry."); + } return new $e(); } diff --git a/src/Common/EntityField.php b/src/Common/EntityField.php index 1d63f2d..a2d5d56 100644 --- a/src/Common/EntityField.php +++ b/src/Common/EntityField.php @@ -27,6 +27,7 @@ class EntityField { $name = $this->entityResolver->searchFieldAnnotation($this->name, new Field() )->name ?? $this->name; + #return $useAlias ? "{$this->alias}.`{$this->name}`" : "`{$this->name}`"; <-SQL return $useAlias ? "{$this->alias}.\"{$name}\"" : "\"{$name}\""; } diff --git a/src/Common/PdoObject.php b/src/Common/PdoObject.php index 2b9d518..e481aca 100644 --- a/src/Common/PdoObject.php +++ b/src/Common/PdoObject.php @@ -16,8 +16,13 @@ class PdoObject extends PDO { return $statement; } } catch (\PDOException $e) { - debogueur($sql, $parameters); - throw $e; + switch ( $e->getCode() ) { + case 42000: + throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters)); + + default: + throw $e; + } } } @@ -27,8 +32,13 @@ class PdoObject extends PDO { return $this->execute($statement, $parameters, true); } } catch (\PDOException $e) { - debogueur($sql, $parameters); - throw $e; + switch ( $e->getCode() ) { + case 42000: + throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters)); + + default: + throw $e; + } } return null; diff --git a/src/EntityTrait.php b/src/EntityTrait.php index c74b32d..2638954 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -196,8 +196,21 @@ trait EntityTrait { foreach($entityResolver->fieldList() as $key => $field) { $annotation = $entityResolver->searchFieldAnnotation($key, new Field() ); - if ( isset($this->$key) ) { - $dataset[ $annotation->name ?? $key ] = is_object($this->$key) ? Ulmus::convertObject($this->$key) : $this->$key; + 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; + + default: + $dataset[$realKey] = $this->$key; + } } elseif ( $field['nullable'] ) { $dataset[ $annotation->name ?? $key ] = null; diff --git a/src/Query/Fragment.php b/src/Query/Fragment.php index d17c3de..7754a2c 100644 --- a/src/Query/Fragment.php +++ b/src/Query/Fragment.php @@ -10,6 +10,6 @@ abstract class Fragment { protected function renderSegments(array $segments, string $glue = " ") : string { - return implode($glue, array_filter($segments)); + return implode($glue, array_filter($segments, function($i) { return ! is_null($i) && $i !== false && $i !== ""; })); } } diff --git a/src/Query/Insert.php b/src/Query/Insert.php index 71da301..e1b9083 100644 --- a/src/Query/Insert.php +++ b/src/Query/Insert.php @@ -33,6 +33,6 @@ class Insert extends Fragment { protected function renderTable() : string { - return $this->table; + return "`$this->table`"; } } diff --git a/src/Query/Limit.php b/src/Query/Limit.php index 346bc3e..244e8e6 100644 --- a/src/Query/Limit.php +++ b/src/Query/Limit.php @@ -19,8 +19,12 @@ class Limit extends Fragment { public function render() : string { + if ( $this->limit < 0 ) { + throw new \Exception("An error occured trying to render the LIMIT fragment ; given value has to be > 0. Received {$this->limit}"); + } + return $this->renderSegments([ - static::SQL_TOKEN, $this->limit + static::SQL_TOKEN, abs($this->limit) ]); } } diff --git a/src/Query/Offset.php b/src/Query/Offset.php index eb22c56..91d861c 100644 --- a/src/Query/Offset.php +++ b/src/Query/Offset.php @@ -13,11 +13,16 @@ class Offset extends Fragment { public function set($offset) : self { $this->offset = $offset; + return $this; } public function render() : string { + if ( $this->offset < 0 ) { + throw new \Exception("An error occured trying to render the OFFSET fragment ; given value has to be > 0. Received {$this->offset}"); + } + return $this->renderSegments([ static::SQL_TOKEN, $this->offset, ]); diff --git a/src/Query/OrderBy.php b/src/Query/OrderBy.php index cca9aca..5e62ad2 100644 --- a/src/Query/OrderBy.php +++ b/src/Query/OrderBy.php @@ -12,12 +12,14 @@ class OrderBy extends Fragment { public function set(array $order) : self { $this->orderBy = $order; + return $this; } public function add(string $field, ? string $direction = null) : self { $this->orderBy[] = [ $field, $direction ]; + return $this; } @@ -25,6 +27,7 @@ class OrderBy extends Fragment { { $list = array_map(function($item) { list($field, $direction) = $item; + return $field . ( $direction ? " $direction" : "" ); }, $this->orderBy); diff --git a/src/Query/Update.php b/src/Query/Update.php index d825a40..dfd0050 100644 --- a/src/Query/Update.php +++ b/src/Query/Update.php @@ -24,12 +24,12 @@ class Update extends Fragment { 'UPDATE', ( $this->priority ?? false ), ( $this->ignore ? 'IGNORE' : false ), - $this->table, + $this->renderTable(), ]); } protected function renderTable() : string { - return $this->table; + return "`$this->table`"; } } diff --git a/src/Query/Values.php b/src/Query/Values.php index b7e8416..45fc348 100644 --- a/src/Query/Values.php +++ b/src/Query/Values.php @@ -33,7 +33,7 @@ class Values extends Fragment { public function render() : string { $this->queryBuilder->addValues($this->flattenRowsArray()); - + return $this->renderSegments([ 'VALUES', $this->renderParameterPlaceholders(), ]); diff --git a/src/Query/Where.php b/src/Query/Where.php index 00d656b..abd5a74 100644 --- a/src/Query/Where.php +++ b/src/Query/Where.php @@ -53,7 +53,7 @@ class Where extends Fragment { public function render() : string { $stack = []; - + foreach ($this->conditionList ?? [] as $key => $item) { if ( $item instanceof Where ) { if ( $item->conditionList ?? false ) { diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index a7d1ffd..af8b6ed 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -143,7 +143,13 @@ class QueryBuilder public function open(string $condition = Query\Where::CONDITION_AND) : self { - if ( null !== ($this->where ?? false) ) { + if ( null !== ($this->where ?? null) ) { + $this->where->conditionList[] = $new = new Query\Where($this, $condition); + $this->where = $new; + } + else { + $this->where = new Query\Where($this, $condition); + $this->push($this->where); $this->where->conditionList[] = $new = new Query\Where($this, $condition); $this->where = $new; } @@ -153,7 +159,13 @@ class QueryBuilder public function close() : self { - if ( null !== ($this->where ?? false) && $this->where->parent ) { + 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; } @@ -171,6 +183,7 @@ class QueryBuilder } $this->whereConditionOperator = $operator; + $where->add($field, $value, $operator, $condition, $not); return $this; @@ -200,7 +213,6 @@ class QueryBuilder public function groupBy() : self { - return $this; } @@ -242,8 +254,6 @@ class QueryBuilder 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); $this->push($join); $join->set($type, $table, $field, $value); diff --git a/src/Repository.php b/src/Repository.php index 9219694..0f91da9 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -214,6 +214,32 @@ class Repository return $this; } + public function wheres(array $fieldValues, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + foreach($fieldValues as $field => $value) { + if ( is_array($value) ) { + switch ($value[1]) { + case Query\Where::CONDITION_AND: + $this->where($field, $value[0], $operator); + break; + + case Query\Where::CONDITION_OR: + $this->or($field, $value[0], $operator); + break; + + case Query\Where::CONDITION_NOT: + $this->notWhere($field, $value[0], $operator); + break; + } + } + else { + $this->where($field, $value, $operator); + } + } + + return $this; + } + public function and($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self { return $this->where($field, $value, $operator); @@ -233,9 +259,9 @@ class Repository return $this; } - public function orNot($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + public function orNot($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self { - $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR, true); + $this->queryBuilder->notWhere($field, $value, $operator, Query\Where::CONDITION_OR, true); return $this; } @@ -296,18 +322,51 @@ class Repository public function like($field, $value) : self { - $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_LIKE, Query\Where::CONDITION_AND); + $this->where($field, $value, Query\Where::OPERATOR_LIKE); + + return $this; + } + + public function orLike($field, $value) : self + { + $this->or($field, $value, Query\Where::OPERATOR_LIKE); return $this; } public function notLike($field, $value) : self { - $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_LIKE, Query\Where::CONDITION_AND, true); + $this->notWhere($field, $value, Query\Where::OPERATOR_LIKE); return $this; } + public function likes(array $fieldValues, string $condition = Query\Where::CONDITION_AND) : self + { + foreach($fieldValues as $field => $value) { + if ( is_array($value) ) { + switch ($value[1]) { + case Query\Where::CONDITION_AND: + $this->like($field, $value[0]); + break; + + case Query\Where::CONDITION_OR: + $this->orLike($field, $value[0]); + break; + + case Query\Where::CONDITION_NOT: + $this->notLike($field, $value[0]); + break; + } + } + else { + $this->like($field, $value); + } + } + + return $this; + } + public function match() : self { @@ -330,10 +389,15 @@ class Repository public function groupBy() : self { - #$this->queryBuilder->groupBy(); return $this; } + public function groups(array $groups) : self + { + # foreach($this->groups) + return $this; + } + public function orderBy($field, ? string $direction = null) : self { $this->queryBuilder->orderBy($field, $direction); @@ -341,6 +405,16 @@ class Repository return $this; } + public function orders(array $orderList) : self + { + foreach($orderList as $field => $direction) { + $this->queryBuilder->orderBy($field, $direction); + } + + return $this; + } + + public function limit(int $value) : self { $this->queryBuilder->limit($value); @@ -375,7 +449,25 @@ class Repository return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value); } - + + public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self + { + $searchRequest->count = $searchRequest->filter( clone $this ) + ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) + ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) + ->orders($searchRequest->orders()) + ->groups($searchRequest->groups()) + ->count(); + + return $searchRequest->filter($this) + ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) + ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) + ->orders($searchRequest->orders()) + ->groups($searchRequest->groups()) + ->offset($searchRequest->offset()) + ->limit($searchRequest->limit()); + } + public function collectionFromQuery(? string $entityClass = null) : EntityCollection { diff --git a/src/SearchRequest/SearchRequestInterface.php b/src/SearchRequest/SearchRequestInterface.php new file mode 100644 index 0000000..b2ab579 --- /dev/null +++ b/src/SearchRequest/SearchRequestInterface.php @@ -0,0 +1,21 @@ +limit; + } + + public function offset(): int + { + return abs( ( $this->page - 1 ) * $this->limit() ); + } + + public function pagination(int $page, int $itemCount) : void + { + $this->count = $itemCount; + $this->page = $page; + } + + public function pageCount() : int + { + return ceil($this->count / $this->limit()); + } + + public function hasPagination() : int + { + return $this->pageCount() > 1; + } +} diff --git a/src/SearchRequest/SearchableInterface.php b/src/SearchRequest/SearchableInterface.php new file mode 100644 index 0000000..bbdc1db --- /dev/null +++ b/src/SearchRequest/SearchableInterface.php @@ -0,0 +1,11 @@ +closeCursor(); + $queryBuilder->reset(); return [ @@ -70,12 +71,17 @@ 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); } + public static function encodeArray(array $array) + { + return json_encode($array); + } + public static function registerAdapter(ConnectionAdapter $adapter, bool $default = false) : void { if ($default) {