- Added Events to repository and entitytrait objects. Some more work to be done on this.

- Diverses bug fixes linked to join and relations
- Field date and datetime are now outputed using given locale.
This commit is contained in:
Dave Mc Nicoll 2020-05-20 15:34:50 -04:00
parent 1839726dc0
commit 4d221859fc
15 changed files with 421 additions and 99 deletions

View File

@ -0,0 +1,31 @@
<?php
namespace Ulmus\Annotation\Property;
class Join implements \Ulmus\Annotation\Annotation {
public string $type;
public /*string|Stringable*/ $key;
public /*string|Stringable*/ $foreignKey;
public string $entity;
public string $alias;
public function __construct(? string $type = null, /*? string|Stringable*/ $key = null, /*? string|Stringable*/ $foreignKey = null)
{
if ($type !== null) {
$this->type = $type;
}
if ($key !== null) {
$this->key = $key;
}
if ($foreignKey !== null) {
$this->foreignKey = $foreignKey;
}
}
}

View File

@ -55,12 +55,17 @@ class PdoObject extends PDO {
else {
throw new \PDOException($statement->errorCode() . " - " . json_encode($statement->errorInfo()));
}
}
}
catch (\PDOException $e) {
$this->rollback();
throw $e;
}
catch (\Throwable $e) {
debogueur($statement, $parameters, $commit);
throw $e;
}
return null;
}

View File

@ -14,7 +14,7 @@ abstract class Sql {
protected array $arguments;
public function __construct(string $name, ...$arguments) {
public function __construct( $name, ...$arguments) {
$this->name = $name;
$this->arguments = $arguments;
$this->parseArguments();
@ -39,6 +39,22 @@ abstract class Sql {
}
};
}
public static function identifier(string $identifier) : object
{
return new class($identifier) {
protected string $identifier;
public function __construct(string $identifier) {
$this->identifier = $identifier;
}
public function __toString() {
return $this->identifier;
}
};
}
public static function escape($value)
{

View File

@ -9,4 +9,8 @@ class Date extends Datetime {
return $this->format("Y-m-d");
}
public function formatLocale(string $format) : string
{
return strftime($format, $this->getTimestamp());
}
}

View File

@ -30,4 +30,8 @@ class Datetime extends \DateTime implements EntityObjectInterface {
return $this->format($this->format);
}
public function formatLocale(string $format) : string
{
return strftime($format, $this->getTimestamp());
}
}

View File

@ -47,23 +47,39 @@ class EntityCollection extends \ArrayObject {
}
}
public function buildArray(string $keyColumn, /* string|callable */ $value) : array
public function buildArray(string $keyColumn, /* string|callable|null */ $value = null) : array
{
$list = [];
foreach($this as $key => $item) {
foreach($this as $item) {
switch (true) {
case is_string($value):
$list[$item->$keyColumn] = $item->$value;
case is_null($value):
$list[] = $item->$keyColumn;
break;
case is_callable($value):
$list[$item->$keyColumn] = $value($item);
break;
case is_object($value):
case is_string($value):
$value = (string) $value;
$list[$item->$keyColumn] = $item->$value;
break;
}
}
return $list;
}
public function toArray(bool $includeRelations = false) : array {
$list = [];
foreach($this as $entity) {
$list[] = $entity->toArray($includeRelations);
}
return $list;
}
}

View File

