探秘ThinkORM实体模型

概述
4.0+
版本引入了实体模型的概念,采用了实体模型后,相当于给模型层做了一个分层设计,把越来越臃肿的模型进行拆分,原来的Model
层则变成了仓储模型,负责数据的查询、关联和事件,及持久化,相对来说侧重于底层操作,而实体模型则承担了数据定义、展示、处理及业务逻辑的角色,当然,在较大的项目中,可以单独把业务逻辑拆分为一个逻辑层或服务层,在项目中应尽可能的遵循这个原则进行分工设计。

分层图示
实体模型的主要优势包括:
- 更直观:支持通过属性方式定义字段和关联;
- 更安全:字段强类型定义支持,并引入新的类型;
- 更易用:提供了包括视图模型、虚拟模型、自动关联等新特性,更简单使用;
- 模型分层:Entity层负责数据展示和处理,Model层负责数据库连接、事件和关联定义。
目前
4.0
版本仍然处于测试阶段,不排除会有使用上的调整,请勿用于正式项目。
如需体验4.0版本的实体模型,首先需要安装开发版
composer require topthink/think-orm:4.0.x-dev
模型定义
实体模型的定义如下,Entity
类和Model
类保持同名(只是命名空间不同),会自动绑定。
<?php
namespace app\entity;
use think\Entity;
class Blog extends Entity
{
}
如果你需要自定义对应的模型类,可以重写parseModel
方法
<?php
namespace app\entity;
use think\Entity;
use app\model\Blog as Other;
class Blog extends Entity
{
protected function parseModel(): string
{
return Other::class;
}
}
你无需定义任何属性即可使用(会在首次实例化的时候自动获取数据表的字段和类型),当然,也支持显式定义模型属性(由于某些原因必须是protected
类型)。
<?php
namespace app\entity;
use think\Entity;
use think\model\type\DateTime;
class Blog extends Entity
{
protected int $id;
protected string $title;
protected string $content;
protected int $user_id;
protected DateTime $create_time;
protected DateTime $update_time;
protected User $user;
}
属性名必须和数据表的实际字段一致(包括大小写),其中user
属性是关联User
实体模型,表示一对一关联关系,如果关联是一对多关系,则变量类型应该是数据集。
关联定义仍然在Model
层完成,Entity
只是负责数据展示。
<?php
namespace app\model;
use think\Model;
class Blog extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
由于Entity
层基于Model
层,所以在实际开发过程中,控制器中的关注点从原来的Model
转变为Entity
层,开发者不需要同时调用两个层,当Entity
层需要调用Model
层的底层功能的时候都是自动的,无需手动调用。因为Model
的所有方法基本上实体模型都可以使用,包括CURD和关联查询。
定义后你可以使用下面的查询
use app\entity\Blog;
$blog = Blog::with(['user'])->find(1);
dump($blog);
$blog->title = 'new title';
$blog->save();
可以输出结果查看数据

如果实体模型没有显式定义属性,那么数据会在data
下面。

