Laravel:生产数据的迁移和播种

bmp9r5qi  于 2023-06-07  发布在  其他
关注(0)|答案(5)|浏览(183)

我的应用程序需要一个预先注册的数据集才能工作。所以我需要在设置应用程序时将它们插入数据库。
Laravel提出了两种机制:

  • Database migrations“它们允许团队修改数据库模式并保持当前模式状态的最新状态。"
  • 数据库播种:* “Laravel还包括一个简单的方法,使用种子类用测试数据来播种数据库。"*

当我读到这个描述时,这些解决方案似乎都没有被改编。
一个类似的问题是asked on stackoverflowanswered。答案建议使用数据库播种器通过检测当前环境来填充数据库:

<?php

class DatabaseSeeder extends Seeder {

    public function run()
    {
            Eloquent::unguard();

            if (App::environment() === 'production')
            {
                $this->call('ProductionSeeder');
            }
            else
            {
                $this->call('StagingSeeder');
            }
    }

}

当然,这个解决方案是有效的。但我不确定这是正确的方法,因为通过使用seeders插入数据,您将失去迁移机制提供的所有优势(数据库升级、回滚……)
我想知道在这种情况下什么是最好的做法。

x3naxklr

x3naxklr1#

Laravel的开发是关于自由的。因此,如果您需要为生产数据库播种,并且认为DatabaseSeeder是最佳的位置,为什么不呢?
好的,seeder主要用于测试数据,但是您会看到一些人像您一样使用它。
我将这类重要的种子视为迁移的一部分,因为这是数据库表中不可缺少的部分,而且每次部署应用程序的新版本时都会运行artisan migrate,所以我就这样做了

php artisan migrate:make seed_models_table

在里面创造我的种子:

public function up()
{
    $models = array(
        array('name' => '...'),
    );

    DB::table('models')->insert($models);
}
d8tt03nd

d8tt03nd2#

我经常发现自己在想什么是正确的答案。就我个人而言,我会避免使用种子来填充数据库中所需的行,因为你必须放入一个条件逻辑负载,以确保你不会试图填充已经存在的东西。(删除和重新创建数据是非常不可取的,因为你可能会以键不匹配而告终,如果你使用级联删除,你可能会不小心擦除数据库的负载!;-)
我将行的“种子”放入迁移脚本中,因为数据需要作为推出过程的一部分。
值得注意的是,你应该使用DB类而不是Eloquent模型来填充这些数据,因为你的类结构可能会随着时间的推移而改变,这将阻止你从头开始重新创建数据库(而不重写历史记录和更改迁移文件,我相信这是一件坏事)。
我倾向于这样的:

public function up()
{
    DB::beginTransaction();

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

    DB::table('town')
        ->insert(
            array(
                array('London'),
                array('Paris'),
                array('New York')
            )
        );

    Schema::create(
        'location',
        function (Blueprint $table) {
            $table->increments('id');
            $table->integer('town_id')->unsigned()->index();
            $table->float('lat');
            $table->float('long');
            $table->timestamps();

            $table->foreign('town_id')->references('id')->on('town')->onDelete('cascade');
        }
    );
    
    DB::commit();
}

这样,当我第一次创建城镇表时,就可以很容易地为它“播种”,并且不会干扰在运行时对它进行的任何添加。

w80xi6nr

w80xi6nr3#

这就是我在生产中使用的。
由于我在每个部署上运行迁移

artisan migrate

我创建了一个seeder(只是为了将播种数据排除在迁移之外,以便于以后访问),然后在迁移过程中运行该seeder

class YourTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {    
        //migrate your table // Example
        Schema::create('test_table', function(Blueprint $table)
        {
            $table->increments('id');
            $table->timestamps();
            $table->softDeletes();
        });

        //seed this table
        $seeder = new YourTableSeeder();
        $seeder->run();
    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
        Schema::drop('test_table');
    }
}

我没有将这个seed调用添加到seeds/DatabaseSeeder.php中,以避免在新安装中运行两次。

wz3gfoph

wz3gfoph4#

Artisan Command解决方案
1.创建新的Artisan命令
php artisan make:command UpsertConfigurationTables
1.将其粘贴到新生成的文件中:UpsertConfigurationTables.php

<?php

namespace App\Console\Commands;

