Laravel 8工厂如何在无字段时传递参数至configure方法
问题:Laravel 8 Factory中传递临时媒体类型参数至afterCreating的问题
场景背景
在Laravel 8项目(依赖fakerphp/faker: ^1.9)中,需要为News模型生成不同类型的测试媒体数据,但不需要在News表中存储news_media_type字段。最初代码尝试在definition方法返回数组中包含该字段,触发数据库报错;改用类的protected变量传递时,configure先执行导致变量为空,afterCreating中变量值又丢失。
最初代码与错误
最初的Factory代码试图将news_media_type放入返回数组,但News表无此字段,引发SQL错误:
<?php class NewsFactory extends Factory { protected $model = News::class; public function definition(): array { /// ... $newsMediaType = $this->faker->randomElement([ '', /* no media */ 'Y', /* youtube url */ 'I', /* uploaded image */ 'V', /* uploaded video */ ]); $youtubeUrl = ''; if($newsMediaType === 'Y') { $this->faker->addProvider(new \Faker\Provider\Youtube($this->faker)); $youtubeUrl = $this->faker->youtubeUri(); } return [ // ... 'youtube_url' => $youtubeUrl, 'news_media_type' => $newsMediaType, // 模型无此字段,导致插入失败 ]; } public function configure(): self { return $this->afterCreating(function (News $newNews) { if ($newNews->news_media_type === 'I') { $newNews->addMediaFromUrl('https://loremflickr.com/480/360') ->storingConversionsOnDisk('local') ->toMediaCollection('news_images'); } }); } }
错误信息:
SQLSTATE[42703]: Undefined column: 7 ERROR: column "news_media_type" of relation "news" does not exist LINE 1: ...t into "news" ("title", "content", "youtube_url", "news_medi... ^ (SQL: insert into "news" ("title", "content", "youtube_url", "news_media_type", "published_at", "created_at", "updated_at") values (Test news Quo dolores laudantium illum., , , I, 2023-06-01 16:28:33, 2023-06-09 12:29:35, 2023-06-09 12:29:35) returning "id
尝试protected变量的问题
改用类成员变量$newsMediaType后,日志显示configure先执行(变量为空),definition赋值后,afterCreating闭包中变量值丢失:
<?php namespace Database\Factories; use App\Modules\News\Models\News; use Illuminate\Database\Eloquent\Factories\Factory; class NewsFactory extends Factory { protected $model = News::class; protected $newsMediaType = null; public function definition(): array { $this->newsMediaType = $this->faker->randomElement(['', 'Y', 'I', 'V']); $this->newsMediaType = 'I'; // 测试固定值 \Log::info(' -10 $this->newsMediaType::'); \Log::info($this->newsMediaType); $youtubeUrl = ''; if($this->newsMediaType === 'Y') { $this->faker->addProvider(new \Faker\Provider\Youtube($this->faker)); $youtubeUrl = $this->faker->youtubeUri(); } return [ 'youtube_url' => $youtubeUrl, ... ]; } public function configure(): self { \Log::info( ' -12 $this->newsMediaType::'); \Log::info($this->newsMediaType); return $this->afterCreating(function (News $newNews) { \Log::info( ' -14 $this->newsMediaType::'); \Log::info($this->newsMediaType); if ($this->newsMediaType === 'I') { $newNews->addMediaFromUrl('https://loremflickr.com/480/360') ->storingConversionsOnDisk('local') ->toMediaCollection('news_images'); } }); } }
日志输出:
[2023-06-10 06:10:50] local.INFO: -12 $this->newsMediaType:: [2023-06-10 06:10:50] local.INFO: [2023-06-10 06:10:50] local.INFO: -10 $this->newsMediaType:: [2023-06-10 06:10:50] local.INFO: I [2023-06-10 06:10:50] local.INFO: -14 $this->newsMediaType:: [2023-06-10 06:10:50] local.INFO:
问题原因
- 最初错误:
definition返回的数组包含模型/数据表不存在的字段,Laravel会将所有返回字段插入数据库,导致SQL字段不存在错误。 - 变量丢失原因:
configure在Factory实例初始化时就执行,definition是创建模型时才执行;afterCreating闭包绑定的是初始化时的变量引用,后续definition对变量的修改无法同步到闭包中。
解决方案
方案1:使用Factory状态方法(推荐)
为每种媒体类型定义独立状态,按需调用生成:
<?php namespace Database\Factories; use App\Modules\News\Models\News; use Illuminate\Database\Eloquent\Factories\Factory; class NewsFactory extends Factory { protected $model = News::class; public function definition(): array { return [ // 其他字段定义 'youtube_url' => '', ]; } // 无媒体 public function noMedia(): self { return $this->state(fn (array $attributes) => []); } // YouTube视频 public function youtube(): self { $this->faker->addProvider(new \Faker\Provider\Youtube($this->faker)); return $this->state(fn (array $attributes) => [ 'youtube_url' => $this->faker->youtubeUri(), ]); } // 上传图片 public function image(): self { return $this->afterCreating(function (News $news) { $news->addMediaFromUrl('https://loremflickr.com/480/360') ->storingConversionsOnDisk('local') ->toMediaCollection('news_images'); }); } // 上传视频 public function video(): self { return $this->afterCreating(function (News $news) { $news->addMediaFromUrl('https://example.com/test-video.mp4') ->storingConversionsOnDisk('local') ->toMediaCollection('news_videos'); }); } }
使用方式:
// 随机生成不同类型 $types = ['noMedia', 'youtube', 'image', 'video']; News::factory()->{$this->faker->randomElement($types)}()->create(); // 生成指定类型 News::factory()->image()->create();
方案2:在definition中直接绑定闭包捕获变量
在definition中生成媒体类型后,直接创建afterCreating闭包并通过use捕获当前值:
<?php namespace Database\Factories; use App\Modules\News\Models\News; use Illuminate\Database\Eloquent\Factories\Factory; class NewsFactory extends Factory { protected $model = News::class; public function definition(): array { $newsMediaType = $this->faker->randomElement(['', 'Y', 'I', 'V']); $youtubeUrl = ''; if ($newsMediaType === 'Y') { $this->faker->addProvider(new \Faker\Provider\Youtube($this->faker)); $youtubeUrl = $this->faker->youtubeUri(); } // 闭包通过use捕获当前的$newsMediaType值 $this->afterCreating(function (News $news) use ($newsMediaType) { if ($newsMediaType === 'I') { $news->addMediaFromUrl('https://loremflickr.com/480/360') ->storingConversionsOnDisk('local') ->toMediaCollection('news_images'); } elseif ($newsMediaType === 'V') { $news->addMediaFromUrl('https://example.com/test-video.mp4') ->storingConversionsOnDisk('local') ->toMediaCollection('news_videos'); } }); return [ // 其他字段 'youtube_url' => $youtubeUrl, ]; } }
内容的提问来源于stack exchange,提问作者mstdmstd
相关产品推荐
相关产品推荐

