php Laravel:生产数据的迁移和播种
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21580088/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Laravel : Migrations & Seeding for production data
提问by gontard
My application needs a pre registered data set to work. So i need to insert them in the database when i set up the application.
我的应用程序需要一个预先注册的数据集才能工作。所以我需要在设置应用程序时将它们插入到数据库中。
Laravelpropose two mechanisms :
Laravel提出了两种机制:
- Database migrations: "They allow a team to modify the database schema and stay up to date on the current schema state."
- Database seeding: "Laravel also includes a simple way to seed your database with test data using seed classes."
When I read this description, none of these solutions seems to be adapted.
当我阅读此描述时,这些解决方案似乎都没有经过调整。
A similar question has been asked on stackoverflowand answered. The answer proposes to use the a database seeder to populate the database by detecting the current environment :
在 stackoverflow 上提出了一个类似的问题并回答了。答案建议使用数据库播种器通过检测当前环境来填充数据库:
<?php
class DatabaseSeeder extends Seeder {
public function run()
{
Eloquent::unguard();
if (App::environment() === 'production')
{
$this->call('ProductionSeeder');
}
else
{
$this->call('StagingSeeder');
}
}
}
Of course, this solution works. But i am not sure that it is the right way to do this, because by inserting data using seeders you are losing all the advantages provided by the migration mechanism (database upgrate, rollback...)
当然,这个解决方案是有效的。但我不确定这是正确的方法,因为通过使用播种机插入数据,您将失去迁移机制提供的所有优势(数据库升级、回滚......)
I want to know what is the best practice in this case.
我想知道在这种情况下什么是最佳实践。
回答by Antonio Carlos Ribeiro
Laravel development is about freedom. So, if you need to seed your production database and think DatabaseSeeder is the best place to do so, why not?
Laravel 的开发是关于自由的。因此,如果您需要为生产数据库做种并且认为 DatabaseSeeder 是最好的地方,为什么不呢?
Okay, seeder is mainly to be used with test data, but you'll see some folks using it as you are.
好的,seeder 主要用于测试数据,但您会看到一些人按原样使用它。
I see this important kind of seed as part of my migration, since this is something that cannot be out of my database tables and artisan migrateis ran everytime I deploy a new version of my application, so I just do
我认为这种重要的种子是我迁移的一部分,因为它不能从我的数据库表中删除,并且artisan migrate每次我部署应用程序的新版本时都会运行,所以我只是这样做
php artisan migrate:make seed_models_table
And create my seedind stuff in it:
并在其中创建我的种子内容:
public function up()
{
$models = array(
array('name' => '...'),
);
DB::table('models')->insert($models);
}
回答by Dan B
I've often found myself wondering what the right answer to this is. Personally, I'd steer clear of using seeding to populate required rows in the database as you'll have to put a load of conditional logic in to ensure that you don't attempt to populate something that's already there. (Deleting and recreating the data is veryinadvisable as you could end up with key mismatches and if you're using cascading deletes you may accidentally wipe a load of your database my mistake! ;-)
我经常发现自己想知道这个问题的正确答案是什么。就我个人而言,我会避免使用播种来填充数据库中所需的行,因为您必须加载大量条件逻辑以确保您不会尝试填充已经存在的内容。(删除和重新创建数据是非常不可取的,因为您最终可能会遇到密钥不匹配的情况,如果您使用级联删除,您可能会不小心擦除数据库负载,这是我的错误!;-)
I put the 'seeding' of rows into the migration script as the chances are, the data will need to be there as part of the rollout process.
我尽可能将行的“播种”放入迁移脚本中,数据将需要作为部署过程的一部分存在。
It's worth noting that you should use the DB class instead of Eloquent models to populate this data as your class structure could change over time which will then prevent you from re-creating the database from scratch (without rewriting history and changing you migration files, which I'm sure is a bad thing.)
值得注意的是,您应该使用 DB 类而不是 Eloquent 模型来填充这些数据,因为您的类结构可能会随着时间的推移而发生变化,这将阻止您从头开始重新创建数据库(无需重写历史记录和更改迁移文件,我肯定是坏事。)
I'd tend to go with something like this:
我倾向于这样的事情:
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();
}
This then allows me to 'seed' the town table easily when I first create it, and wont interfere with any additions made to it at run time.
这使我可以在第一次创建城镇表时轻松“播种”它,并且不会干扰运行时对其进行的任何添加。
回答by Manpreet
This is what I use in production.
这是我在生产中使用的。
Since I run migration on each deployment
因为我在每个部署上运行迁移
artisan migrate
I create a seeder (just to keep seeding data out of migration for easy access later) and then run that seeder along with the migration
我创建了一个播种机(只是为了将播种数据排除在迁移之外以便以后轻松访问),然后在迁移的同时运行该播种机
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');
}
}
I do not add this seed call to seeds/DatabaseSeeder.php to avoid running it twice on a new installation.
我不会将此种子调用添加到seeds/DatabaseSeeder.php 以避免在新安装中运行它两次。
回答by Adam Berry
The Artisan Command Solution
Artisan 命令解决方案
Create a new artisan command
php artisan make:command UpsertConfigurationTablesPaste this into the newly generated file:
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); } }Populate
$_modelswith the Eloquent models you want to seed.Define the seed rows in the model:
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 ], ]; }Add the command to your Laravel Forge deployment script (or any other CI deployment script):
php artisan upsert:configuration
创建一个新的工匠命令
php artisan make:command UpsertConfigurationTables将其粘贴到新生成的文件中:
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); } }填充
$_models您想要播种的 Eloquent 模型。定义模型中的种子行:
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 ], ]; }将命令添加到 Laravel Forge 部署脚本(或任何其他 CI 部署脚本):
php artisan upsert:configuration
Other noteworthy things:
其他值得注意的事情:
- Upsert Functionality:If you ever want to alter any of the seeded rows, simply update them in your model and it was update your database values next time you deploy. It will never create duplicate rows.
- Soft-Deletable Models:Note that you define deletions by setting
deleted_attotrueorfalse. The Artisan command will handle calling the correct method to delete or recover your record.
- Upsert 功能:如果您想更改任何种子行,只需在您的模型中更新它们,下次部署时就会更新您的数据库值。它永远不会创建重复的行。
- 软删除模型:请注意,您可以通过设置
deleted_at为true或来定义删除false。Artisan 命令将调用正确的方法来删除或恢复您的记录。
Problems With Other Mentioned Solutions:
其他提到的解决方案的问题:
- Seeder:Running seeders in production is an abuse of the seeders. My concern would be that an engineer in the future would alter the seeders thinking that it's harmless since the documentation states that they are designed to seed test data.
- Migrations:Seeding data in a migration is strange and a an abuse of the purpose of the migration. It also doesn't let you update these values once your migration has been run.
- 播种机:在生产中运行播种机是对播种机的滥用。我担心的是,未来的工程师会改变播种者,认为它是无害的,因为文档指出它们旨在播种测试数据。
- 迁移:在迁移中播种数据很奇怪,而且是对迁移目的的滥用。一旦运行迁移,它也不允许您更新这些值。

