Laravel學習筆記之Schema Builder 和 Migration System(上)

說明:本文主要學習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。php

開發環境: Laravel5.3 + PHP7mysql

表的操做-tables

在設計database時須要建立、刪除和更新表,Schema Builder類提供了一些methods來面向對象的執行這些操做,而不須要寫一行SQL。在寫Laravel程序時,也常常使用相似命令php artisan make:migration create_accounts_table --create=accounts來作一個遷移類建立數據表,會在database/migrations文件夾下獲得相似以下的代碼類:laravel

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對象。爲何有這麼奇怪的寫法呢?sql

看下Schema Facade中getFacadeAccessor的源碼:json

/**
     * 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(),看下源碼:api

// \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表了:app

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()更優雅。看下兩個方法的源碼:ide

/**
     * 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對象來添加命令dropdropIfExists。在路由中刪除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()方法來更新表字段值,如更新accountsnumber字段,,不過若是該表中有字段類型爲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的使用及其原理。到時見。

RightCapital招聘Laravel DevOps

相關文章
相關標籤/搜索