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();
}