如何在Laravel中为带参构造函数的模型使用Factory/Faker
你的推测完全正确!Laravel的Factory在默认逻辑里,是直接通过new Model($attributesArray)的方式创建模型实例的,根本不会走服务容器的绑定流程——这就是为什么你在Tinker里调用factory(App\Doombot::class)->make()时会触发类型错误:它把工厂生成的属性数组直接传给了需要两个整数参数的构造函数,自然就报错了。
接下来给你两种可行的解决方案,你可以根据自己的需求选择:
方案一:调整模型构造函数,兼容Eloquent默认逻辑
Laravel的Eloquent模型原本的构造函数是支持接收属性数组的,你重写构造函数后破坏了这个原生行为。我们可以修改构造函数,既保留Eloquent的初始化逻辑,又实现你的自定义参数需求:
class Doombot extends Model { public $timestamps = false; // 注意:Eloquent会自动映射数据库字段到模型属性,不需要手动声明public属性 // 所以可以删掉原来的public $model_id;等声明,除非你有特殊业务需要 public function __construct(array $attributes = []) { // 先调用父类构造函数,让Eloquent完成基础初始化 parent::__construct($attributes); // 如果属性数组里没传model_id和weapon_id,就用随机值填充 if (empty($this->model_id)) { $this->model_id = random_int(1, 2); } if (empty($this->weapon_id)) { $this->weapon_id = random_int(1, 2); } } }
修改后,你的Factory可以简化成只生成name字段,剩下的model_id和weapon_id会由构造函数自动填充:
$factory->define(Doombot::class, function (Faker $faker) { return [ 'name' => $faker->firstName ]; });
这时候再在Tinker里执行factory(App\Doombot::class)->make()就正常了,Factory会把数组传给构造函数,Eloquent自动赋值name,构造函数补全另外两个参数。
方案二:让Factory通过服务容器创建模型
如果你不想修改模型构造函数,也可以让Factory主动使用服务容器来获取模型实例,再填充Factory生成的字段。
在Tinker里直接操作
你可以先用raw()方法拿到Factory生成的属性数组,再通过服务容器获取模型,最后填充属性:
// 先拿到Factory生成的name字段 $nameAttr = factory(App\Doombot::class)->raw(['name' => $faker->firstName]); // 从服务容器获取预初始化好model_id和weapon_id的Doombot实例 $doombot = app(App\Doombot::class); // 填充name字段 $doombot->fill($nameAttr); // 如果要保存到数据库就调用save() $doombot->save();
自定义Factory的创建逻辑
你也可以直接在Factory里修改逻辑,让它返回通过服务容器创建的模型实例:
$factory->define(Doombot::class, function (Faker $faker) { // 从服务容器获取已经初始化好参数的模型 $doombot = app(Doombot::class); // 给模型赋值name字段 $doombot->name = $faker->firstName; // 返回模型实例 return $doombot; });
不过要注意,这种方式下Factory的create()方法可能需要额外处理,因为默认逻辑是基于属性数组的,所以方案一通常是更稳妥、更符合Laravel设计规范的选择。
内容的提问来源于stack exchange,提问作者Justin Schwimmer

