laravel 8+ 模型工厂必须用 user::factory()->create() 静态调用,全局 factory() 函数已移除;工厂类须严格置于 database/factories/userfactory.php,类名 userfactory,继承 factory 并声明 $model = user::class;definition() 中须用 fake()->name() 而非 $this->faker->name。

直接说结论:Laravel 8+ 的模型工厂必须用 User::factory()->create() 这类静态调用方式,factory(User::class)->create() 全局函数已彻底移除,继续用会报 Call to undefined function factory() 或静默失败。
工厂类放哪儿、叫啥名、怎么写
三个硬性条件缺一不可,否则 User::factory() 会抛出 Unable to locate factory for [App\Models\User]:
- 文件路径必须是
database/factories/UserFactory.php——不能有子目录(比如database/factories/User/UserFactory.php就找不到) - 类名必须是
UserFactory,且继承Illuminate\Database\Eloquent\Factories\Factory -
protected $model = User::class;这行必须存在,Laravel 不靠文件名推断模型,只认这个属性
definition() 里字段值要用 fake()->name(),不是 $this->faker->name(旧写法在 Laravel 8+ 会报错)。fake()->unique() 只对同一工厂实例内连续调用有效——User::factory()->count(10)->create() 安全,但循环里写 10 次 User::factory()->create() 可能撞邮箱。
测试中怎么生成数据:make vs create
make() 和 create() 行为差异直接影响测试速度和数据库状态:
-
User::factory()->make():只构造内存中的模型实例,不写入数据库,适合单元测试中避免 DB 依赖 -
User::factory()->create():保存到数据库,返回已持久化的模型对象,适合功能测试或需要真实外键的场景 -
withAttributes(['email' => 'test@example.com'])可覆盖默认字段,比在definition()里硬编码更灵活 -
count(5)必须放在create()前,User::factory()->count(5)->create()是批量插入,效率远高于循环调用
关联模型怎么连:for()、has()、hasAttached()
关联不是靠手动设外键,而是用工厂方法自动维护关系链:
-
Post::factory()->for($user)->create():要求$user是已create()的模型实例,for()会自动填user_id -
User::factory()->has(Post::factory()->count(3))->create():一个用户带 3 篇文章,has()自动处理post.user_id -
User::factory()->hasAttached(Role::factory(), ['active' => true])->create():用于多对多中间表字段(如active),hasAttached()比手动attach()更可靠
注意:for() 不接受 make() 出来的对象;大批量生成时(比如 1000 条),建议包一层 User::withoutEvents(fn () => ...) 关闭观察者,避免性能崩掉。
状态方法怎么定义和复用
状态方法本质是可链式调用的字段覆盖器,不是魔法,别指望它自动触发业务逻辑:
- 在工厂类里加一个 public 方法,比如
published(),内容是return $this->state(['status' => 'published', 'published_at' => now()]); - 调用时写
Post::factory()->published()->create(),多个状态可叠加:->published()->featured(),后调用的覆盖同名字段 - 状态方法支持参数:
public function titled($title) { return $this->state(['title' => $title]); },调用写->titled('Hello World') - 状态方法在 Seeder 和测试里都能复用,但别在
definition()里调用它——那会导致无限递归
最常被忽略的一点:状态方法返回的必须是 $this->state(...) 的结果,否则链式调用会断掉。写错成 return ['status' => 'draft'] 就会直接报错。











