hyperf 3.0 中模型 enum 属性需手动实现读写转换:读取时用 status::from() 转整数为枚举实例,写入时存 $value;dto 中用 readonly enum 保障类型安全,并注意 json 序列化、迁移注释及模型事件中属性访问一致性。

Hyperf 3.0 模型中直接赋值 enum 属性会报错
Hyperf 3.0 默认使用 Hyperf\Database\Model,底层基于 PDO + fetchObject()。当数据库查出一个整数(如 status = 1),而模型属性声明为 private Status $status(Status 是 enum Status: int),PHP 会直接抛出 Fatal error: Cannot assign int to property User::$status of type Status。这不是 Hyperf 的 bug,是 PHP 类型系统在起作用——PDO 不懂枚举,也不会自动调用 Status::from()。
必须显式控制读写时的 enum 转换逻辑
Hyperf 不像 Laravel 那样提供 $casts 原生支持枚举(Laravel 10+ 才有 AsEnumArrayObject 等),所以你要自己接管字段映射。核心就两条:
- 写入前:把
Status::Published→Status::Published->value(即1)再存进数据库 - 读取后:从数据库拿到
1→ 主动调用Status::from(1)或Status::tryFrom(1)构造实例
推荐在模型里用访问器(accessor)和修改器(mutator)实现:
use App\Enums\Status;
class User extends Model
{
protected array $casts = [
'status' => 'integer',
];
protected function getStatusAttribute(int $value): Status
{
return Status::from($value);
}
protected function setStatusAttribute(Status $value): void
{
$this->attributes['status'] = $value->value;
}
}
注意:$casts 仍需设为 'integer' 或 'string',否则 Hyperf 会跳过类型转换,直接把原始值塞给属性,触发致命错误。
Hyperf 的 DTO 或 Command 中用 readonly + enum 更安全
在请求验证、命令处理等场景,建议用 DTO 封装输入,配合 readonly 和 enum 确保不可变性:
final class CreateUserCommand
{
public function __construct(
public readonly string $name,
public readonly Status $status = Status::Draft,
) {}
}
这样构造时就强制类型检查,避免传入任意字符串或数字。Hyperf 的 @RequestValidation 注解可配合自定义规则校验 enum 值是否合法(比如用 Status::tryFrom($input) 返回 null 则报错)。
常见坑:
- 别在 DTO 构造函数里对 enum 属性做
match映射——破坏单点定义,且无法复用from()的异常语义 - 如果数据库字段是
TINYINT,但旧数据含非法值(如99),Status::from(99)会抛ValueError;此时必须用Status::tryFrom(99)并判断返回值,否则整个请求崩掉 - Hyperf 的 JSON 序列化默认不处理 enum,输出时是空对象
{};需手动加jsonSerialize()方法返回$this->value或$this->name
迁移脚本里动态生成 ENUM 字段值列表
Hyperf 自带的 migration 不支持原生 MySQL ENUM 类型,通常用 tinyInteger() + 注释说明。但如果真要用数据库级约束(比如防止非法插入),得手写 SQL 或借助第三方包。关键是:别硬编码值列表。
正确做法是用 array_map 提取枚举 case 的 value:
// 在 migration 中
use App\Enums\Status;
Schema::create('users', function (Blueprint $table) {
$table->tinyInteger('status')
->comment('Status: ' . implode(', ', array_map(fn($s) => $s->value, Status::cases())));
});
这样既保持一致性,又避免改枚举后忘记同步 migration。注意:MySQL 的 ENUM('draft','published') 是字符串枚举,而 PHP 的 enum Status: string 必须确保 case Draft = 'draft' 和数据库值完全一致(包括大小写、空格)。
最易被忽略的一点:Hyperf 的模型事件(如 saving)里,$model->status 是 enum 实例,但 $model->getAttribute('status') 是原始整数——混用会导致逻辑错乱。所有业务判断请统一走 enum 实例方法(如 $model->status->isPublished()),而不是直接比数值。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











