ulmus/src/Repository.php

807 lines
29 KiB
PHP

<?php
namespace Ulmus;
use Ulmus\Attribute\Property\{
Field, OrderBy, Where, Having, Relation, Filter, Join, FilterJoin, WithJoin
};
use Psr\SimpleCache\CacheInterface;
use Ulmus\Common\EntityResolver;
use Ulmus\Entity\EntityInterface;
use Ulmus\Repository\RepositoryInterface;
use Ulmus\Repository\WithOptionEnum;
class Repository implements RepositoryInterface
{
use EventTrait, CacheTrait, Repository\ConditionTrait, Repository\EscapeTrait, Repository\QueryBuildingTrait;
const DEFAULT_ALIAS = "this";
public string $alias;
public string $entityClass;
public array $events = [];
public ? ConnectionAdapter $adapters;
public readonly QueryBuilder\QueryBuilderInterface $queryBuilder;
protected EntityResolver $entityResolver;
protected array $joined = [];
public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) {
$this->entityClass = $entity;
$this->alias = $alias;
$this->entityResolver = Ulmus::resolveEntity($entity);
if ($adapter) {
$this->adapter = $adapter;
$qbClass = $adapter->adapter()->queryBuilderClass();
$this->queryBuilder = new $qbClass();
}
else {
$this->adapter = $this->entityResolver->databaseAdapter();
$this->queryBuilder = $this->entityClass::queryBuilder();
}
if ($this->adapter->cacheObject) {
$this->attachCachingObject($this->adapter->cacheObject);
}
}
public function __clone()
{
#$this->queryBuilder = clone $this->queryBuilder;
}
public function loadOne() : ? object
{
return $this->limit(1)->selectSqlQuery()->collectionFromQuery()[0] ?? null;
}
public function loadOneFromField($field, $value) : ? object
{
return $this->where($field, $value)->loadOne();
}
public function loadFromPk($value, null|string|\Stringable $primaryKey = null) : ? object
{
return $primaryKey ? $this->loadOneFromField($primaryKey, $value) : $this->wherePrimaryKey($value)->loadOne();
}
public function loadAll () : EntityCollection
{
return $this->selectSqlQuery()->collectionFromQuery();
}
public function loadAllFromField($field, $value, $operator= Query\Where::OPERATOR_EQUAL) : EntityCollection
{
return $this->loadFromField($field, $value, $operator);
}
public function loadFromField($field, $value, $operator= Query\Where::OPERATOR_EQUAL) : EntityCollection
{
return $this->selectSqlQuery()->where($field, $value, $operator)->collectionFromQuery();
}
public function count() : int
{
$this->removeQueryFragment([ Query\Select::class, Query\OrderBy::class, ]);
if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) {
$this->select( "DISTINCT COUNT(*) OVER ()" );
}
else {
$pk = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
if ($pk && count($pk) === 1) {
$field = key($pk);
$this->select(Common\Sql::function('COUNT', Common\Sql::raw("DISTINCT " . $this->entityClass::field($field))));
}
else {
$this->select(Common\Sql::function('COUNT', Common\Sql::raw('*')));
}
}
$this->selectSqlQuery();
$this->finalizeQuery();
return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0);
}
public function exists(mixed $primaryKey) : bool
{
return $this->wherePrimaryKey($primaryKey)->count() !== 0;
}
public function deleteOne()
{
return $this->limit(1)->deleteSqlQuery()->runDeleteQuery();
}
public function deleteAll()
{
$this->eventExecute(Event\Query\Delete::class, $this);
return $this->deleteSqlQuery()->runDeleteQuery();
}
public function deleteFromPk($value) : bool
{
if ( $value !== 0 && empty($value) ) {
throw new Exception\EntityPrimaryKeyUnknown("A primary key value has to be defined to delete an item.");
}
return (bool) $this->wherePrimaryKey($value, false)->deleteOne()->rowCount;
}
public function deleteFromCompoundKeys(array $values) : bool
{
foreach($values as $field => $value) {
$this->where($field, $value);
}
return (bool) $this->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 ) {
$pkField = key($primaryKeyDefinition);
$this->eventExecute(Event\Query\Delete::class, $this, $entity);
return $this->deleteFromPk($entity->$pkField);
}
else {
$compoundKeyFields = Ulmus::resolveEntity($this->entityClass)->getCompoundKeyFields();
if ($compoundKeyFields) {
$this->eventExecute(Event\Query\Delete::class, $this, $entity);
return $this->deleteFromCompoundKeys(array_combine($compoundKeyFields->column, array_map(fn($column) => $entity->$column, $compoundKeyFields->column)));
}
else {
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
}
}
}
public function destroyAll(EntityCollection $collection) : void
{
foreach($collection as $entity) {
$this->destroy($entity);
}
}
public function clone(object|array $entity) : object|false
{
$entity = is_object($entity) ? clone $entity : $entity;
foreach(Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() as $key => $field) {
unset($entity->$key);
}
return $this->save($entity) ? $entity : false;
}
public function save(object|array $entity, ? array $fieldsAndValue = null, bool $replace = false) : bool
{
if ( is_array($entity) ) {
$entity = new $this->entityClass($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);
if (empty($dataset)) {
throw new \InvalidArgumentException("Unable to add a new row from an empty dataset.");
}
$pdoObject = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery();
if ( null !== $primaryKeyDefinition ) {
$pkField = key($primaryKeyDefinition);
if ($pdoObject->lastInsertId ) {
$dataset[$pkField] = $pdoObject->lastInsertId;
}
elseif ($replace) {
$pkValue = $dataset[$pkField];
}
}
$entity->entityFillFromDataset($dataset, true);
$this->eventExecute(Event\Query\Insert::class, $this, $entity, $dataset, $replace);
return (bool) ( $pkValue ?? $pdoObject->lastInsertId );
}
else {
if ( $primaryKeyDefinition === null ) {
if ( null === $compoundKeyFields = Ulmus::resolveEntity($this->entityClass)->getCompoundKeyFields() ) {
throw new \Exception(sprintf("No primary key or compound key found for entity %s", $this->entityClass));
}
}
$diff = $fieldsAndValue ?? $this->generateWritableDataset($entity);
# dump($diff);
if ( [] !== $diff ) {
if ($primaryKeyDefinition) {
$pkField = key($primaryKeyDefinition);
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
$this->where($pkFieldName, $dataset[$pkFieldName]);
}
else {
foreach($compoundKeyFields->column as $field) {
$this->where($field, $dataset[$field]);
}
}
$update = $this->updateSqlQuery($diff)->runUpdateQuery();
$entity->entityFillFromDataset($dataset, true);
# $fieldsAndValue ??= &$dataset;
$this->eventExecute(Event\Query\Update::class, $this, $entity, $dataset, $replace);
return $update ? (bool) $update->rowCount : false;
}
}
return false;
}
public function saveAll(EntityCollection|array $collection) : int
{
$changed = 0;
foreach ($collection as $entity) {
$this->save($entity) && $changed++;
}
return $changed;
}
public function replace(object|array $entity, ? array $fieldsAndValue = null) : bool
{
return $this->save($entity, $fieldsAndValue, true);
}
public function replaceAll(EntityCollection|array $collection) : int
{
$changed = 0;
foreach($collection as $entity) {
$this->replace($entity) && $changed++;
}
return $changed;
}
public function createTable() : mixed
{
$this->eventExecute(Event\Query\Create::class, $this);
return $this->createSqlQuery()->runQuery();
}
public function alterTable(array $fields) : mixed
{
$this->eventExecute(Event\Query\Alter::class, $this, $fields);
return $this->alterSqlQuery($fields)->runQuery();
}
public function truncateTable() : mixed
{
$this->eventExecute(Event\Query\Truncate::class, $this, $table, $alias, $schema);
return $this->truncate()->runQuery();
}
public function listTables(? string $database = null) : mixed
{
return $this->showTablesSqlQuery($database)->runQuery();
}
public function listColumns(? string $table = null) : EntityCollection
{
$table ??= $this->entityResolver->tableName();
$this->showColumnsSqlQuery($table);
return $this->collectionFromQuery(Entity\InformationSchema\Column::class);
}
public function generateDatasetDiff(object $entity, bool $oldValues = false) : array
{
$array = array_change_key_case($entity->toArray());
$dataset = array_change_key_case($entity->entityGetDataset(false, true));
return array_udiff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset, function($e1, $e2) {
if ( is_array($e1) ) {
if (is_array($e2)) {
return Ulmus::encodeArray($e1) !== Ulmus::encodeArray($e2);
}
else {
return false;
}
}
if (is_null($e1) || is_null($e2)) {
return $e1 !== $e2;
}
if ($e1 instanceof \BackedEnum) {
$e1 = $e1->value;
}
if ($e2 instanceof \BackedEnum) {
$e2 = $e2->value;
}
return (string) $e1 !== (string) $e2;
});
}
public function generateWritableDataset(object $entity, bool $oldValues = false) : array
{
$intersect = [];
$dataset = $this->generateDatasetDiff($entity, $oldValues);
foreach($dataset as $field => $value) {
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, Field::class, false)->readonly ?? false ) ) {
$intersect[$field] = $field;
}
}
return array_intersect_key($dataset, $intersect);
}
public function yield() : \Generator
{
$this->selectSqlQuery();
$this->finalizeQuery();
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
yield $this->instanciateEntity()->entityFillFromDataset($entityData);
}
}
public function selectEntity(string $entity, string $alias, string $prependField = "") : self
{
$prependField and ($prependField .= "$");
foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ])) {
$this->select(sprintf("%s.$key as {$prependField}{$field->name}", $this->escapeIdentifier($alias)));
}
}
return $this;
}
public function selectJsonEntity(string $entity, string $alias, bool $skipFrom = false) : self
{
$fieldlist = [];
foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ])) {
$fieldlist[] = $key;
$fieldlist[] = $entity::field($field->name, $this->escapeIdentifier($alias));
}
}
$this->select(
Common\Sql::function('JSON_ARRAYAGG', Common\Sql::function('JSON_OBJECT', ... $fieldlist))
);
return $skipFrom ? $this : $this->from(
$entity::resolveEntity()->tableName(), $alias, $entity::resolveEntity()->schemaName()
);
}
/* @TODO */
public function commit() : self
{
return $this;
}
/* @TODO */
public function rollback() : self
{
return $this;
}
public function wherePrimaryKey(mixed $value, null|string|bool $alias = self::DEFAULT_ALIAS) : self
{
if ( null === $primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() ) {
throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'");
}
$pkField = key($primaryKeyField);
return $this->where($this->entityClass::field($primaryKeyField[$pkField]->name ?? $pkField, $alias), $value);
}
public function withJoin(string|array $fields, array $options = []) : self
{
$canSelect = null === $this->queryBuilder->getFragment(Query\Select::class);
if ( $canSelect ) {
$select = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true);
$this->select($this->entityClass::fields(array_map(fn($f) => $f['object']->name ?? $f['name'], $select)));
}
$fields = (array) $fields;
usort($fields, fn($e1, $e2) => str_contains($e1, '.') <=> str_contains($e2, '.'));
# @TODO Apply fff annotation to this too !
foreach(array_filter($fields, fn($e) => $e && ! isset($this->joined[$e]) ) as $item) {
$this->joined[$item] = true;
$attribute = $this->entityResolver->searchFieldAnnotation($item, [ Join::class ]) ?:
$this->entityResolver->searchFieldAnnotation($item, [ Relation::class ]);
$isRelation = $attribute instanceof Relation;
if ($isRelation && ( $attribute->isManyToMany() )) {
throw new \Exception("Many-to-many relation can not be preloaded within joins.");
}
if ( $attribute ) {
$alias = $attribute->alias ?? $item;
$entity = $attribute->entity ?? $this->entityResolver->reflectedClass->getProperties(true)[$item]->getTypes()[0]->type;
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ]) ) {
$escAlias = $this->escapeIdentifier($alias);
$fieldName = $this->escapeIdentifier($key);
$name = $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Field::class ])->name ?? $field->name;
if ($canSelect) {
$this->select("$escAlias.$fieldName as $alias\${$name}");
}
}
}
$this->open();
if ( ! in_array(WithOptionEnum::SkipWhere, $options)) {
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ] ) as $condition) {
if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) {
$this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
}
}
}
if ( ! in_array(WithOptionEnum::SkipHaving, $options)) {
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ]) as $condition) {
$this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
}
}
if ( ! in_array(WithOptionEnum::SkipFilter, $options)) {
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Filter::class ]) as $filter) {
call_user_func_array([$this->entityClass, $filter->method], [$this, $item, true]);
}
}
$this->close();
$key = is_string($attribute->key) ? $this->entityClass::field($attribute->key) : $attribute->key;
$foreignKey = is_string($attribute->foreignKey) ? $entity::field($attribute->foreignKey, $alias) : $attribute->foreignKey;
$this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) {
if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) {
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) {
if ( ! is_object($condition->field) ) {
$field = $this->entityClass::field($condition->field);
}
else {
$field = clone $condition->field;
}
# Adding directly
if ( $field->entityClass === $entity ) {
$field->alias = $alias;
$join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->getValue(), $condition->operator);
}
}
}
if ( ! in_array(WithOptionEnum::SkipJoinFilter, $options) ) {
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ FilterJoin::class ]) as $filter) {
call_user_func_array([$this->entityClass, $filter->method], [$join, $item, true]);
}
}
});
}
else {
throw new \Exception("Referenced field `$item` which do not exist or do not contain a valid @Join or @Relation attribute.");
}
}
return $this;
}
public function withSubquery(string|array $fields, array $options = []) : self
{
# We skip subqueries when counting results since it should not affect the row count.
if ( $this instanceof Repository\ServerRequestCountRepository ) {
return $this;
}
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
$this->select($this->escapeIdentifier($this->alias) . ".*");
}
# Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) {
if ( $relation = $this->entityResolver->searchFieldAnnotation($item, [ Relation::class ]) ) {
$alias = $relation->alias ?? $item;
if ( $relation->isManyToMany() ) {
$entity = $relation->bridge;
$repository = $relation->bridge::repository();
$repository->queryBuilder->parent = $this->queryBuilder;
extract(Repository\RelationBuilder::relationAnnotations($item, $relation));
$repository->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $relation->bridgeField), $relationRelation->entity::field($relationRelation->foreignKey, $alias), $relation->bridgeField)
->where( $entity::field($bridgeRelation->key, $relation->bridgeField), $entity::field($bridgeRelation->foreignKey, $this->alias))
->selectJsonEntity($relationRelation->entity, $alias)->open();
}
else {
$entity = $relation->entity ?? $this->entityResolver->properties[$item]['type'];
$repository = $entity::repository()->selectJsonEntity($entity, $alias)->open();
}
# $relation->isManyToMany() and $repository->selectJsonEntity($relation->bridge, $relation->bridgeField, true);
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) {
$repository->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
}
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ] ) as $condition) {
$repository->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
}
$repository->close();
$key = is_string($relation->key) ? $this->entityClass::field($relation->key) : $relation->key;
if (! $relation->isManyToMany() ) {
$foreignKey = is_string($relation->foreignKey) ? $entity::field($relation->foreignKey, $alias) : $relation->foreignKey;
$repository->where( $foreignKey, $key);
}
$this->select("(" . $r = Common\Sql::raw($repository->queryBuilder->render() . ") as $item\$collection"));
}
else {
throw new \Exception("You referenced field `$item` which do not exist or do not contain a valid @Join attribute.");
}
}
return $this;
}
public function loadCollectionRelation(EntityCollection $collection, array|string $fields) : void
{
foreach ((array)$fields as $name) {
if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, [ Relation::class ] ))) {
$order = $this->entityResolver->searchFieldAnnotationList($name, [ OrderBy::class ]);
$where = $this->entityResolver->searchFieldAnnotationList($name, [ Where::class ]);
$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 ($baseEntityResolver->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $baseEntityResolver->searchFieldAnnotation($field->name, [ Relation\Ignore::class ])) {
$repository->select($baseEntityResolver->entityClass::field($key));
}
}
foreach ($where as $condition) {
# $repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [$this]) : $condition->getValue(), $condition->operator, $condition->condition);
$repository->where($condition->field, $condition->getValue(/* why repository sent here ??? $this */), $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;
}
$values = array_unique($values);
$repository->where($key, $values);
$results = $repository->loadAll();
if ($relation->isOneToOne()) {
foreach ($collection as $item) {
$item->$name = $results->searchOne($item->$entityProperty, $property) ?: new $baseEntity();
}
}
elseif ($relation->isOneToMany()) {
foreach ($collection as $item) {
$item->$name = $baseEntity::entityCollection();
$item->$name->mergeWith($results->searchAll($item->$entityProperty, $property));
}
}
}
}
}
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self
{
if ($count) {
$searchRequest->applyCount($this->serverRequestCountRepository());
# @TODO Handle no request to do if count is 0 here
#if ($searchRequest->count) {
$searchRequest->apply($this);
# }
}
else {
$searchRequest->apply($this);
}
return $this;
}
protected function serverRequestCountRepository() : Repository
{
return new Repository\ServerRequestCountRepository($this->entityClass, $this->alias, $this->adapter);
}
public function collectionFromQuery(? string $entityClass = null) : EntityCollection
{
$entityClass ??= $this->entityClass;
$entityCollection = $entityClass::entityCollection();
$this->finalizeQuery();
$dataset = [];
$this->eventExecute(\Ulmus\Event\Repository\CollectionFromQueryDatasetInterface::class, $this, $dataset);
foreach($dataset ?: Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
$this->eventExecute(\Ulmus\Event\Repository\CollectionFromQueryItemInterface::class, $entityData);
$entity = $this->instanciateEntity($entityClass);
$entity->loadedFromAdapter = $this->adapter->name;
$entityCollection->append( $entity->entityFillFromDataset($entityData) );
}
$this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $this, $entityCollection);
return $entityCollection;
}
public function arrayFromQuery() : array
{
$this->selectSqlQuery();
$this->finalizeQuery();
return Ulmus::datasetQueryBuilder($this->queryBuilder, $this->adapter);
}
public function runQuery() : mixed
{
$this->finalizeQuery();
return Ulmus::runQuery($this->queryBuilder, $this->adapter);
}
public function runInsertQuery() : mixed
{
$this->finalizeQuery();
return Ulmus::runInsertQuery($this->queryBuilder, $this->adapter);
}
public function runUpdateQuery() : mixed
{
$this->finalizeQuery();
return Ulmus::runUpdateQuery($this->queryBuilder, $this->adapter);
}
public function runDeleteQuery() : mixed
{
$this->finalizeQuery();
return Ulmus::runDeleteQuery($this->queryBuilder, $this->adapter);
}
protected function fromRow($row) : self
{
}
protected function fromCollection($rows) : self
{
}
public function instanciateEntityCollection(...$arguments) : EntityCollection
{
return $this->entityClass::entityCollection(...$arguments);
}
public function instanciateEntity(? string $entityClass = null, array $dataset = []) : object
{
$entityClass ??= $this->entityClass;
try {
$entity = new $entityClass($dataset);
}
catch (\Throwable $ex) {
$entity = ( new \ReflectionClass($entityClass) )->newInstanceWithoutConstructor()->fromArray($dataset);
}
$entity->initializeEntity();
return $entity;
}
public function hasFilters() : bool
{
return isset($this->queryBuilder->where);
}
protected function matchEntity(object $entity) {
return $entity::class === $this->entityClass;
}
protected function finalizeQuery() : void {}
}