工厂类必须显式声明$model属性且路径、类名、命名空间三者严格匹配,否则User::factory()报错;文件须位于database/factories/UserFactory.php,类名为UserFactory,命名空间为Database\Factories,$model=App\Models\User::class,definition()中用$this->faker而非new Faker。

工厂类必须显式声明 $model 属性,且文件路径、类名、命名空间三者严格匹配,否则 User::factory() 会直接报错“Unable to locate factory”。
工厂类文件位置和命名必须符合 PSR-4 自动发现规则
Laravel 8+ 默认启用自动发现,但只扫描 database/factories/ 目录下顶层的 *Factory.php 文件。任何嵌套(如 database/factories/User/UserFactory.php)或文件名不匹配(如 UserModelFactory.php)都会导致工厂无法被找到。
- 正确路径:
database/factories/UserFactory.php - 正确类名:
UserFactory(对应User模型) - 错误示例:
App\Factories\UserFactory命名空间 —— Laravel 不会自动加载该路径,除非你手动use Database\Factories\UserFactory或在DatabaseFactory::configure()中注册
$model 属性不能省略,也不能靠构造函数推断
旧版 Laravel 允许通过文件名或参数推断模型,新版本强制要求在工厂类中显式定义 protected $model = User::class;。漏写或写错类名(比如忘了 App\Models\ 前缀),User::factory()->create() 就会抛出 Target class [App\Database\Factories\UserFactory] does not exist 或更隐蔽的 Call to undefined method App\Models\User::factory()。
- 必须写:
protected $model = \App\Models\User::class;(Laravel 10 推荐用完整命名空间) - 不要写:
protected $model = 'User';或protected $model = User::class;(没 use 就报错) - 别依赖
newFactory()方法绕过 —— 那是为自定义命名空间留的后门,不是默认行为
definition() 里用 $this->faker,别 new Faker 实例
工厂基类已自动注入 Faker 实例到 $this->faker,直接调用即可。手动 new Faker 不仅冗余,还会破坏 unique() 的作用域 —— 它只对同一 $this->faker 实例内连续调用有效。
- 正确写法:
'email' => $this->faker->unique()->safeEmail - 错误写法:
'email' => (new \Faker\Generator)->unique()->safeEmail -
unique()在单条create()中不保唯一:循环调用User::factory()->create()10 次,仍可能冲突;必须用User::factory()->count(10)->create()才生效
别在工厂里写业务逻辑或 DB 查询
工厂职责是“生成数据”,不是“校验数据”或“查重”。比如想确保邮箱不重复,不要在 definition() 里写 while (User::where(...)) —— 这会让批量创建变慢、不可预测,还可能在事务测试中引发死锁。
- 替代方案:用
$this->faker->uuid . '@example.com'构造确定性唯一值 - 真要查重?放在 Seeder 或测试 setup 里,用
retry(5, 100, fn() => User::factory()->create()) - 关联字段优先走
for()和has(),而不是在afterCreating回调里手动create(['user_id' => $user->id])—— 后者容易触发外键约束失败或 N+1
最常被忽略的一点:工厂类本身不参与迁移生命周期。你在测试中用 User::factory()->has(Post::factory())->create() 插入了用户和文章,但 RefreshDatabase 只清空 users 和 posts 表,不会管中间表或软删除字段。如果模型用了软删除或有额外约束,得自己加 DB::table('...')->truncate() 或配好级联关系。


















