如何基于数据库同项目字段设置Laravel模型自增版本属性?
Laravel 11实现Text模型自动生成版本号方案
需求回顾
在使用Laravel 11和PostgreSQL的项目中,texts表的version字段需要满足:
- 插入新记录时,自动设为同项目下已有
text的最大version值加1 - 更新记录时,不修改
version值 - 优先用Eloquent逻辑实现,避免数据库触发器/函数
方案一:重写模型的save方法
直接在Text模型中重写save方法,区分创建和更新场景:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Text extends Model { // 根据实际业务调整可批量赋值的字段 protected $fillable = ['content', 'user_id', 'project_id', 'words', 'processed']; public function save(array $options = []) { // 仅在创建新记录时计算version if (!$this->exists) { // 获取同项目的最大版本号,无记录时默认设为1 $maxVersion = static::where('project_id', $this->project_id)->max('version'); $this->version = $maxVersion ? $maxVersion + 1 : 1; } return parent::save($options); } }
关键细节说明
$this->exists:Eloquent模型自带属性,判断当前模型是否已存入数据库,仅新模型(创建场景)会触发版本计算- 避免硬编码表名:使用
static::where()调用当前模型的查询构建器,无需手动写texts表名 - 空值处理:当项目下无任何
text记录时,max('version')返回null,此时version直接设为1
方案二:使用模型creating事件(更优雅)
利用Laravel的模型事件机制,仅在创建新记录前触发版本计算,更符合Laravel的设计理念:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Text extends Model { protected $fillable = ['content', 'user_id', 'project_id', 'words', 'processed']; protected static function booted() { // 绑定creating事件:仅在创建新记录时执行 static::creating(function (Text $text) { $maxVersion = static::where('project_id', $text->project_id)->max('version'); $text->version = $maxVersion ? $maxVersion + 1 : 1; }); } }
优势
- 无需重写
save方法,避免和其他自定义save逻辑冲突 - 事件驱动的方式耦合度更低,逻辑更清晰
并发问题处理(可选)
如果系统存在高并发创建同项目text的场景,可能会出现版本号重复的情况。可以通过以下方式解决:
- 添加数据库唯一索引
在texts表的迁移文件中,给project_id和version组合添加唯一索引:
Schema::table('texts', function (Blueprint $table) { $table->unique(['project_id', 'version']); });
- 捕获冲突异常并重试
修改模型逻辑,捕获PostgreSQL的唯一约束冲突错误(错误码23505),自动重试版本号计算:
// 以方案一为例,修改save方法 public function save(array $options = []) { if (!$this->exists) { $attempts = 3; // 设置重试次数 while ($attempts--) { $maxVersion = static::where('project_id', $this->project_id)->max('version'); $this->version = $maxVersion ? $maxVersion + 1 : 1; try { return parent::save($options); } catch (\Illuminate\Database\QueryException $e) { // 仅处理唯一约束冲突异常 if ($e->getCode() !== '23505') { throw $e; } // 重试次数耗尽则抛出异常 if ($attempts === 0) { throw new \Exception('无法生成唯一版本号,请稍后重试'); } } } } return parent::save($options); }
内容的提问来源于stack exchange,提问作者Sergei Tsibulchenko
相关产品推荐
相关产品推荐

