- Added caching mechanism as an event listener
This commit is contained in:
1 parent
b601939459
commit
9eed2fe1a8
25 files changed
+607
-402
No files matched your search
+53
-372
@@ -5,25 +5,27 @@ 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, Repository\ConditionTrait, Repository\EscapeTrait;
|
||||
use EventTrait, CacheTrait, Repository\ConditionTrait, Repository\EscapeTrait, Repository\QueryBuildingTrait;
|
||||
|
||||
const DEFAULT_ALIAS = "this";
|
||||
|
||||
public ? ConnectionAdapter $adapter;
|
||||
|
||||
public string $alias;
|
||||
|
||||
public string $entityClass;
|
||||
|
||||
public array $events = [];
|
||||
|
||||
protected QueryBuilder\QueryBuilderInterface $queryBuilder;
|
||||
public ? ConnectionAdapter $adapters;
|
||||
|
||||
public readonly QueryBuilder\QueryBuilderInterface $queryBuilder;
|
||||
|
||||
protected EntityResolver $entityResolver;
|
||||
|
||||
@@ -33,10 +35,21 @@ class Repository implements RepositoryInterface
|
||||
$this->entityClass = $entity;
|
||||
$this->alias = $alias;
|
||||
$this->entityResolver = Ulmus::resolveEntity($entity);
|
||||
$this->adapter = $adapter ?? $this->entityResolver->databaseAdapter();
|
||||
|
||||
$queryBuilder = $this->adapter->adapter()->queryBuilderClass();
|
||||
$this->queryBuilder = new $queryBuilder();
|
||||
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()
|
||||
@@ -113,6 +126,8 @@ class Repository implements RepositoryInterface
|
||||
|
||||
public function deleteAll()
|
||||
{
|
||||
$this->eventExecute(Event\Query\Delete::class, $this);
|
||||
|
||||
return $this->deleteSqlQuery()->runDeleteQuery();
|
||||
}
|
||||
|
||||
@@ -139,6 +154,8 @@ class Repository implements RepositoryInterface
|
||||
else {
|
||||
$pkField = key($primaryKeyDefinition);
|
||||
|
||||
$this->eventExecute(Event\Query\Delete::class, $this, $entity);
|
||||
|
||||
return $this->deleteFromPk($entity->$pkField);
|
||||
}
|
||||
}
|
||||
@@ -193,6 +210,8 @@ class Repository implements RepositoryInterface
|
||||
|
||||
$entity->entityFillFromDataset($dataset, true);
|
||||
|
||||
$this->eventExecute(Event\Query\Insert::class, $this, $entity, $dataset, $replace);
|
||||
|
||||
return (bool) ( $pkValue ?? $pdoObject->lastInsertId );
|
||||
}
|
||||
else {
|
||||
@@ -211,6 +230,9 @@ class Repository implements RepositoryInterface
|
||||
|
||||
$entity->entityFillFromDataset($dataset, true);
|
||||
|
||||
# $fieldsAndValue ??= &$dataset;
|
||||
$this->eventExecute(Event\Query\Update::class, $this, $entity, $dataset, $replace);
|
||||
|
||||
return $update ? (bool) $update->rowCount : false;
|
||||
}
|
||||
}
|
||||
@@ -229,64 +251,6 @@ class Repository implements RepositoryInterface
|
||||
return $changed;
|
||||
}
|
||||
|
||||
public function insertAll(EntityCollection|array $collection, int $size = 1000) : int
|
||||
{
|
||||
if ( empty($collection) ) {
|
||||
return 0;
|
||||
}
|
||||
elseif ( is_array($collection) ) {
|
||||
$collection = $this->entityClass::entityCollection($collection);
|
||||
}
|
||||
|
||||
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 ( ! $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;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function replace(object|array $entity, ? array $fieldsAndValue = null) : bool
|
||||
{
|
||||
return $this->save($entity, $fieldsAndValue, true);
|
||||
@@ -303,35 +267,34 @@ class Repository implements RepositoryInterface
|
||||
return $changed;
|
||||
}
|
||||
|
||||
public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self
|
||||
|
||||
public function createTable() : mixed
|
||||
{
|
||||
$schema = $schema ?: $this->entityResolver->schemaName();
|
||||
$this->eventExecute(Event\Query\Create::class, $this);
|
||||
|
||||
$this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $this->escapeIdentifier($alias ?: $this->alias), $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null);
|
||||
|
||||
$this->finalizeQuery();
|
||||
|
||||
# ??? $result = Ulmus::runSelectQuery($this->queryBuilder, $this->adapter);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createTable()
|
||||
{
|
||||
return $this->createSqlQuery()->runQuery();
|
||||
}
|
||||
|
||||
public function alterTable(array $fields)
|
||||
public function alterTable(array $fields) : mixed
|
||||
{
|
||||
$this->eventExecute(Event\Query\Alter::class, $this, $fields);
|
||||
|
||||
return $this->alterSqlQuery($fields)->runQuery();
|
||||
}
|
||||
|
||||
public function listTables(? string $database = null)
|
||||
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)
|
||||
public function listColumns(? string $table = null) : EntityCollection
|
||||
{
|
||||
$table ??= $this->entityResolver->tableName();
|
||||
|
||||
@@ -358,11 +321,8 @@ class Repository implements RepositoryInterface
|
||||
|
||||
return (string) $e1 !== (string) $e2;
|
||||
});
|
||||
|
||||
# return array_diff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset );
|
||||
}
|
||||
|
||||
|
||||
public function generateWritableDataset(object $entity, bool $oldValues = false) : array
|
||||
{
|
||||
$intersect = [];
|
||||
@@ -370,7 +330,7 @@ class Repository implements RepositoryInterface
|
||||
$dataset = $this->generateDatasetDiff($entity, $oldValues);
|
||||
|
||||
foreach($dataset as $field => $value) {
|
||||
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, [ Field::class, Field::class ], false)->readonly ?? false ) ) {
|
||||
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, Field::class, false)->readonly ?? false ) ) {
|
||||
$intersect[$field] = $field;
|
||||
}
|
||||
}
|
||||
@@ -389,15 +349,6 @@ class Repository implements RepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
public function removeQueryFragment(null|Query\QueryFragmentInterface|string|array $fragment) : self
|
||||
{
|
||||
foreach((array) $fragment as $item) {
|
||||
$this->queryBuilder->removeFragment($item);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function selectEntity(string $entity, string $alias, string $prependField = "") : self
|
||||
{
|
||||
$prependField and ($prependField .= "$");
|
||||
@@ -431,167 +382,6 @@ class Repository implements RepositoryInterface
|
||||
);
|
||||
}
|
||||
|
||||
public function select(array|string|\Stringable $fields, bool $distinct = false) : self
|
||||
{
|
||||
$this->queryBuilder->select($fields, $distinct);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function distinct(array|string|\Stringable $fields) : self
|
||||
{
|
||||
$this->queryBuilder->select($fields);
|
||||
$this->queryBuilder->getFragment(Query\Select::class)->distinct = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function insert(array $fieldlist, string $table, string $alias, ? string $schema, bool $replace = false) : self
|
||||
{
|
||||
$this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $this->escapeIdentifier($alias), $this->escapedDatabase(), $schema, $replace);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function values(array $dataset) : self
|
||||
{
|
||||
$this->queryBuilder->values($dataset);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function update(string $table, string $alias, ? string $schema) : self
|
||||
{
|
||||
$this->queryBuilder->update($this->escapeTable($table), $alias ? $this->escapeIdentifier($alias) : null, $this->escapedDatabase(), $schema);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set(array $dataset) : self
|
||||
{
|
||||
$keys = array_keys($dataset);
|
||||
$escapedFields = array_combine($keys, array_map([ $this, 'escapeField' ], $keys));
|
||||
|
||||
$this->queryBuilder->set($dataset, $escapedFields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete(...$args) : self
|
||||
{
|
||||
$this->queryBuilder->delete();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function from(string $table, ? string $alias, ? string $schema) : self
|
||||
{
|
||||
$this->queryBuilder->from($this->escapeTable($table), $alias ? $this->escapeIdentifier($alias) : null, $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function join(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
|
||||
{
|
||||
$join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $alias ? $this->escapeIdentifier($alias) : null);
|
||||
|
||||
if ( $callback ) {
|
||||
$callback($join);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function outerJoin(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
|
||||
{
|
||||
$join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $alias ? $this->escapeIdentifier($alias) : null);
|
||||
|
||||
if ( $callback ) {
|
||||
$callback($join);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function match() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function notMatch() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function between() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function notBetween() : self
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function groupBy(string|\Stringable $field) : self
|
||||
{
|
||||
$this->queryBuilder->groupBy($field);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function groups(array $groups) : self
|
||||
{
|
||||
foreach($groups as $field ) {
|
||||
$this->groupBy($field);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy(string|object $field, ? string $direction = null) : self
|
||||
{
|
||||
$this->queryBuilder->orderBy($field, $direction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
# @UNTESTED
|
||||
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) {
|
||||
if (is_numeric($field)) {
|
||||
$this->orderBy($direction);
|
||||
}
|
||||
else {
|
||||
# Associative array with direction
|
||||
$this->orderBy($field, $direction);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit(int $value) : self
|
||||
{
|
||||
$this->queryBuilder->limit($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function offset(int $value) : self
|
||||
{
|
||||
$this->queryBuilder->offset($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/* @TODO */
|
||||
public function commit() : self
|
||||
{
|
||||
@@ -873,17 +663,21 @@ class Repository implements RepositoryInterface
|
||||
$entityCollection = $entityClass::entityCollection();
|
||||
|
||||
$this->finalizeQuery();
|
||||
|
||||
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
|
||||
|
||||
$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->resetVirtualProperties()->entityFillFromDataset($entityData) );
|
||||
$entityCollection->append( $entity->entityFillFromDataset($entityData) );
|
||||
}
|
||||
|
||||
$this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $entityCollection);
|
||||
$this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $this, $entityCollection);
|
||||
|
||||
return $entityCollection;
|
||||
}
|
||||
@@ -925,110 +719,6 @@ class Repository implements RepositoryInterface
|
||||
return Ulmus::runDeleteQuery($this->queryBuilder, $this->adapter);
|
||||
}
|
||||
|
||||
public function resetQuery() : self
|
||||
{
|
||||
$this->queryBuilder->reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function insertSqlQuery(array $dataset, bool $replace = false) : self
|
||||
{
|
||||
if ( null === $insert = $this->queryBuilder->getFragment(Query\Insert::class) ) {
|
||||
$this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName(), $replace);
|
||||
}
|
||||
else {
|
||||
$insert->replace = $replace;
|
||||
}
|
||||
|
||||
$this->values($dataset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function updateSqlQuery(array $dataset) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Update::class) ) {
|
||||
$this->update($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
$this->set($dataset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function selectSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
|
||||
$fields = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true);
|
||||
$this->select($this->entityClass::fields(array_map(fn($f) => $f->object->name ?? $f->name, $fields)));
|
||||
}
|
||||
|
||||
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
||||
$this->from($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function deleteSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Delete::class) ) {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
||||
$this->from($this->entityResolver->tableName(), null, $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) {
|
||||
$this->queryBuilder->create($this->adapter->adapter(), $this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function alterSqlQuery(array $fields) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) {
|
||||
$this->queryBuilder->alter($this->adapter->adapter(), $fields, $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showDatabasesSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showDatabases();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showTablesSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showTables();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showColumnsSqlQuery(string $table) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
||||
$this->queryBuilder->showColumns($table);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function fromRow($row) : self
|
||||
{
|
||||
|
||||
@@ -1039,15 +729,6 @@ class Repository implements RepositoryInterface
|
||||
|
||||
}
|
||||
|
||||
public function getSqlQuery(bool $flush = true) : string
|
||||
{
|
||||
$result = $this->queryBuilder->render();
|
||||
|
||||
$flush and $this->queryBuilder->reset();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function instanciateEntityCollection(...$arguments) : EntityCollection
|
||||
{
|
||||
return $this->entityClass::entityCollection(...$arguments);
|
||||
|
||||
Reference in new issue
Block a user