从Encryption trait迁移至Laravel模型$cast时旧数据解密异常
问题根因
返回值中携带的s:23:"Harry Potter";是PHP serialize() 函数的标准序列化输出,问题本质是旧第三方加密trait与Laravel 9原生encrypted cast的加解密逻辑不匹配:
- 旧trait加密流程:原始值 →
serialize()序列化 → OpenSSL加密 → 入库;解密时反向执行:库中取值 → OpenSSL解密 →unserialize()反序列化 → 返回原始值 - Laravel原生
encryptedcast流程:原始值 → OpenSSL加密 → 入库;解密时直接返回解密后的明文,不做序列化/反序列化处理
读取旧存量数据时,解密得到的是旧逻辑写入的序列化字符串,没有经过反序列化步骤就直接返回,因此会出现多余的序列化标记。新增数据没有经过序列化步骤,所以加解密表现正常。
解决方案
不需要全量遍历修正存量数据,实现一个兼容新旧格式的自定义Cast即可,上线后无侵入兼容所有历史数据,后续数据更新时会自动迁移为原生格式。
- 生成自定义Cast类:
执行artisan命令创建类文件:
php artisan make:cast EncryptedCompat
- 编辑生成的
app/Casts/EncryptedCompat.php文件,写入如下逻辑:
<?php namespace App\Casts; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Database\Eloquent\Model; class EncryptedCompat implements CastsAttributes { public function get(Model $model, string $key, mixed $value, array $attributes): mixed { if (is_null($value)) { return null; } $decrypted = decrypt($value); // 识别旧存量数据的序列化格式,自动反序列化 if ($this->isPhpSerialized($decrypted)) { return unserialize($decrypted); } // 新格式数据直接返回明文 return $decrypted; } public function set(Model $model, string $key, mixed $value, array $attributes): mixed { if (is_null($value)) { return null; } // 新写入数据统一使用原生逻辑加密,不再做序列化 return encrypt($value); } protected function isPhpSerialized(mixed $data): bool { if (!is_string($data)) { return false; } $data = trim($data); if ($data === 'N;') { return true; } if (strlen($data) < 4) { return false; } if ($data[1] !== ':') { return false; } $validTypes = ['s', 'a', 'i', 'd', 'b', 'o', 'O', 'C']; if (!in_array($data[0], $validTypes)) { return false; } return (bool)preg_match("/^{$data[0]}:[0-9]+:/s", $data); } }
- 将模型中原本配置的
encrypted相关cast替换为自定义兼容类即可:
// 原配置 protected $casts = [ 'name' => 'encrypted', 'extra_config' => 'encrypted:array', ]; // 替换后 protected $casts = [ 'name' => \App\Casts\EncryptedCompat::class, // 数组/JSON类型的加密字段可根据实际存储逻辑,在get/set方法中增加json_encode/json_decode处理即可 ];
方案优势
- 无停机成本,不需要提前执行全量数据迁移脚本,上线即兼容所有新旧数据
- 后续新增、更新数据时会自动按Laravel原生加密格式存储,存量数据会在日常业务更新对应记录时自动完成格式转换,无额外维护成本
- 待所有存量数据完成格式转换后,可直接将cast改回Laravel原生
encrypted类型,没有历史包袱
内容的提问来源于stack exchange,提问作者Inigo EC
相关产品推荐
相关产品推荐

