- Add base repository interfaces and abstract classes - Implement separated read/write repositories for Domain and Username models - Add intelligent query caching with automatic invalidation - Include cache management service and CLI commands - Add comprehensive configuration for cache TTL and monitoring - Enhance performance through optimized data access patterns
336 lines
8.9 KiB
PHP
336 lines
8.9 KiB
PHP
<?php
|
|
|
|
namespace App\Repositories;
|
|
|
|
use App\Repositories\Contracts\ReadRepositoryInterface;
|
|
use Illuminate\Cache\CacheManager;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
abstract class BaseRepository implements ReadRepositoryInterface
|
|
{
|
|
protected Model $model;
|
|
|
|
protected CacheManager $cache;
|
|
|
|
protected Builder $query;
|
|
|
|
protected array $withRelations = [];
|
|
|
|
protected array $withCountRelations = [];
|
|
|
|
protected string $cacheTag;
|
|
|
|
protected int $cacheTtl = 3600; // 1 hour default
|
|
|
|
protected bool $cachingEnabled = true;
|
|
|
|
public function __construct(Model $model, CacheManager $cache)
|
|
{
|
|
$this->model = $model;
|
|
$this->cache = $cache;
|
|
$this->query = $model->newQuery();
|
|
$this->cacheTag = $this->getCacheTag();
|
|
}
|
|
|
|
abstract protected function getCacheTag(): string;
|
|
|
|
protected function getCacheKey(string $method, array $parameters = []): string
|
|
{
|
|
$key = $this->cacheTag.':'.$method;
|
|
|
|
if (! empty($parameters)) {
|
|
$key .= ':'.md5(serialize($parameters));
|
|
}
|
|
|
|
$key .= ':'.md5(serialize($this->withRelations));
|
|
$key .= ':'.md5(serialize($this->withCountRelations));
|
|
|
|
return $key;
|
|
}
|
|
|
|
protected function executeQuery(callable $callback, ?string $cacheKey = null, ?int $ttl = null)
|
|
{
|
|
if (! $this->cachingEnabled || $cacheKey === null) {
|
|
return $callback();
|
|
}
|
|
|
|
$ttl = $ttl ?? $this->cacheTtl;
|
|
|
|
return $this->cache->tags([$this->cacheTag])->remember($cacheKey, $ttl, $callback);
|
|
}
|
|
|
|
protected function resetQuery(): void
|
|
{
|
|
$this->query = $this->model->newQuery();
|
|
$this->withRelations = [];
|
|
$this->withCountRelations = [];
|
|
}
|
|
|
|
public function findById(int $id, array $columns = ['*']): ?Model
|
|
{
|
|
$cacheKey = $this->getCacheKey('find_by_id', ['id' => $id, 'columns' => $columns]);
|
|
|
|
return $this->executeQuery(function () use ($id, $columns) {
|
|
$result = $this->query->find($id, $columns);
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey);
|
|
}
|
|
|
|
public function findBy(string $column, mixed $value, array $columns = ['*']): ?Model
|
|
{
|
|
$cacheKey = $this->getCacheKey('find_by', [$column => $value, 'columns' => $columns]);
|
|
|
|
return $this->executeQuery(function () use ($column, $value, $columns) {
|
|
$result = $this->query->where($column, $value)->first($columns);
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey);
|
|
}
|
|
|
|
public function all(array $columns = ['*']): Collection
|
|
{
|
|
$cacheKey = $this->getCacheKey('all', ['columns' => $columns]);
|
|
|
|
return $this->executeQuery(function () use ($columns) {
|
|
$result = $this->query->get($columns);
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey);
|
|
}
|
|
|
|
public function get(array $columns = ['*']): Collection
|
|
{
|
|
$cacheKey = $this->getCacheKey('get', ['columns' => $columns]);
|
|
|
|
return $this->executeQuery(function () use ($columns) {
|
|
$result = $this->query->get($columns);
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey);
|
|
}
|
|
|
|
public function paginate(int $perPage = 15, array $columns = ['*'], string $pageName = 'page'): LengthAwarePaginator
|
|
{
|
|
$page = request()->input($pageName, 1);
|
|
$cacheKey = $this->getCacheKey('paginate', [
|
|
'per_page' => $perPage,
|
|
'columns' => $columns,
|
|
'page' => $page,
|
|
]);
|
|
|
|
return $this->executeQuery(function () use ($perPage, $columns, $pageName) {
|
|
$result = $this->query->paginate($perPage, $columns, $pageName);
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey, 1800); // 30 minutes for pagination
|
|
}
|
|
|
|
public function whereIn(string $column, array $values, array $columns = ['*']): Collection
|
|
{
|
|
$cacheKey = $this->getCacheKey('where_in', [$column => $values, 'columns' => $columns]);
|
|
|
|
return $this->executeQuery(function () use ($column, $values, $columns) {
|
|
$result = $this->query->whereIn($column, $values)->get($columns);
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey);
|
|
}
|
|
|
|
public function where(string $column, mixed $operator, mixed $value = null): static
|
|
{
|
|
if (func_num_args() === 2) {
|
|
$this->query->where($column, $operator);
|
|
} else {
|
|
$this->query->where($column, $operator, $value);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function whereNull(string $column): static
|
|
{
|
|
$this->query->whereNull($column);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function whereNotNull(string $column): static
|
|
{
|
|
$this->query->whereNotNull($column);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function whereBetween(string $column, array $values): static
|
|
{
|
|
$this->query->whereBetween($column, $values);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function orderBy(string $column, string $direction = 'asc'): static
|
|
{
|
|
$this->query->orderBy($column, $direction);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function limit(int $limit): static
|
|
{
|
|
$this->query->limit($limit);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function with(array $relations): static
|
|
{
|
|
$this->withRelations = array_merge($this->withRelations, $relations);
|
|
$this->query->with($relations);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function withCount(array $relations): static
|
|
{
|
|
$this->withCountRelations = array_merge($this->withCountRelations, $relations);
|
|
$this->query->withCount($relations);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function exists(): bool
|
|
{
|
|
$cacheKey = $this->getCacheKey('exists');
|
|
|
|
return $this->executeQuery(function () {
|
|
$result = $this->query->exists();
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey, 300); // 5 minutes for exists check
|
|
}
|
|
|
|
public function count(): int
|
|
{
|
|
$cacheKey = $this->getCacheKey('count');
|
|
|
|
return $this->executeQuery(function () {
|
|
$result = $this->query->count();
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey, 600); // 10 minutes for count
|
|
}
|
|
|
|
public function first(array $columns = ['*']): ?Model
|
|
{
|
|
$cacheKey = $this->getCacheKey('first', ['columns' => $columns]);
|
|
|
|
return $this->executeQuery(function () use ($columns) {
|
|
$result = $this->query->first($columns);
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey);
|
|
}
|
|
|
|
public function firstWhere(string $column, mixed $operator, mixed $value = null): ?Model
|
|
{
|
|
$cacheKey = $this->getCacheKey('first_where', [$column => $operator, 'value' => $value]);
|
|
|
|
return $this->executeQuery(function () use ($column, $operator, $value) {
|
|
if (func_num_args() === 2) {
|
|
$result = $this->query->firstWhere($column, $operator);
|
|
} else {
|
|
$result = $this->query->firstWhere($column, $operator, $value);
|
|
}
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey);
|
|
}
|
|
|
|
public function pluck(string $column, ?string $key = null): Collection
|
|
{
|
|
$cacheKey = $this->getCacheKey('pluck', ['column' => $column, 'key' => $key]);
|
|
|
|
return $this->executeQuery(function () use ($column, $key) {
|
|
$result = $this->query->pluck($column, $key);
|
|
$this->resetQuery();
|
|
|
|
return $result;
|
|
}, $cacheKey);
|
|
}
|
|
|
|
public function clearCache(): void
|
|
{
|
|
$this->cache->tags([$this->cacheTag])->flush();
|
|
}
|
|
|
|
public function clearCacheForModel(Model $model): void
|
|
{
|
|
$this->cache->tags([$this->cacheTag])->flush();
|
|
}
|
|
|
|
public function disableCaching(): static
|
|
{
|
|
$this->cachingEnabled = false;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function enableCaching(): static
|
|
{
|
|
$this->cachingEnabled = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setCacheTtl(int $ttl): static
|
|
{
|
|
$this->cacheTtl = $ttl;
|
|
|
|
return $this;
|
|
}
|
|
|
|
protected function startTransaction(): void
|
|
{
|
|
DB::beginTransaction();
|
|
}
|
|
|
|
protected function commitTransaction(): void
|
|
{
|
|
DB::commit();
|
|
}
|
|
|
|
protected function rollbackTransaction(): void
|
|
{
|
|
DB::rollBack();
|
|
}
|
|
|
|
public function transaction(callable $callback): mixed
|
|
{
|
|
try {
|
|
$this->startTransaction();
|
|
$result = $callback();
|
|
$this->commitTransaction();
|
|
|
|
return $result;
|
|
} catch (\Throwable $e) {
|
|
$this->rollbackTransaction();
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|