Laravel:Migrations&播种生产数据

时间:2014-02-05 14:32:19

标签: php database laravel laravel-4 database-migration

我的应用程序需要预先注册的数据集才能工作。所以我需要在设置应用程序时将它们插入数据库中。

Laravel提出了两种机制:

  • Database migrations“他们允许团队修改数据库架构并保持最新的当前架构状态。”
  • Database seeding“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');
            }
    }

}

当然,这个解决方案有效。但我不确定这是否是正确的方法,因为通过使用播种机插入数据,您将失去迁移机制提供的所有优势(数据库升级,回滚......)

我想知道在这种情况下最佳做法是什么。

4 个答案:

答案 0 :(得分:61)

Laravel的发展与自由有关。那么,如果您需要为生产数据库设定种子并认为DatabaseSeeder是最佳选择,那么为什么不呢?

好的,播种机主要用于测试数据,但你会看到一些人在使用它。

我将这种重要的种子视为迁移的一部分,因为这是我的数据库表之外的事情,每次部署新版本的应用程序时都会运行artisan migrate,所以我只是这样做

php artisan migrate:make seed_models_table

并在其中创建我的种子:

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

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

答案 1 :(得分:29)

我经常发现自己想知道对此的正确答案是什么。就个人而言,我没有避免使用种子来填充数据库中所需的行,因为您必须加载一些条件逻辑以确保您不会尝试填充“#39”中的内容。已经存在了。 (删除和重新创建数据非常是不可取的,因为您最终可能会出现密钥不匹配,如果您使用级联删除,则可能会意外擦除数据库的负载!! - )< / p>

我把'播种'&#39;如果有机会进入迁移脚本,那么数据将需要作为推出过程的一部分存在。

值得注意的是,您应该使用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();
}

然后这让我可以播​​种#39;我第一次创建它时很容易在城镇表中使用,并且不会干扰在运行时对其进行的任何添加。

答案 2 :(得分:3)

Artisan Command Solution

  1. 创建新的工匠命令

    php artisan make:command UpsertConfigurationTables

  2. 将其粘贴到新生成的文件中: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);
        }
    }
    
  3. 用您想要播种的雄辩模型填充$_models

  4. 定义模型中的种子行: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
            ],
        ];
    }
    
  5. 将命令添加到您的Laravel Forge部署脚本(或任何其他CI部署脚本):php artisan upsert:configuration

其他值得注意的事情:

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

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

  • 播种机:在生产中运行播种机是对播种机的滥用。我担心的是,将来的工程师会改变播种者的想法,认为这是无害的,因为文档指出他们是设计用来播种测试数据的。
  • 迁移:在迁移中播种数据很奇怪,并且滥用了迁移目的。一旦运行了迁移,它也不允许您更新这些值。

答案 3 :(得分:1)

这是我在生产中使用的。

自从我在每个部署上运行迁移

artisan migrate

我使用以下方法创建了一个种子器(只是为了防止在迁移过程中保留种子数据,以便以后访问,并仅查看种子目录中的文件就知道了我播种了哪些数据)

php artisan make:seeder YourTableSeeder

,然后在迁移中调用它。

class YourTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {    
        $seeder = new YourTableSeeder();
        $seeder->run();
    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
    }
}

我不会将此种子调用添加到seed / DatabaseSeeder.php中,以避免在新安装中运行两次。