说明:本文主要学习Schema Builder和Migration System的使用及相关原理。传统上在设计database时需要写大量的SQL语句,但Laravel提供了Schema Builder这个神器使得在设计database时使用面向对象方法来做,不需要写一行SQL,并且还提供了另一个神器Migration System,可以对database做版本控制,包括回滚上一次的迁移操作。本小系列主要分为上中下三篇。本篇主要学习使用Schema Builder来creating,dropping,updating tables;adding,removing,renaming columns;simple index,unique index,foreign keys
,同时也会学习相关源码来进一步了解Schema Builder。
开发环境: Laravel5.3 + PHP7
表的操作-tables
在设计database时需要创建、删除和更新表,Schema Builder类提供了一些methods来面向对象的执行这些操作,而不需要写一行SQL。在写Laravel程序时,也经常使用类似命令php artisan make:migration create_accounts_table --create=accounts
来做一个迁移类创建数据表,会在database/migrations
文件夹下得到类似如下的代码类:
use Illuminate\Support\Facades\Schema;use Illuminate\Database\Schema\Blueprint;use Illuminate\Database\Migrations\Migration;class CreateAccountsTable extends Migration{ /** * Run the migrations. * * @return void */ public function up() { Schema::create('accounts', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('accounts'); }}
(1)creating tables
在执行php artisan migrate
命令时操作的是up()
方法中语句。在创建的迁移类CreateAccountsTable
中,Schema::create()
就是创建表的语句,并且第一个参数就是表的名字,第二个参数是个闭包,是操作columns的语句,并且参数是个Blueprint对象。为什么有这么奇怪的写法呢?
看下Schema Facade中getFacadeAccessor
的源码:
/** * Get a schema builder instance for the default connection. * * @return \Illuminate\Database\Schema\Builder */ protected static function getFacadeAccessor() { // 这里'db'服务是在DatabaseServiceProvider中定义的,是DatabaseManager对象,且laravel默认connection是mysql // 则返回的是MysqlBuilder,也就是\Illuminate\Database\Schema\Builder的子类 return static::$app['db']->connection()->getSchemaBuilder(); }
根据注释就知道Schema::create
就是等同于MysqlBuilder::create()
,看下源码:
// \Illuminate\Database\Schema\Builder /** * Create a new table on the schema. * * @param string $table * @param \Closure $callback * @return \Illuminate\Database\Schema\Blueprint */ public function create($table, Closure $callback) { /** @var \Illuminate\Database\Schema\Blueprint $blueprint */ $blueprint = $this->createBlueprint($table); // 添加'create'命令 $blueprint->create(); // 执行闭包里的操作,也就是操作columns $callback($blueprint); $this->build($blueprint); } protected function createBlueprint($table, Closure $callback = null) { if (isset($this->resolver)) { return call_user_func($this->resolver, $table, $callback); } return new Blueprint($table, $callback); } // Illuminate\Database\Schema\Blueprint public function create() { return $this->addCommand('create'); } protected function build(Blueprint $blueprint) { $blueprint->build($this->connection, $this->grammar); }
create()
中的$callback($blueprint);
语句执行了闭包操作,并且闭包参数还是个Blueprint对象。最关键的方法时Blueprint对象的build()
方法,下文再聊具体细节。
当然,Schema Builder是可以在任意模块中使用的,如在路由中使用,执行https://localhost:8888/create_accounts
就可以创建一个accounts
表了:
Route::get('create_accounts', function () { \Illuminate\Support\Facades\Schema::create('accounts', function(\Illuminate\Database\Schema\Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('number'); $table->tinyInteger('status'); $table->enum('source', ['bank account', 'credit card', 'investment account']); $table->timestamps(); });});
(2)dropping tables
Schema Builder提供了两个方法来删除表:drop(string $table)
和dropIfExists(string $table)
,参数是表名,dropIfExists()
表示只有在表存在才删除,所以dropIfExists()
比drop()
更优雅。看下两个方法的源码:
/** * Drop a table from the schema. * * @param string $table * @return \Illuminate\Database\Schema\Blueprint */ public function drop($table) { $blueprint = $this->createBlueprint($table); $blueprint->drop(); $this->build($blueprint); } /** * Drop a table from the schema if it exists. * * @param string $table * @return \Illuminate\Database\Schema\Blueprint */ public function dropIfExists($table) { $blueprint = $this->createBlueprint($table); $blueprint->dropIfExists(); $this->build($blueprint); } // Illuminate\Database\Schema\Blueprint public function drop() { return $this->addCommand('drop'); } public function dropIfExists() { return $this->addCommand('dropIfExists'); }
同样是使用了Blueprint对象来添加命令drop
和dropIfExists
。在路由中删除accounts
表:
Route::get('delete_accounts', function () { \Illuminate\Support\Facades\Schema::dropIfExists('accounts');// \Illuminate\Support\Facades\Schema::drop('accounts');});
(3)updating tables
更新表的操作包括更新表名和更新表字段。
使用Schema::rename($from, $to)
方法来更新表名:
Route::get('rename_bank_accounts', function () { \Illuminate\Support\Facades\Schema::rename('accounts', 'bank_accounts');});
看下Schema的rename()
源码,同样是使用Blueprint对象添加rename
命令:
public function rename($from, $to) { $blueprint = $this->createBlueprint($from); $blueprint->rename($to); $this->build($blueprint); } // Illuminate\Database\Schema\Blueprint public function rename($to) { return $this->addCommand('rename', compact('to')); }
使用Schema::table()
方法来更新表字段值,如更新accounts
的number
字段,,不过如果该表中有字段类型为enum
就不支持修改:
Route::get('update_accounts', function () { \Illuminate\Support\Facades\Schema::table('accounts', function(\Illuminate\Database\Schema\Blueprint $table) { $table->string('number', 50)->change(); });});
同时,Schema还提供了几个有用的方法,如hasTable($table),hasColumn($table, $column),hasColumns($table, array $column),getColumnListing($table)
:
Route::get('get_column_listing', function () {// if (\Illuminate\Support\Facades\Schema::hasTable('accounts'))// if (\Illuminate\Support\Facades\Schema::hasColumn('accounts', 'name')) if (\Illuminate\Support\Facades\Schema::hasColumns('accounts', ['name'])) { // ['id', 'name', 'number', 'status', 'source', 'created_at', 'updated_at'] return \Illuminate\Support\Facades\Schema::getColumnListing('accounts'); }});
字段的操作-column
(1)adding columns
设计database时需要添加columns,上文说过这段逻辑是在Schema::create(),Schema::table()
的闭包里执行的,利用Blueprint对象来依次添加每一个column字段属性和Mysql中数据类型对应,如:
\Illuminate\Support\Facades\Schema::create('accounts', function(\Illuminate\Database\Schema\Blueprint $table) { $table->string('name'); // => $this->addColumn('string', $column, compact('length')); });
常用的Schema字段类型函数表如下:
Schema Column Type | MySQL Column Type |
---|---|
bigIncrements('id') | id UNSIGNED BIGINT |
bigInteger('number') | number BIGINT |
binary('data') | data BLOB |
boolean('is_viewed') | is_viewed BOOLEAN |
char('title', 50) | title CHAR(50) |
date('created_at') | created_at DATE |
dateTime('updated_at') | updated_at DATETIME |
decimal('amount', 2, 2) | amount DECIMAL(2,2) |
double('length', 10, 10) | length DOUBLE(10, 10) |
enum('source', ['fund', 'equity']) | source ENUM('fund', 'equity') |
float('width', 5, 5) | width FLOAT(5, 5) |
json('options') | options JSON |
string('content') | content VARCHAR(255) |
text('description') | description TEXT |
... | ... |
``
(2)removing columns
Schema提供了dropColumn()
方法来删除表中字段:
Route::get('drop_column', function () { \Illuminate\Support\Facades\Schema::table('accounts', function(\Illuminate\Database\Schema\Blueprint $table) { $table->dropColumn(['number', 'status']); });});
(3)renaming columns
Schema提供了renameColumn()
来修改column名称,不过如果该表中有字段类型为enum
就不支持修改:
Route::get('rename_column', function () { \Illuminate\Support\Facades\Schema::table('accounts', function(\Illuminate\Database\Schema\Blueprint $table) { $table->renameColumn('name', 'title'); });});
索引操作-index
(1)simple index
Schema提供了index()
方法来给column加索引,且索引名称约定为table-name_column-name_index-type
:
Route::get('index_column', function () { \Illuminate\Support\Facades\Schema::table('accounts', function(\Illuminate\Database\Schema\Blueprint $table) { $table->index('name'); });});
同时,Schema提供了dropIndex('table-name_column-name_index')
来删除simple index。
(2)unique index
Schema提供了unique()
方法来给column加索引,且索引名称约定为table-name_column-name_index-type
:
Route::get('unique_column', function () { \Illuminate\Support\Facades\Schema::table('accounts', function(\Illuminate\Database\Schema\Blueprint $table) { $table->unique(['title']); });});
同时,Schema提供了dropUnique('table-name_column-name_unique')
来删除unique index。
(3)foreign key
Schema提供了foreign()->reference()->on()
fluent api来设计foreign key,且索引名称约定为table-name_column-name_index-type
:
Route::get('foreign_key_column', function () { \Illuminate\Support\Facades\Schema::create('bills', function(\Illuminate\Database\Schema\Blueprint $table) { $table->increments('id'); $table->unsignedInteger('account_id'); $table->float('amount', 5, 5); $table->foreign('account_id') ->references('id') ->on('accounts') ->onUpdate('CASCADE') ->onDelete('CASCADE'); });});
同时,Schema提供了dropForeign('table-name_column-name_foreign')
来删除foreign index。
总结:本篇主要学习下Laravel使用了Schema和Blueprint两个类来设计database,中篇将以Schema::create()为例仔细研究下源码是如何转换为SQL并执行SQL语句的,下篇聊下Migration System的使用及其原理。到时见。
招聘