定制 meritum/database 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

meritum/database

Composer 安装命令:

composer require meritum/database

包简介

Model and repository layer for the Meritum ecosystem

README 文档

README

Model and repository layer for the Meritum ecosystem. Provides an active-record style model with casting, accessors, and mutators, paired with a repository pattern for querying and persisting data.

Requirements

  • PHP 8.4+
  • georgeff/kernel ^1.7
  • georgeff/database ^1.0

Installation

composer require meritum/database

Setup

Register DatabaseModule with the kernel. It reads database credentials from environment variables.

use Meritum\Database\DatabaseModule;

$kernel->register(new DatabaseModule());

Environment Variables

Variable Default Description
DB_DRIVER pgsql, mysql, or sqlite
DB_HOST Database host
DB_PORT Database port
DB_DATABASE Database name (or file path for SQLite)
DB_USERNAME Database username
DB_PASSWORD Database password
DB_READ_HOSTS [] JSON array of read replica hostnames
DB_STICKY_WRITE true Route reads after a write to the write host
DB_PGSQL_SCHEMA public PostgreSQL schema
DB_PGSQL_SSL_MODE prefer PostgreSQL SSL mode
DB_MYSQL_CHARSET utf8mb4 MySQL charset

DB_READ_HOSTS accepts a JSON array:

DB_READ_HOSTS=["replica1.example.com","replica2.example.com"]

Models

Define a model by extending Model and setting the $table property.

use Meritum\Database\Model;

class User extends Model
{
    protected string $table = 'users';
}

Primary Keys

By default the primary key column is id with type string, and a UUIDv4 is auto-generated on insert. For auto-incrementing integer keys:

class Order extends Model
{
    protected string $table = 'orders';
    protected string $primaryKey = 'order_id';
    protected string $primaryKeyType = 'int';
    protected bool $incrementing = true;
}

Timestamps

Timestamps are enabled by default. The model automatically sets created_at on insert and updated_at on every save. To disable:

class EventLog extends Model
{
    protected string $table = 'event_logs';
    protected bool $timestamps = false;
}

To use different column names:

protected string $createdAtColumn = 'inserted_at';
protected string $updatedAtColumn = 'modified_at';

Casting

Define casts to automatically convert database values to PHP types on read, and back on write.

class Post extends Model
{
    protected string $table = 'posts';

    protected array $casts = [
        'published_at' => 'datetime',
        'view_count'   => 'int',
        'is_featured'  => 'bool',
        'metadata'     => 'json',
    ];
}

Supported cast types:

Type PHP Type Notes
int int
float float
string string
bool bool
json array Encoded as JSON string in the database
datetime DateTimeImmutable Stored as Y-m-d H:i:s
date DateTimeImmutable Stored as Y-m-d, time zeroed
timestamp DateTimeImmutable Stored as Unix timestamp

Accessors and Mutators

Use accessors() to transform attribute values on read, and mutators() to transform them on write.

class User extends Model
{
    protected string $table = 'users';

    protected function accessors(): array
    {
        return [
            'email' => fn(mixed $v): string => strtolower((string) $v),
        ];
    }

    protected function mutators(): array
    {
        return [
            'name' => fn(mixed $v): string => trim((string) $v),
        ];
    }
}

Serialization

Models implement JsonSerializable. DateTime values are formatted as ISO 8601.

json_encode($user); // {"id":"...","name":"Alice","created_at":"2024-01-15T12:00:00+00:00"}

Repositories

Define a repository by extending Repository and implementing getModelClass().

use Meritum\Database\Repository;
use Georgeff\Database\Contract\DatabaseManagerInterface;

class UserRepository extends Repository
{
    protected function getModelClass(): string
    {
        return User::class;
    }

    public function findByEmail(string $email): ?User
    {
        $this->query()->where('email', $email);

        return $this->first();
    }

    public function allActive(): Collection
    {
        $this->query()->where('status', 'active');

        return $this->get();
    }
}

Inject via the container:

use Georgeff\Database\Contract\DatabaseManagerInterface;

$repo = new UserRepository($container->get(DatabaseManagerInterface::class));

Saving and Deleting

// Insert
$user = new User(['name' => 'Alice', 'email' => 'alice@example.com']);
$repo->save($user); // returns true on success

