yuandian/database

A lightweight PHP 8.1+ ORM with annotations, relations, soft deletes and multi-datasource support.

Maintainers

Package info

github.com/yuan-dian/database

pkg:composer/yuandian/database

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-05-07 07:09 UTC

This package is auto-updated.

Last update: 2026-05-07 07:40:13 UTC


README

yuandian/database

一款基于 PHP 8.1+ 的轻量级 ORM 框架,支持多数据库驱动、注解驱动模型、关联映射、软删除、雪花算法主键等特性。

目录

环境要求

依赖 版本
PHP >= 8.1
ext-pdo *
ext-mongodb *(可选,MongoDB 支持)
yuandian/container ^1.0
yuandian/tools ^1.0

安装

composer require yuandian/database

快速开始

<?php

use yuandian\Database\Attribute\Connection;
use yuandian\Database\Attribute\Table;
use yuandian\Database\Attribute\TableId;
use yuandian\Database\Enums\IdType;
use yuandian\Database\Model\Model;

#[Table('user')]
#[Connection('mysql')]
class User extends Model
{
    #[TableId(IdType::AUTO)]
    public int $id = 0;

    public string $userName = '';

    public string $email = '';

    public int $age = 0;
}

// 新增
$user = new User();
$user->userName = '张三';
$user->email = 'zhangsan@example.com';
$user->age = 25;
$user->save();
echo "新增用户 ID: {$user->id}\n";

// 查询
$user = User::where('id', 1)->find();
echo $user->userName; // 张三

// 修改
$user->userName = '李四';
$user->save();

// 删除
$user->delete();

模型定义

表名映射

使用 #[Table] 注解指定表名。未使用注解时,自动将类名驼峰转下划线作为表名:

#[Table('user_profile')]   // → 表名 user_profile
class UserProfile extends Model {}

class ArticleComment extends Model {}  // → 表名 article_comment

主键策略

使用 #[TableId] 注解声明主键属性及策略:

策略 枚举值 说明
自增 IdType::AUTO 数据库自增主键,插入后自动回填
雪花算法 IdType::ASSIGN_ID 插入时自动生成数字型雪花 ID
UUID IdType::ASSIGN_UUID 插入时自动生成字符串型 UUID
// 自增主键
class User extends Model
{
    #[TableId(IdType::AUTO)]
    public int $id = 0;
}

// 雪花算法主键
class Order extends Model
{
    #[TableId(IdType::ASSIGN_ID)]
    public int $orderId = 0;
}

// UUID 主键
class Event extends Model
{
    #[TableId(IdType::ASSIGN_UUID)]
    public string $eventId = '';
}

数据源指定

使用 #[Connection] 注解指定模型使用的数据源:

#[Table('user')]
#[Connection('mysql')]    // → 使用 mysql 数据源
class User extends Model {}

#[Table('log')]
#[Connection('mongodb')]  // → 使用 mongodb 数据源
class Log extends Model {}

未使用 #[Connection] 注解时,使用配置中的默认数据源。

字段映射规则

模型属性使用小驼峰命名,数据库字段使用下划线命名,框架自动转换:

模型属性:$userName => 数据库字段:user_name

查询返回的数据会自动映射到模型属性,保存时自动转换为下划线字段名。

CRUD 操作

新增

$user = new User();
$user->userName = '张三';
$user->email = 'zhangsan@example.com';
$user->age = 25;
$user->save();
echo $user->id; // 主键自动回填

查询

// 查询单条(返回模型实例或 null)
$user = User::where('id', 1)->find();

// 查询多条(返回模型实例数组)
$users = User::where('age', '>', 18)->select();

// 使用 with 预加载关联
$book = Book::with('chapters', 'isbn')->where('book_id', 1)->find();

修改

$user = User::where('id', 1)->find();
$user->userName = '李四';
$user->save();

删除

// 通过模型删除
$user = User::where('id', 1)->find();
$user->delete();

// 通过查询删除
User::where('id', 1)->delete();

链式操作

WHERE 条件

// 基本条件
User::where('age', '>', 18)->select();
User::where('userName', '张三')->select();         // 省略运算符,默认 =

// 批量条件
User::where(['userName' => '张三', 'age' => 25])->select();

// OR 条件
User::where('age', '>', 18)->orWhere('age', '<', 10)->select();

// IN / NOT IN
User::whereIn('id', [1, 2, 3])->select();
User::whereNotIn('id', [4, 5])->select();

// NULL / NOT NULL
User::whereNull('email')->select();
User::whereNotNull('email')->select();

// BETWEEN
User::whereBetween('age', 18, 30)->select();

// LIKE
User::whereLike('userName', '%张%')->select();

// 原始条件
User::whereRaw('age > ? AND id IN (?)', [18, [1, 2, 3]])->select();

// 嵌套条件组
User::where(function ($q) {
    $q->where('age', '>', 18)->orWhere('age', '<', 10);
})->select();

字段选择

User::field('id,userName,email')->select();
User::field(['id', 'userName', 'email'])->select();

JOIN 查询

User::alias('u')
    ->leftJoin('profile AS p', 'u.id = p.user_id')
    ->field('u.userName, p.bio')
    ->select();

聚合查询

$count = User::where('age', '>', 18)->count();
$sum   = User::sum('age');
$max   = User::max('age');
$min   = User::min('age');
$avg   = User::avg('age');

关联关系

关联通过属性注解定义,支持预加载与手动加载。

一对一 HasOne