@ -8,25 +8,26 @@ use Ulmus\Repository,
Ulmus\Common\EntityField;
use Ulmus\Annotation\Classes\{ Method, Table, Collation, };
use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, };
use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, Join };
use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, };
trait EntityTrait {
use EventTrait;
/**
* @Ignore
*/
protected bool $strictEntityFieldsDeclaration = false;
protected bool $entityStrictFieldsDeclaration = false;
/**
* @Ignore
*/
protected array $unmatchedEntityDatasetFields = [];
protected array $entityDatasetUnmatchedFields = [];
/**
* @Ignore
*/
public array $datasetSource = [];
public array $entityLoadedDataset = [];
/**
* @Ignore
@ -37,94 +38,144 @@ trait EntityTrait {
# 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() );
# @TODO REFACTOR THIS CODE ASAP !
if ( $this->isLoaded() ) {
if ( $relation->entity ?? false ) {
$baseEntity = $relation->entity();
$repository = $baseEntity->repository();
if ( null !== ( $join= $entityResolver->searchFieldAnnotation($name, new Join() ) ) ) {
$vars = [];
foreach($where as $condition) {
$repository->where($condition->field, $condition->value, $condition->operator);
$entity = $join->entity ?? $entityResolver->properties[$name]['type'];
foreach($this->entityDatasetUnmatchedFields as $key => $value) {
$len = strlen( $name ) + 1;
if ( substr($key, 0, $len ) === "{$name}$" ) {
$vars[substr($key, $len)] = $value;
}
}
foreach($order as $item) {
$repository->orderBy($item->field, $item->order);
}
$field = $relation->key;
if ( method_exists($this, $filterMethod = "filterRelation$name") ) {
$this->$filterMethod($repository);
if ( [] !== $data = (array_values(array_unique($vars)) !== [ null ] ? $vars : []) ) {
return ( new $entity() )->fromArray($data);
}
}
switch( $relationType ) {
case 'onetoone':
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field );
$result = call_user_func([$repository, $relation->function]);
if ( count($result) === 0 ) {
return $baseEntity;
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() );
if ( $relation->entity ?? false ) {
$baseEntity = $relation->entity();
$repository = $baseEntity->repository();
foreach($where as $condition) {
$repository->where($condition->field, $condition->value, $condition->operator);
}
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.");
foreach($order as $item) {
$repository->orderBy($item->field, $item->order);
}
$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_");
$field = $relation->key;
}
$repository->select("{$repository->alias}.*")
->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey), $bridgeAlias)
->join(Query\Join::TYPE_INNER, $this->resolveEntity()->tableName(), $relation->bridge::field($bridgeRelation->key, $bridgeAlias), static::field($bridgeRelation->foreignKey, $relationAlias), $relationAlias)
->where( static::field($bridgeRelation->foreignKey, $relationAlias), $this->{$bridgeRelation->foreignKey} );
switch( $relationType ) {
case 'onetoone':
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field );
$this->$name = call_user_func([ $repository, $relationRelation->function ]);
if ($relation->bridgeField ?? false) {
$repository = $relationRelation->entity::repository();
$this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
$repository->select("$bridgeAlias.*")
$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);
$this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
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_");
# @TODO Rewrite to be done here, this code must move somewhere else...
$repository->select("{$repository->alias}.*")
->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey), $bridgeAlias)
->join(Query\Join::TYPE_INNER, $this->resolveEntity()->tableName(), $relation->bridge::field($bridgeRelation->key, $bridgeAlias), static::field($bridgeRelation->foreignKey, $relationAlias), $relationAlias)
->where( static::field($bridgeRelation->foreignKey, $relationAlias), $this->{$bridgeRelation->foreignKey} );
$bridgeName = $relation->bridgeField;
foreach($where as $condition) {
$repository->where($condition->field, $condition->value, $condition->operator);
}
foreach($order as $item) {
$repository->orderBy($item->field, $item->order);
}
$this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
$this->$bridgeName = $repository->collectionFromQuery($relation->bridge);
}
return $this->$name;
}
$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(), $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey), $bridgeAlias)
->join(Query\Join::TYPE_INNER, $this->resolveEntity()->tableName(), $relation->bridge::field($bridgeRelation->key, $bridgeAlias), static::field($bridgeRelation->foreignKey, $relationAlias), $relationAlias)
->where( static::field($bridgeRelation->foreignKey, $relationAlias), $this->{$bridgeRelation->foreignKey} );
foreach($where as $condition) {
$repository->where($condition->field, $condition->value, $condition->operator);
}
foreach($order as $item) {
$repository->orderBy($item->field, $item->order);
}
$bridgeName = $relation->bridgeField;
$this->$bridgeName = $repository->collectionFromQuery($relation->bridge);
}
return $this->$name;
}
return;
}
}
else {
return;
}
throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name));
}
/**
* @Ignore
*/
public function __isset(string $name) : bool
{
if ( $this->isLoaded() && static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
return true;
}
return isset($this->$name);
}
@ -145,11 +196,11 @@ trait EntityTrait {
}
if ( $field === null ) {
if ($this->strictEntityFieldsDeclaration ) {
if ($this->entityStrictFieldsDeclaration ) {
throw new \Exception("Field `$key` can not be found within your entity ".static::class);
}
else {
$this->unmatchedEntityDatasetFields[$key] = $value;
$this->entityDatasetUnmatchedFields[$key] = $value;
}
}
elseif ( is_null($value) ) {
@ -178,24 +229,30 @@ trait EntityTrait {
if ( ! $loaded ) {
#if ( $field !== null ) {
# $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
# $this->datasetSource[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!!
# $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!!
#}
$this->datasetSource = array_change_key_case($dataset, \CASE_LOWER);
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER);
}
}
return $this;
}
/**
* @Ignore
*/
public function fromArray(iterable $dataset) : self
{
return $this->entityFillFromDataset($dataset);
}
public function entityGetDataset(bool $returnSource = false) : array
{
/**
* @Ignore
*/
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array
{
if ( $returnSource ) {
return $this->datasetSource;
return $this->entityLoadedDataset;
}
$dataset = [];
@ -226,19 +283,52 @@ trait EntityTrait {
}
}
# @TODO Must fix recursive bug !
if ($includeRelations) {
foreach($entityResolver->properties as $name => $field){
$relation = $entityResolver->searchFieldAnnotation($name, new Relation() );
if ( $relation && isset($this->$name) && ($relation->entity ?? $relation->bridge) !== static::class ) {
if ( null !== $value = $this->$name ?? null ) {
if ( is_iterable($value) ) {
$list = [];
foreach($value as $entity) {
$list[] = $entity->entityGetDataset(true);
}
$dataset[$name] = $list;
}
elseif ( is_object($value) ) {
$dataset[$name] = $value->entityGetDataset(true);
}
}
}
}
}
return $dataset;
}
public function toArray() : array
/**
* @Ignore
*/
public function toArray($includeRelations = false) : array
{
return $this->entityGetDataset();
return $this->entityGetDataset($includeRelations);
}
/**
* @Ignore
*/
public function toCollection() : array
{
return new EntityCollection($this->toArray());
}
/**
* @Ignore
*/
public function isLoaded() : bool
{
if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) {
@ -250,12 +340,17 @@ trait EntityTrait {
return isset($this->$key);
}
/**
* @Ignore
*/
public function __sleep()
{
return array_keys($this->resolveEntity()->fieldList());
}
/**
* @Ignore
*/
public function jsonSerialize()
{
return $this->entityGetDataset();
@ -264,7 +359,7 @@ trait EntityTrait {
/**
* @Ignore
*/
public function resolveEntity() : EntityResolver
public static function resolveEntity() : EntityResolver
{
return Ulmus::resolveEntity(static::class);
}

View File

@ -0,0 +1,9 @@
<?php
namespace Ulmus\Event;
use Ulmus\Repository;
interface EntityRelationLoadInterface {
public function execute(string $name, Repository $repository) : Repository;
}

View File

@ -0,0 +1,9 @@
<?php
namespace Ulmus\Event;
use Ulmus\EntityCollection;
interface RepositoryCollectionFromQueryInterface {
public function execute(EntityCollection $collection) : EntityCollection;
}

25
src/EventTrait.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace Ulmus;
trait EventTrait
{
public array $eventList = [];
public function eventRegister(object $event) : void
{
$this->eventList[] = $event;
}
public function eventFromType(string $type) : array
{
return array_filter($this->eventList, fn($ev) => $ev instanceof $type);
}
public function eventExecute(string $type, ...$arguments) : void
{
foreach($this->eventFromType($type) as $event) {
call_user_func_array([ $event, 'execute'], $arguments);
}
}
}

View File

@ -47,6 +47,6 @@ class Join extends Fragment {
public function render() : string
{
return $this->renderSegments([ $this->side, static::SQL_TOKEN, $this->table, $this->alias ?? "", $this->attachment, $this->field, "=", $this->value ]);
return $this->renderSegments([ strtoupper($this->side), static::SQL_TOKEN, $this->table, $this->alias ?? "", $this->attachment, $this->field, "=", $this->value ]);
}
}

View File

@ -112,7 +112,7 @@ class Where extends Fragment {
return (in_array($this->operator, [ '!=', '<>' ]) ? Where::CONDITION_NOT . " " : "") . Where::COMPARISON_IN;
}
return $this->operator;
return $this->operator;
}
protected function value()
@ -140,7 +140,7 @@ class Where extends Fragment {
return $value->name();
}
else {
return $this->queryBuilder->addParameter($this->value);
return $this->queryBuilder->addParameter($value);
}
}

View File

@ -174,6 +174,11 @@ class QueryBuilder
public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
{
# Empty IN case
if ( [] === $value ) {
return $this;
}
if ( $this->where ?? false ) {
$where = $this->where;
}

View File

@ -6,6 +6,8 @@ use Ulmus\Common\EntityResolver;
class Repository
{
use EventTrait;
const DEFAULT_ALIAS = "this";
public ? ConnectionAdapter $adapter;
@ -17,7 +19,9 @@ class Repository
public string $alias;
public string $entityClass;
public array $events = [];
public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) {
$this->entityClass = $entity;
$this->alias = $alias;
@ -36,9 +40,9 @@ class Repository
return $this->where($field, $value)->loadOne();
}
public function loadFromPk($value, $primaryKey = "id") : ? object
public function loadFromPk($value, /* ? stringable */ $primaryKey = null) : ? object
{
return $this->loadOneFromField($primaryKey, $value);
return $primaryKey ? $this->loadOneFromField($primaryKey, $value) : $this->wherePrimaryKey($value)->loadOne();
}
public function loadAll() : EntityCollection
@ -53,6 +57,10 @@ class Repository
public function count() : int
{
if ( null !== $select = $this->queryBuilder->getFragment(Query\Select::class) ) {
$this->queryBuilder->removeFragment($select);
}
$this->select("COUNT(*)")->selectSqlQuery();
$this->finalizeQuery();
@ -79,8 +87,39 @@ class Repository
return (bool) $this->wherePrimaryKey($value)->deleteOne()->rowCount();
}
public function destroy(object $entity) : bool
{
if ( ! $this->matchEntity($entity) ) {
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
}
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
if ( $primaryKeyDefinition === null ) {
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
}
else {
$pkField = key($primaryKeyDefinition);
return $this->deleteFromPk($entity->$pkField);
}
return false;
}
public function destroyAll(EntityCollection $collection) : void
{
foreach($collection as $entity) {
$this->destroy($entity);
}
}
public function save(object $entity) : bool
{
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();
@ -124,7 +163,7 @@ class Repository
public function generateDatasetDiff(object $entity) : array
{
return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(true)) );
return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(false, true)) );
}
public function yieldAll() : \Generator
@ -294,9 +333,9 @@ class Repository
return $this;
}
public function orNotHaving($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self
public function orNotHaving($field, $value) : self
{
$this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_OR, true);
$this->queryBuilder->having($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Having::CONDITION_OR, true);
return $this;
}
@ -322,9 +361,9 @@ class Repository
return $this;
}
public function orNotIn($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
public function orNotIn($field, $value) : self
{
return $this->orNot($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR);
return $this->orNot($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR, true);
}
public function like($field, $value) : self
@ -436,11 +475,14 @@ class Repository
return $this;
}
/* @TODO */
public function commit() : self
{
return $this;
}
/* @TODO */
public function rollback() : self
{
return $this;
@ -457,6 +499,33 @@ class Repository
return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value);
}
public function withJoin(/*string|array*/ $fields) : self
{
$resolvedEntity = Ulmus::resolveEntity($this->entityClass);
$this->select("{$this->alias}.*");
foreach((array) $fields as $item) {
if ( null !== $join = $resolvedEntity->searchFieldAnnotation($item, new Annotation\Property\Join) ) {
$alias = $join->alias ?? $item;
$entity = $join->entity ?? $resolvedEntity->properties[$item]['type'];
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME) as $key => $field) {
$this->select("$alias.$key as {$alias}\${$field['name']}");
}
$key = is_string($join->key) ? $this->entityClass::field($join->key) : $join->key;
$foreignKey = is_string($join->foreignKey) ? $entity::field($join->foreignKey, $alias) : $join->foreignKey;
$this->join($join->type, $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias);
}
else {
throw new \Exception("You referenced field `$item` which do not exist or do not contain a valid @Join annotation.");
}
}
return $this;
}
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self
{
$searchRequest->count = $searchRequest->filter( clone $this )
@ -473,12 +542,12 @@ class Repository
->offset($searchRequest->offset())
->limit($searchRequest->limit());
}
public function collectionFromQuery(? string $entityClass = null) : EntityCollection
{
$class = $entityClass ?: $this->entityClass;
$entityCollection = new EntityCollection();
$entityCollection = $this->instanciateEntityCollection();
$this->selectSqlQuery();
@ -487,9 +556,20 @@ class Repository
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
$entityCollection->append( ( new $class() )->entityFillFromDataset($entityData) );
}
$this->eventExecute(Event\RepositoryCollectionFromQueryInterface::class, $entityCollection);
return $entityCollection;
}
public function arrayFromQuery() : array
{
$this->selectSqlQuery();
$this->finalizeQuery();
return Ulmus::datasetQueryBuilder($this->queryBuilder, $this->adapter);
}
public function runQuery() : ? \PDOStatement
{
@ -556,9 +636,9 @@ class Repository
}
public function table()
public function instanciateEntityCollection() : EntityCollection
{
return "REFLECT TABLE";
return new EntityCollection();
}
public function escapeTable(string $identifier) : string
@ -576,5 +656,9 @@ class Repository
return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_SCHEMA);
}
protected function matchEntity(object $entity) {
return get_class($entity) === $this->entityClass;
}
protected function finalizeQuery() : void {}
}

View File

@ -37,6 +37,25 @@ abstract class Ulmus
];
}
public static function datasetQueryBuilder(QueryBuilder $queryBuilder, ? ConnectionAdapter $adapter = null) : array
{
$rows = [];
$sql = $queryBuilder->render();
$statement = ( $adapter ?: static::$defaultAdapter )->pdo()->select($sql, $queryBuilder->parameters ?? []);
while ( $row = $statement->fetch() ) {
$rows[] = $row;
}
$statement->closeCursor();
$queryBuilder->reset();
return $rows;
}
public static function pdo(? ConnectionAdapter $adapter = null) : Common\PdoObject
{
return ( $adapter ?: static::$defaultAdapter )->pdo();