Repository#
Meritum\Database\Repository<T extends Model>
Abstract base class for all model repositories. Implements RepositoryInterface
and adds protected query-building infrastructure: scopes, pagination, and cursor pagination. Extend it and implement getModelClass().
Constructor#
public function __construct(DatabaseManagerInterface $db);Inject DatabaseManagerInterface from the container:
$kernel->define(UserRepository::class, fn($c) => new UserRepository(
$c->get(DatabaseManagerInterface::class)
));Abstract Methods#
abstract protected function getModelClass(): string;Return the fully-qualified class name of the model this repository manages:
protected function getModelClass(): string
{
return User::class;
}Public Persistence Methods#
public function save(Model $model): bool;Inserts or updates $model. Inserts when $model->isNew(). Auto-generates a UUID primary key for new string-keyed models that have no primary key set. Sets created_at and updated_at on insert; sets only updated_at on update. Calls syncOriginal() after a successful write.
public function delete(Model $model): bool;Deletes $model by its primary key. Returns true on success.
Public Finder Methods#
public function find(int|string $id): ?T;Finds a model by primary key. Returns null when not found.
public function findOrFail(int|string $id): T;Finds a model by primary key. Throws ModelNotFoundException when not found.
public function findBy(string $column, mixed $value): ?T;Finds the first model where $column equals $value. Returns null when not found.
Protected Query Methods#
These methods are intended for use inside subclass query methods.
protected function query(): SelectInterface;Returns a fresh SelectInterface scoped to the model’s table and primary key. Calling query() resets the internal query state — call it at the start of every new query chain, not mid-chain.
protected function first(): ?T;Executes the current query and returns the first result as a hydrated model, or null.
protected function firstOrFail(): T;Executes the current query and returns the first result as a hydrated model. Throws ModelNotFoundException when no result is found. Use in place of first() when the absence of a result is an error condition:
public function findBySlug(string $slug): Post
{
$this->query()->where('slug', $slug);
return $this->firstOrFail();
}protected function get(): Collection<T>;Executes the current query and returns all results as a typed Collection.
protected function count(): int;Executes the current query and returns the row count.
protected function paginate(int $perPage, int $currentPage): Paginator<T>;Executes the current query with limit/offset and returns a Paginator.
protected function cursor(int $perPage, ?string $cursor): CursorPaginator<T>;Executes the current query using keyset pagination on the primary key. Pass null for the first page; pass the nextCursor or previousCursor from a previous result for subsequent pages. Implemented as cursorBy($primaryKeyName, $perPage, $cursor).
This method calls resetOrderBy() before applying its own ORDER BY, so any prior orderBy() call — including one applied by a scope — is safely cleared rather than conflicting with it.
UUIDv4 primary keys are randomly generated and have no natural ordering, making them unsuitable as a cursor column. String primary keys default to UUIDv7, which sorts correctly — this only matters if a model overrides generateUuid() to return Uuid::v4().
protected function cursorBy(string $column, int $perPage, ?string $cursor): CursorPaginator<T>;Executes the current query using keyset pagination on an arbitrary $column instead of the primary key. The primary key is always included as a tiebreaker — sorted and compared alongside $column — so pagination stays correct even when $column has duplicate values:
public function cursorPaginateByCreatedAt(int $perPage, ?string $cursor = null): CursorPaginator
{
$this->query();
return $this->cursorBy('created_at', $perPage, $cursor);
}Scope Methods#
protected function addScope(string $name, callable $scope): void;Registers a named scope applied automatically to every query. The callable receives the SelectInterface:
$this->addScope('active', fn(SelectInterface $q) => $q->where('is_active', true));protected function withoutScope(string $name): static;Returns $this with the named scope suspended for the next query execution. The scope is re-enabled after execution.
protected function withoutScopes(): static;Returns $this with all scopes suspended for the next query execution.
Overridable Methods#
protected function generateUuid(): string;Override to change the UUID generator used for new string-keyed models. Defaults to Uuid::v7(). Override to Uuid::v4() if you specifically need randomly-ordered, non-time-correlated IDs — note that UUIDv4 is unsuitable as a primary-key cursor column:
use Meritum\Database\Support\Uuid;
protected function generateUuid(): string
{
return Uuid::v4();
}