use yuandian\Database\Attribute\HasOne;

#[Table('user')]
class User extends Model
{
    #[TableId(IdType::AUTO)]
    public int $id = 0;
    public string $userName = '';
    #[HasOne(Profile::class, foreignKey: 'user_id', localKey: 'id')]
    public Profile $profile;
}

#[Table('profile')]
class Profile extends Model
{
    #[TableId(IdType::AUTO)]
    public int $id = 0;
    public int $userId = 0;
    public string $bio = '';
}

一对多 HasMany

use yuandian\Database\Attribute\HasMany;

#[Table('book')]
class Book extends Model
{
    #[TableId(IdType::AUTO)]
    public int $bookId = 0;
    public string $bookName = '';

    #[HasMany(Chapter::class, foreignKey: 'book_id', localKey: 'book_id')]
    public array $chapters;
}

#[Table('chapter')]
class Chapter extends Model
{
    #[TableId(IdType::AUTO)]
    public int $id = 0;
    public int $bookId = 0;
    public string $name = '';
}

远程一对一 HasOneThrough

use yuandian\Database\Attribute\HasOneThrough;

#[Table('book')]
class Book extends Model
{
    #[TableId(IdType::AUTO)]
    public int $bookId = 0;
    public int $authorId = 0;

    /**
     * Book → Profile(中间表)→ Address(目标表)
     */
    #[HasOneThrough(
        model: Address::class,
        through: Profile::class,
        foreignKey: 'author_id',
        throughKey: 'profile_id',
        localKey: 'book_id',
        throughPk: 'id'
    )]
    public Address $address;
}

远程一对多 HasManyThrough

use yuandian\Database\Attribute\HasOneThrough;

#[Table('book')]
class Book extends Model
{
    #[TableId(IdType::AUTO)]
    public int $bookId = 0;

    /**
     * Book → BookTag(中间表)→ Tag(目标表)
     */
    #[HasManyThrough(
        model: Tag::class,
        through: BookTag::class,
        foreignKey: 'book_id',
        throughKey: 'tag_id',
        localKey: 'book_id',
        throughPk: 'id'
    )]
    /*** @var array<Tag>*/
    public array $tags;
}

参数说明:

参数 说明
model 目标关联模型
through 中间模型
foreignKey 当前模型 → 中间表的外键
throughKey 中间表 → 目标表的外键
localKey 当前模型的本地键
throughPk 中间表的主键

预加载

使用 with()load() 预加载关联,避免 N+1 查询问题:

// 预加载(在查询时一次性加载关联数据)
$books = Book::with('chapters', 'isbn')
    ->where('author_id', 1)
    ->select();

foreach ($books as $book) {
    echo $book->bookName;
    // 以下访问均命中缓存,无额外查询
    foreach ($book->chapters as $ch) {
        echo "  - {$ch->name}";
    }
    echo $book->isbn->isbn;
}

// 查询后按需加载
$book = Book::where('book_id', 1)->find();
$book->load('chapters', 'isbn');

软删除

基本用法

在模型类上使用 #[SoftDelete] 注解启用软删除:

use yuandian\Database\Attribute\SoftDelete;

#[Table('article')]
#[SoftDelete] // 默认列名: deleted_time
class Article extends Model
{
    #[TableId(IdType::AUTO)]
    public int $id = 0;
    public string $title = '';
    public string $content = '';
}

自定义列名

#[SoftDelete('deleted_at')]
class Article extends Model
{
}

继承 BaseModel

在 BaseModel 上统一声明软删除,所有子模型自动继承:

#[Connection('mysql')]
#[SoftDelete]
class BaseModel extends Model
{
    #[TableId(IdType::AUTO)]
    public int $id = 0;
}

#[Table('article')]
class Article extends BaseModel   // 自动继承 #[SoftDelete]
{
    public string $title = '';
    public string $content = '';
}

禁用软删除

子类可通过 enabled: false 显式禁用父类的软删除:

#[SoftDelete(enabled: false)]
class Article extends Model
{
}

自动时间戳

使用方法

在 BaseModel 上统一声明自动时间戳,所有子模型自动继承:

// 使用默认列名 create_time,update_time
#[AutoWriteTime]
class Article extends Model
{
}

// 禁用
#[AutoWriteTime(enabled: false)]
class Article extends Model
{
}

// 指定createTime列名:create_at
#[AutoWriteTime(createTime: 'create_at')]
class Article extends Model
{
}

// 禁用 createTime 写入
#[AutoWriteTime(createTime: false)]
class Article extends Model
{
}

// 指定 updateTime 列名:update_at
#[AutoWriteTime(updateTime: 'update_at')] → 列名 update_at
class Article extends Model
{
}
// 禁用 updateTime 写入
#[AutoWriteTime(updateTime: false)]
class Article extends Model
{
}

事务管理

闭包事务(推荐)

use yuandian\Database\Facade\DB;

DB::transaction(function () {
    $book = new Book();
    $book->bookName = '事务测试';
    $book->authorId = 1;
    $book->save();

    $chapter = new Chapter();
    $chapter->bookId = $book->bookId;
    $chapter->name = '第一章';
    $chapter->save();
});
// 自动 commit,异常时自动 rollback

手动事务

use yuandian\Database\Facade\DB;

DB::beginTransaction();
try {
    $book = new Book();
    $book->bookName = '手动事务';
    $book->authorId = 1;
    $book->save();

    DB::commit();
} catch (\Throwable $e) {
    DB::rollback();
    throw $e;
}