探秘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\Jsonjson格式字段
\think\model\type\DateTime对应数据表的DateTimetimestamp类型
\think\model\type\Date对应数据表的Date类型

即使没有显式定义属性的时候,实体模型在查询数据后也会对数据表的相关类型自动进行类型转换。

属性器

除了兼容原来的方式(setXXXAttrgetXXXAttr方法)定义,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和实体模型的建议和反馈!

暂无介绍....

延伸阅读:

一款开源高颜值的 IM 即时通讯系统!

前言 在数字化转型日益加速的今天,即时通讯已经成为企业运营和社交互动不可或缺的一部分。然而,市面上的即时通讯软...

郭 智满
2025年4月4日
国产开源企业级自定义报表系统

为什么要做这个项目?优秀的自定义报表工具有很多,比较有代表性的,大厂的有FineReport,免费的有积木报表,但是开源...

郭 智满
2025年3月17日
由微软、清华和中科大联手打造的最强3D生成模型

现在 3D 建模在好多领域都超火,像游戏、电影、VR、AR,还有 3D 打印等等。但传统建模方式简直是 “磨人小妖精”,...

郭 智满
2025年3月23日
Deep Research 开源版上线,一句话让Agent帮你工作!

项目背景Eko是一个开源的JavaScript框架,专门用来帮我们构建AI代理(Agent)工作流。我们可以用自然语言描...

郭 智满
2025年3月18日
解放双手,工作流自动化神器!

我们每天都在处理各种繁琐的任务。从数据提取到报表生成,从邮件发送到社交媒体管理,这些看似简单的任务却占据了我们大量的时间...

郭 智满
2025年2月6日