// Update
$user = $repo->find('some-uuid');
$user->set('name', 'Alicia');
$repo->save($user);

// Delete
$repo->delete($user);

save() returns false without hitting the database if the model has no dirty attributes.

Finding Records

// By primary key — returns null if not found
$user = $repo->find('some-uuid');

// By primary key — throws ModelNotFoundException if not found
$user = $repo->findOrFail('some-uuid');

// By any column
$user = $repo->findBy('email', 'alice@example.com');

Building Queries

Terminal methods (first, get, count, paginate, cursor) are protected and intended to be called from within a repository method after calling query().

protected function activeCount(): int
{
    $this->query()->where('status', 'active');

    return $this->count();
}

Offset Pagination

public function paginate(int $perPage, int $page): Paginator
{
    $this->query();

    return $this->paginate($perPage, $page);
}

The returned Paginator provides:

$paginator->collection();   // Collection<T>
$paginator->total();        // int — total matching records
$paginator->perPage();      // int
$paginator->currentPage();  // int
$paginator->lastPage();     // int
$paginator->from();         // int — 1-based first record index
$paginator->to();           // int — 1-based last record index
$paginator->hasMorePages(); // bool

Serializes to:

{
    "data": [...],
    "total": 100,
    "perPage": 15,
    "currentPage": 2,
    "lastPage": 7,
    "from": 16,
    "to": 30
}

Cursor Pagination

Cursor pagination is efficient for large datasets. It uses an opaque token to track position rather than an offset.

public function browse(int $perPage, ?string $cursor = null): CursorPaginator
{
    $this->query();

    return $this->cursor($perPage, $cursor);
}

The returned CursorPaginator provides:

$paginator->collection();      // Collection<T>
$paginator->perPage();         // int
$paginator->nextCursor();      // ?string — pass to next forward request
$paginator->previousCursor();  // ?string — pass to go back
$paginator->hasMorePages();    // bool
$paginator->hasPreviousPages(); // bool

Serializes to:

{
    "data": [...],
    "perPage": 15,
    "nextCursor": "eyJ2IjoxL...",
    "previousCursor": null
}

Cursors are URL-safe base64 tokens. Pass nextCursor or previousCursor back as the cursor parameter to navigate forward or backward. Direction is encoded in the token — no separate parameter is needed.

Scopes

Scopes are invariant filters registered at construction time via injected dependencies. They are applied automatically to every query built with query().

class PostRepository extends Repository
{
    public function __construct(
        DatabaseManagerInterface $db,
        private readonly Tenant $tenant,
    ) {
        parent::__construct($db);

        $this->addScope('tenant', function (SelectInterface $query): void {
            $query->where('tenant_id', $this->tenant->id());
        });
    }

    protected function getModelClass(): string
    {
        return Post::class;
    }
}

To bypass a scope for a single query:

// Disable one scope
$this->withoutScope('tenant')->query()->where('status', 'published');

// Disable all scopes
$this->withoutScopes()->query();

Scope bypasses apply only to the next query and reset automatically.

Generating UUIDs

By default, string primary keys are auto-generated as UUIDv4. Override generateUuid() to use a different version:

protected function generateUuid(): string
{
    return Uuid::v7(); // if available
}

Collections

Collection is an immutable, keyed collection of models. Models are keyed by their primary key value.

$collection->count();           // int
$collection->isEmpty();         // bool
$collection->isNotEmpty();      // bool
$collection->has('some-id');    // bool
$collection->get('some-id');    // ?Model
$collection->first();           // ?Model
$collection->last();            // ?Model
$collection->all();             // array<int|string, Model>
$collection->keys();            // array<int|string>
$collection->filter($callback); // new Collection
$collection->each($callback);   // same Collection (for side effects)
$collection->push($model);      // new Collection
$collection->merge($other);     // new Collection — existing keys win on conflict

Exception Handling

ModelNotFoundException is thrown by findOrFail(). Map it to a 404 in your HTTP exception handler:

use Meritum\Database\Exception\ModelNotFoundException;

// In your exception handler:
if ($e instanceof ModelNotFoundException) {
    return $response->withStatus(404);
}

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 4
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-17

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固