在Laravel迁移文件中填充数据库

169

我正在学习 Laravel,并且有一个可以工作的迁移文件来创建一个用户表。我正在尝试在迁移过程中填充用户记录:

public function up()
{
    Schema::create('users', function($table){

        $table->increments('id');
        $table->string('email', 255);
        $table->string('password', 64);
        $table->boolean('verified');
        $table->string('token', 255);
        $table->timestamps();

        DB::table('users')->insert(
            array(
                'email' => `name@domain.example`,
                'verified' => true
            )
        );

    });
}

但是当我运行php artisan migrate时,出现了以下错误:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'vantage.users' doesn't exist

这显然是因为Artisan还没有创建表格,但所有文档似乎都说可以使用Fluent Query作为迁移的一部分来填充数据。有人知道怎么做吗?

1
我在想这样做是否正确,为什么不使用Seeder呢? - SyncroIT
9个回答

297

不要把DB::insert()放在Schema::create()内部,因为create方法必须完成表格的创建工作,然后才能插入数据。请尝试使用以下代码:

public function up()
{
    // Create the table
    Schema::create('users', function($table){
        $table->increments('id');
        $table->string('email', 255);
        $table->string('password', 64);
        $table->boolean('verified');
        $table->string('token', 255);
        $table->timestamps();
    });

    // Insert some stuff
    DB::table('users')->insert(
        array(
            'email' => 'name@domain.example',
            'verified' => true
        )
    );
}

7
如何插入多条数据? - Sahbaz
17
我可以翻译这段代码: @SuperMario'sYoshi,我认为类似这样的内容 DB::table('users')->insert([ ['email' => 'taylor@example.com', 'votes' => 0], ['email' => 'dayle@example.com', 'votes' => 0] ]); - Денис

106

我知道这是一篇旧文章,但由于在谷歌搜索中出现了这篇文章,我想在这里分享一些知识。@erin-geyer指出混合使用数据库迁移和填充数据脚本可能会带来麻烦,而@justamartin则反驳说有时您需要在部署过程中填充数据。

我认为有时候更希望能够一致地推出数据更改,这样就可以将应用程序部署到测试环境,检查一切是否正常,然后放心地将其部署到生产环境,确保结果相同(而不必记住运行某些手动步骤)。

但是,将填充数据的脚本和数据库迁移分开仍然具有价值,因为它们是两个相关但不同的问题。我们的团队通过创建调用填充数据脚本的数据库迁移来进行妥协。代码如下:

public function up()
{
    Artisan::call( 'db:seed', [
        '--class' => 'SomeSeeder',
        '--force' => true ]
    );
}

这使您可以像迁移一样仅执行一次种子。您还可以实现阻止或增强行为的逻辑。例如:

public function up()
{
    if ( SomeModel::count() < 10 )
    {
        Artisan::call( 'db:seed', [
            '--class' => 'SomeSeeder',
            '--force' => true ]
        );
    }
}

如果存在少于10个SomeModels,那么这显然会有条件地执行你的填充器。 如果想要将填充器作为标准填充器包含在调用artisan db:seed时执行以及迁移时执行,以便不会“重复”,则此项功能非常有用。 您还可以创建反向填充器,以使回滚按预期工作,例如:

public function down()
{
    Artisan::call( 'db:seed', [
        '--class' => 'ReverseSomeSeeder',
        '--force' => true ]
    );
}

在生产环境下运行seeder需要使用第二个参数--force


2
这绝对是最好的答案。可维护的代码,分离关注点! - helsont
39
当从迁移脚本中调用种子数据时,需要谨慎考虑长期影响。迁移脚本是按时间版本化的,而种子数据通常不是。在开发过程中,种子数据的需求经常会发生变化,可能导致版本化的迁移脚本运行非版本化的种子数据,从而破坏幂等性。换句话说,从一天到另一天运行相同的迁移脚本集可能会产生不同的结果。 - originalbryan
3
我已经有一段时间没发布这篇文章了,我想提供我们使用这种技术的经验。总体来说,它对我们很有效,如果我必须再做一遍,我还是会选择使用它。不过要注意一个问题。@originalbryan 是完全正确的,结果是有时候当我们启动一个新的数据库并运行迁移时,可能会遇到迁移失败的情况,因为当迁移运行时,Seeder(和模型)比数据库更加更新(因为我们可能在架构完全更新之前进行数据填充)。当出现这种情况时,我们将更新旧的迁移以解决此问题。 - darrylkuhn
2
@darrylkuhn,是的,您的建议违反了规则。如果我们不在迁移文件中调用seeder,而是直接在迁移文件中放置“INSERT-s”,那么我们就可以在不更改旧迁移文件的情况下保存数据一致性。实际上,我并没有看到在迁移文件中放置INSERT的问题 - 例如,我有一个包含30个表的系统,并且想要添加需要一些初始数据的新功能 - 所以我将该初始数据放在迁移文件中(在单独的方法中)- 没有问题:P - Kamil Kiełczewski
10
Laravel的所有语言都暗示seeder用于测试数据,因此我认为设计时应该牢记这一点。区分应用程序数据和测试数据非常重要,将所需数据直接包含在迁移中可以非常清晰地区分它们。 - Brettins
显示剩余4条评论