你可能会注意到两张图的数据存在差异(后面的图输出属性更多一些),这是因为显式定义属性的情况下,只会展示定义的数据,其它数据被自动舍弃了。
自动关联查询
可以通过配置简化关联查询,无需每次都通过with
方法指定关联。
<?php
namespace app\entity;
use think\Entity;
class Blog extends Entity
{
protected function getOptions():array
{
return [
'auto_relation' => ['user'],
];
}
}
然后,你只需要调用
$blog = Blog::find(1);
即可自动查询关联数据,也可以在查询的时候重新指定其它的关联查询。
新增类型
实体模型增加了一些新的类型,可用于属性的类型定义,包括:
类型 | 描述 |
---|---|
\think\model\type\Json | json 格式字段 |
\think\model\type\DateTime | 对应数据表的DateTime 或timestamp 类型 |
\think\model\type\Date | 对应数据表的Date 类型 |
即使没有显式定义属性的时候,实体模型在查询数据后也会对数据表的相关类型自动进行类型转换。
属性器
除了兼容原来的方式(setXXXAttr
和getXXXAttr
方法)定义,Entity
增加了修改器和获取器合并定义的方式。
现在你可以只需要定义一个和属性同名的属性器方法(驼峰命名)即可:
// name 字段属性器定义
protected function name()
{
return [
'get' => function ($value) {
return strtoupper((string) $value);
},
'set' => function ($value) {
return strtolower($value);
},
];
}
也支持单独定义修改器或获取器,属性器支持非数据库字段的定义。
模型参数
大部分情况下,实体模型不需要任何设置即可使用,常用的实体模型设置属性包括(以下属性都不是必须设置):
属性 | 描述 |
---|---|
type | 模型需要自动转换的字段及类型(数组,默认为空) |
model_class | 对应Model类名(字符串,默认为空) |
strict | 是否严格区分字段大小写(默认为true) |
disuse | 废弃字段(数组,默认为空) |
readonly | 只读字段(数组,默认为空) |
hidden | 输出隐藏字段(数组,默认为空) |
visible | 输出显示字段(数组,默认为空) |
append | 输出追加字段(数组,默认为空) |
mapping | 字段映射(数组,默认为空) |
auto_relation | 自动关联(数组,默认为空) |
virtual | 是否为虚拟模型(默认为false) |
view | 是否为视图模型(默认为false) |
create_time | 自动创建的时间字段(默认为create_time ) |
update_time | 自动更新的时间字段(默认为update_time ) |
为了避免和属性定义冲突,实体模型的参数定义采用了使用方法统一定义的方式,使用getOptions
方法返回数组定义。
protected function getOptions():array
{
return ['strict' => false];
}
当strict
定义为false
的话,表示读取字段属性的时候不区分大小写,前提是数据表的规范必须是小写+下划线定义(显式定义属性字段的时候规范一致),但操作属性的时候可以使用驼峰定义的规范,例如你可以使用下面的代码:
$blog = Blog::find(1);
echo $blog->userId;
// 和下面等效
echo $blog->user_id;
当没有显式定义数据表属性的时候,又需要对个别字段类型进行强类型定义的话,可以通过type
参数设置字段类型。
use app\enum\StatusEnum;
protected function getOptions():array
{
return [
'strict' => false
'type' => [
'status' => StatusEnum::class
]
];
}
虚拟模型
定义实体模型的时候继承Virtual
就是一个虚拟模型。
<?php
namespace app\entity;
use think\entity\Virtual;
class Blog extends Virtual
{
}
你不需要有对应的数据表,而且虚拟模型无法进行查询和数据持久化操作。
// 创建数据
$blog = Blog::create($data);
// 修改数据
$blog->name = 'thinkphp';
视图模型
可以通过视图查询的方式返回一个视图模型,而且不需要有对应的Model
类,而且视图模型只能查询不能写入和删除。
<?php
namespace app\entity;
use app\entity\Blog;
use app\entity\User;
use think\db\Query;
use think\entity\View;
class Test extends View
{
public function query(Query $query)
{
// 执行视图查询 view方法第一个参数传入实体模型类名即可
$this->view(Blog::class, 'id,title')
->view(User::class, 'name,email', 'Blog.user_id = User.id')
->where('status', '>', 0);
}
}
事实上,你可以在query
方法里面使用查询构造器执行任何的查询。
<?php
namespace app\entity;
use think\db\Query;
use think\entity\View;
class Test extends View
{
public function query(Query $query)
{
// 执行Db查询
$query->view(['blog' => 'b'], true) // 获取blog表所有字段
->view(['user' => 'u'], 'name,email', 'b.user_id = u.id', 'LEFT')
->order('create_time');
}
}
然后可以进行视图查询
use app\entity\Test;
$result = Test::find(1);
dump($result);

如果你并不需要实体模型,仍然可以统一使用Model层,用法没有任何的改变和影响,因此对项目升级是没有任何问题
欢迎大家留言探讨关于新版ORM和实体模型的建议和反馈!
数控机床领域的软件系统!开源
Docker部署资产管理系统
延伸阅读:
由微软、清华和中科大联手打造的最强3D生成模型
现在 3D 建模在好多领域都超火,像游戏、电影、VR、AR,还有 3D 打印等等。但传统建模方式简直是 “磨人小妖精”,...
Deep Research 开源版上线,一句话让Agent帮你工作!
项目背景Eko是一个开源的JavaScript框架,专门用来帮我们构建AI代理(Agent)工作流。我们可以用自然语言描...