use Exception;
use Illuminate\Console\Command;

class UpsertConfigurationTables extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'upsert:configuration';

    /**
     * The console command description.
     *
     * @var string
     */
     protected $description = 'Upserts the configuration tables.';

    /**
     * The models we want to upsert configuration data for
     *
     * @var array
     */
    private $_models = [
        'App\ExampleModel'
    ];

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        foreach ($this->_models as $model) {

            // check that class exists
            if (!class_exists($model)) {
                throw new Exception('Configuration seed failed. Model does not exist.');
            }

            // check that seed data exists
            if (!defined($model . '::CONFIGURATION_DATA')) {
                throw new Exception('Configuration seed failed. Data does not exist.');
            }

            /**
             * seed each record
             */
            foreach ($model::CONFIGURATION_DATA as $row) {
                $record = $this->_getRecord($model, $row['id']);
                foreach ($row as $key => $value) {
                    $this->_upsertRecord($record, $row);
                }
            }
        }
    }

    /**
     * _fetchRecord - fetches a record if it exists, otherwise instantiates a new model
     *
     * @param string  $model - the model
     * @param integer $id    - the model ID
     *
     * @return object - model instantiation
     */
    private function _getRecord ($model, $id)
    {
        if ($this->_isSoftDeletable($model)) {
            $record = $model::withTrashed()->find($id);
        } else {
            $record = $model::find($id);
        }
        return $record ? $record : new $model;
    }

    /**
     * _upsertRecord - upsert a database record
     *
     * @param object $record - the record
     * @param array  $row    - the row of update data
     *
     * @return object
     */
    private function _upsertRecord ($record, $row)
    {
        foreach ($row as $key => $value) {
            if ($key === 'deleted_at' && $this->_isSoftDeletable($record)) {
                if ($record->trashed() && !$value) {
                    $record->restore();
                } else if (!$record->trashed() && $value) {
                    $record->delete();
                }
            } else {
                $record->$key = $value;
            }
        }
        return $record->save();
    }

    /**
     * _isSoftDeletable - Determines if a model is soft-deletable
     *
     * @param string $model - the model in question
     *
     * @return boolean
     */
    private function _isSoftDeletable ($model)
    {
        $uses = array_merge(class_uses($model), class_uses(get_parent_class($model)));
        return in_array('Illuminate\Database\Eloquent\SoftDeletes', $uses);
    }
}

1.用您想要播种的Eloquent模型填充$_models
1.在模型中定义种子行:const CONFIGURATION_DATA

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class ExampleModel extends Model
{
    use SoftDeletes;

    const CONFIG_VALUE_ONE = 1;
    const CONFIG_VALUE_TWO = 2;
    const CONFIGURATION_DATA = [
        [
            'id'         => self::CONFIG_VALUE_ONE,
            'col1'       => 'val1',
            'col2'       => 'val2',
            'deleted_at' => false
        ],
        [
            'id'         => self::CONFIG_VALUE_TWO,
            'col1'       => 'val1',
            'col2'       => 'val2',
            'deleted_at' => true
        ],
    ];
}

1.将命令添加到Laravel Forge部署脚本(或任何其他CI部署脚本):php artisan upsert:configuration
其他值得注意的事情:

***更新插入功能:**如果您想要更改任何种子行,只需在模型中更新它们,并且在下次部署时更新数据库值。它永远不会创建重复的行。
***软删除模型:**请注意,通过将deleted_at设置为truefalse来定义删除。Artisan命令将处理调用正确的方法来删除或恢复记录。

其他提到的解决方案的问题:

***播种机:**在生产中运行播种机是对播种机的滥用。我担心的是,将来的工程师会改变播种器,认为这是无害的,因为文档中说明它们是为播种测试数据而设计的。
***迁移:**在迁移中播种数据是奇怪的,并且是对迁移目的的滥用。它也不允许您在迁移运行后更新这些值。

fcg9iug3

fcg9iug35#

我也碰到了这个。我最终在迁移中添加了一个artisan命令来运行seeder。

use Illuminate\Support\Facades\Artisan;

return new class extends Migration
{

    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('roles', function (Blueprint $table) {
            $table->id();
    });

    Artisan::call('db:seed --class=DataSeeder --force');
}

相关问题