4.2 模型设计与 CRUD


4.2 模型设计与 CRUD

上一节能「要数据」了,本节升级到「要对象」:一个模型类怎么从空壳长成业务里可信赖的数据对象——字段许可、获取器与修改器、自动时间戳、软删除,一样样装配。读完本节,你应当能为任意业务表写出完整模型,并画出一条记录从出生到「删除」的状态流转图。

一、模型的本质:给表配一个「说话的人」

模型是表的业务化身:表只管存,模型负责用业务语言表达「这条数据能怎样」。先看书店项目里 Book 模型的完整样子:

namespace app\model; use think\Model; use think\model\concern\SoftDelete; class Book extends Model { use SoftDelete; // 软删除:删除只打标记 protected $table = 'book'; // 表名(前缀自动拼) protected $pk = 'id'; // 批量赋值白名单:配合 3.2 的 only,双保险 protected $field = ['id', 'seller_id', 'title', 'summary', 'price', 'stock', 'status', 'delete_time', 'create_time', 'update_time']; // 类型自动转换:数据库取出的字符串在这里变成真正类型 protected $type = [ 'price' => 'float', 'stock' => 'integer', ]; protected $defaultSoftDelete = null; // 未删除标记值 // 获取器:读取 price 时自动执行,展示层零负担 public function getPriceAttr($value) { return number_format($value, 2, '.', ''); } // 修改器:写入 title 前自动执行,清洗逻辑集中一处 public function setTitleAttr($value) { return trim(strip_tags($value)); } }

四个关键装配各司其职:$field 白名单挡住 3.2 提过的字段注入;$type 让类型转换不再散落在业务代码;获取器统一「读出的样子」;修改器统一「写入前的清洗」。业务代码从此用对象语言说话:$book->price$book->save(),SQL 字样彻底退出服务层。

二、CRUD 全流程:对象视角的增删改查

use app\model\Book; // ---- 创建 ---- $book = Book::create([ 'seller_id' => 7, 'title' => '<b>PHP 对象、模式与实践</b>', // 修改器自动剥标签 'price' => 35.5, 'stock' => 3, ]); echo $book->id; // 自增主键直接可读 // ---- 读取 ---- $one = Book::findOrEmpty(42); // 找不到返回空模型,避免判 null if ($one->isEmpty()) { return json(['code' => 404, 'msg' => '不存在'], 404); } $mine = Book::where('seller_id', 7)->order('id', 'desc')->limit(5)->select(); // ---- 更新 ---- $one->price = 30.0; $one->stock += 1; $one->save(); // 只更新变化的字段 // 批量静态更新:跳过模型事件,慎用于有监听依赖的字段 Book::where('status', 0)->whereTime('create_time', '<', '-90 days')->update(['status' => 2]); // ---- 软删除 ---- $one->delete(); // delete_time 写入当前时间,行还在 $trash = Book::withTrashed()->find(42); // 连同已删一起查 $alive = Book::onlyTrashed()->select(); // 只看已删 $one->restore(); // 恢复

静态 create 与实例 save 的分工:新建用 create 一步到位,改动用「取出来、改属性、save」。软删除是「业务上删除」的默认姿势——订单、商品这类数据极少允许物理消失,审计与找回全靠标记位;物理删除留给日志清理、临时数据这类真的可以走的数据。

三、一条记录的一生:状态流转

把上面的操作串成状态机,模型的每次调用都是在状态间移动:

状态图对两件事特别有用:一是设计字段——status 的枚举值有哪些、delete_time 用不用,先画图再建表,少走回头路;二是排查「数据怎么变成这样」——把变更入口(控制器、命令行、队列)对着状态箭头数一遍,漏掉的箭头往往就是漏洞。

四、自动化双刃剑:时间戳与字段许可

自动时间戳(create_time/update_time 由模型维护)默认开启,省掉手动填时间的繁琐;代价是绕过模型的批量更新(上一节 update 静态调用之外的原生路径)不会触发它,两套入口混用会让 update_time 的语义漂移。团队约定一个原则即可:业务数据的变更一律走模型,原生更新只留给运维性批量操作,并在代码注释里写明原因。同理,$field 白名单要随表结构演进同步维护——加字段忘了进白名单,写入会被静默丢弃,这类「不报错的错」最耗排查时间。

⚠️ 常见坑:find() 在老版本里找不到会返回 null,新版本行为统一为抛异常或配合 findOrEmpty 使用;混读两代教程时这里极易踩坑。本书统一用 findOrEmptyisEmpty() 判断,行为在 6.x/8.x 完全一致。

💡 关键直觉:模型层的所有自动化(类型、获取器、修改器、时间戳)都在收拢「同一件事的多种写法」。判断某段清洗逻辑该不该进修改器,就问它是不是「这字段每次写入都要发生」——是就进模型,不是就留在业务层。

五、动手练习:给 Order 建一个有纪律的模型

背景:练习项目要落订单表。操作:建表含金额、状态、软删位与两个时间列;按本节模板写 Order 模型,金额字段 float 类型化,状态字段加获取器把数字翻译成文字;写一条完整的下单创建与一次软删除取消;再用 onlyTrashed 列出被取消订单。结果示例:控制器里不再出现任何字段清洗与状态数字,展示层直接输出可读状态。解读:体会「纪律上移」——清洗、类型、状态语义全部住进模型后,新写的控制器天然干净。变式:给 Order 加「取消原因」修改器,把空串统一存为 null,思考为什么这类规范化也该住在模型里。

本节要点回顾

  • 模型四装配:字段白名单、类型转换、获取器、修改器,各自收拢一类「多种写法」。
  • CRUD 分工:创建走静态 create,修改走实例 save,查询判空用 findOrEmpty。
  • 软删除是默认:业务数据删除打标记,restore 可回滚,物理删除留给可消失的数据。
  • 状态机思维:画状态流转图来设计字段与排查异常数据,比翻日志直观。
  • 自动化的边界:时间戳与白名单只在走模型时生效,业务变更必须走模型是团队铁律。

对象立起来了,对象之间怎么牵手?下一节看关联关系与 N+1 查询的现场解剖。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U