19

4
我同意你的看法,Erin。不要将迁移和种子数据混淆,因为你很有可能想要在开发环境中种植一些数据,但不想在生产环境中这样做。 - Daniel Vigueras
32
很好的观点,但有些情况下,某些数据必须存在于生产环境中。例如,第一个默认管理员用户必须存在,以便客户可以首次登录,一些预设授权角色也必须存在,一些业务逻辑数据也可能需要立即使用。因此,我认为强制性数据应该添加到迁移中(这样你就可以通过单独的迁移记录上/下数据记录),但种子数据可以留给开发环境。 - JustAMartin
一个小提示:数据库填充的链接现在是:https://laravel.com/docs/5.3/seeding - magikMaker
3
请包含链接文章的相关部分。不鼓励“仅链接答案”。您提供的第一个链接已经失效,我必须从archive.org中找回它! - totymedli

10
如果您正在使用 Laravel 8 并且想要初始化多条记录,您可以通过以下两种方式之一实现。
1. 不建议的方法
public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });

        DB::table('categories')->insert(
            array(
                [
                    'name' => 'Category1',
                ],
                [
                    'name' => 'Category2',
                ],
                [
                    'name' => 'Category3',
                ],
            )
        );
    }

上述方法可行,但会使"created_at"和"updated_at"列保持空白。
2. 推荐的方式:
 public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });


        $data =  array(
            [
                'name' => 'Category1',
            ],
            [
                'name' => 'Category2',
            ],
            [
                'name' => 'Category3',
            ],
        );
        foreach ($data as $datum){
            $category = new Category(); //The Category is the model for your migration
            $category->name =$datum['name'];
            $category->save();
        }

    }

2
它不起作用,因为模型在那个时间点不存在......永远不要在迁移中使用模型,因为你可能会引用在某些时候被删除的模型......我最近在处理其他开发者犯的类似错误。 - salih0vicX
如果可以的话,我会给@salih0vicX点赞十几次。在迁移中使用任何优雅的东西都可能会导致后续的重大问题。迁移功能是一个时间点,而代码将始终是“当前”的。在任何长期运行的项目中,不可避免地会出现不兼容性。 - undefined

5
这应该可以满足你的需求。
public function up()
{
    DB::table('user')->insert(array('username'=>'dude', 'password'=>'z19pers!'));
}

1

我尝试了这种数据库插入方法,但是它没有使用模型,忽略了我在模型上的可缩放特性。所以,考虑到此表的模型已经存在,一旦迁移完成,我想模型将可用于插入数据。然后我想出了这个:

public function up() {
        Schema::create('parent_categories', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('slug');
            $table->timestamps();
        });
        ParentCategory::create(
            [
                'id' => 1,
                'name' => 'Occasions',
            ],
        );
    }

这段代码正确运行,并考虑了我的模型上的 sluggable trait,自动生成该条目的 slug,并使用时间戳。 注意:添加ID并非必需,但在这个示例中我想要特定的ID。 已在 Laravel 5.8 上测试通过。

1
另一个干净的方法是定义一个私有方法,它创建实例并持久化相关的模型。
public function up()
{
    Schema::create('roles', function (Blueprint $table) {
        $table->increments('id');
        $table->string('label', 256);
        $table->timestamps();
        $table->softDeletes();
    });

    $this->postCreate('admin', 'user');
}

private function postCreate(string ...$roles)  {
    foreach ($roles as $role) {
        $model = new Role();
        $model->setAttribute('label', $role);
        $model->save();
    }
}

使用这个解决方案,Eloquent将生成时间戳字段。
编辑: 最好使用种子系统来区分数据库结构生成和数据库填充。

我喜欢这个...它完全满足了我的需求,在迁移中默认添加了一些用户角色。需要确保导入模型或直接引用它 $model = new App\UserRoles();,但除此之外...完美! - FAB

0

通过编辑Martin Mbae的答案中的第一个选项,如果你想要与第二个选项相同的结果但不使用模型,你只需要这样做:

public function up()
{
    Schema::create('categories', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->timestamps();
    });

    $sampleData = ['sample1', 'sample2', 'sample3'];
    $data = [];

    foreach ($sampleData as $sample) {
        $data[] = [
            'table_name' => $sample,
            'created_at' => now(),
            'updated_at' => now()
        ];
    }
    
    DB::table('categories')->insert($data);
}

0

如果您已经填写了列并添加了新列,或者想要使用新的模拟值填写旧列,请执行以下操作:

public function up()
{
    DB::table('foydabars')->update(
        array(
            'status' => '0'
        )
    );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接