結論から申し上げます
本記事はなにもまとまっておらず、ただただmigrationに係るファイルを一つ一つ追っていったログ です。
本当は↓↓こんな感じの図を交えつつ
「Laravelのmigration処理を解き明かしたった!」みたいなタイトルで一世を風靡しようと思ったのですが、挫折しました。
せっかく途中まで調べたので恥を忍んで公開しておこうと思います。
悔しいからまた挑戦します!!
ということで、以下ログです。
migrationってなに?
まずはmigrationの本来の意味を調べます。
mi·gra·tion
- seasonal movement of animals from one region to another.
- movement from one part of something to another.
ざっくり**"場所から場所への移動"**と捉えておけば問題ないでしょう。
次に、公式ドキュメントを確認。
https://readouble.com/laravel/5.5/ja/migrations.html
...横文字だらけでわかりづらいのですが、要するにデータベースの定義をLaravelからできるものです。
- Migrationファイルを作って
- そのファイルの中身に指令を書き込んで
- その指令通りにLaravelにデータベースを操作してもらう
この3パターンで完了です!
ということで、それぞれの処理について詳細をみていきましょう。
登場人物
migrationファイルを作る
まずは、マイグレーション処理をするために、migrationファイルを作成しましょう。
マイグレーションファイルを作るためには、php artisan
コマンドを使います。Laravel用のコマンドですね。
(artisanは職人という意味らしいです)
ここでは、test
というテーブルを作成したいとします。
root@8c8e2d4a121f:/var/www# php artisan make:migration create_tests_table
Created Migration: 2019_03_01_085359_create_tests_table
すると、database/migrations 配下に新しいファイルができているのがわかります。
ファイルの中身をみてみましょう。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTestsTable extends Migration
{
public function up()
{
Schema::create('tests', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('tests');
}
}
Migration
クラスを継承する形でCreateTestsTable
クラスが作成されていることがわかると思います。
中身をみてみると、up()
という関数とdown()
という関数があることがわかります。
up()
メソッドは新規テーブル、カラム、インデックスをデータベースに追加することができます。down()
は逆に、up()
メソッドの処理を元に戻す機能を記述します。
そして、両メソッド - up()
とdown()
- の中身はSchema
ファサードが使えます。Schemaとは何か。それを次にみていきます。
回り道: Migration.phpを覗いてみる
では、少し回り道をしてMigration
クラスを覗いてみましょう。
抽象クラスのようですね。
コメントを日本語化して掲載してみます。
※抽象クラスの解説はこちらの記事が詳しいです。
https://laraweb.net/surrounding/1972/
<?php
namespace Illuminate\Database\Migrations;
abstract class Migration
{
//データベース接続するために使う名前。
protected $connection;
//サポートされている場合、トランザクション内でマイグレーションをラップすることを可能にします。
public $withinTransaction = true;
//マイグレーション接続の名前を取得します。
public function getConnection()
{
return $this->connection;
}
}
Migrationクラス自体は抽象メソッド(abstract)ですが、内部のメソッド・プロパティにはabstract
が定義されていないので、これら3つはそのままCreateTestsTable
クラスに継承されるわけですね。
up()の中身を記述する - Schemaファサード
さて、ではup()
メソッドの中身を改めて確認していきましょう。
public function up()
{
Schema::create('tests', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
}
ファサードってなんぞ?となると思いますが、ここでファサードの元々の意味をまた確認してみましょう。
fa·cade
the face of a building, especially the principal front that looks onto a street or open space.
建物の正面、という意味です。詳細はこちらの記事がわかりやすかったです。
Laravelのファサードとは、建築用語での「建築物 の 正面部分」という意味が、プログラミングでの「ライブラリ(建築物)の呼び出し部分(正面部分)」という意味になるわけですね。
ということで、Schemaファサード(ここではcreate
)についてみていきましょう
Schema::create('tests', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
::
はスコープ定義演算子です。クラスをインスタンス化しなくても、クラス内のプロパティを扱うことができます。
http://php.net/manual/ja/language.oop5.paamayim-nekudotayim.php
つまり、Schemaのcreateメソッドを実行して、という意味でそのまんま捉えてOKです。
Schema::create([テーブル名], [Blueprintオブジェクトを受け取るクロージャ関数] )
という形式で書かれています。
回り道:createを探す旅
さて、ここでまた回り道をしてcreateがどこに記述されているかを確認してみましょう。
Schemaクラスを確認してみます。
vendor/laravel/framework/src/Illuminate/Support/Facades/Schema.php
<?php
namespace Illuminate\Support\Facades;
/**
* @method static \Illuminate\Database\Schema\Builder create(string $table, \Closure $callback)
* ...
* @see \Illuminate\Database\Schema\Builder
*/
class Schema extends Facade
{
/**
* 接続のために Schema Builder インスタンスを取得する。
* @return \Illuminate\Database\Schema\Builder
*/
public static function connection($name)
{
return static::$app['db']->connection($name)->getSchemaBuilder();
}
/**
* Schema Builder インスタンスを デフォルトの接続として取得する。
* @return \Illuminate\Database\Schema\Builder
*/
protected static function getFacadeAccessor()
{
return static::$app['db']->connection()->getSchemaBuilder();
}
}
...あれ?createがない?
ということで、継承元のFacade
クラスを確認してみるが、ない。
あちこち探してみると、こんな場所にいました。
vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php
<?php
namespace Illuminate\Database\Schema;
use Closure;
use LogicException;
use Illuminate\Database\Connection;
class Builder
{
...
/**
* schema上にテーブルを作成する。
*/
public function create($table, Closure $callback)
{
$this->build(tap($this->createBlueprint($table), function ($blueprint) use ($callback) {
$blueprint->create();
$callback($blueprint);
}));
}
...
}
create($table, Closure $callback)
となっており、先ほどの定義と一致しますね。
でもBuilder
クラスなんてSchema
クラスにはどこにも出てきません。
では、どこで接続するのか?
...それは、後半のお楽しみ。
閑話休題。
Facade
クラスにもconnection()
メソッドはありませんでした。
するとどういうことが起こるのか?
Facade
クラス内の__callStatic
に注目してください。
vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
<?php
namespace Illuminate\Support\Facades;
...
abstract class Facade
{
...
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
}
__callStatic
はマジックメソッドと呼ばれるPHPの文法の一種で、アンダーライン2つ(__)から始まる関数名は、ある条件下の時に発動するメソッドです。
http://php.net/manual/ja/language.oop5.magic.php
__callStatic
は存在しない静的メソッドが呼ばれたら実行されます。
ここで振り返ってみてください。
Schema::create
とcreate
メソッドを呼び出しているのに、Schema
クラスにも、Facade
クラスにもcreate
が存在しません。
...そう、つまり、__callStatic
が実行されるわけです!
ということで、__callStatic
の中身を見ましょう。
すると、
$instance = static::getFacadeRoot();
という処理を行なっていますね。
ちなみにここでstatic::
が使用されていますが、これは遅延静的束縛と呼ばれるものです。
ここでは説明を割愛しますが、こちらのサイトがわかりやすいです。
https://ackintosh.github.io/blog/2013/08/25/late-static-binding/
では、getFacadeRoot
に関連する場所を探してみましょう。
/**
* ファサードの裏にあるルートオブジェクトを取得する
*/
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
/**
* ファサードのルートのインスタンスをコンテナから解決する
*/
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}
まずはstatic::resolveFacadeInstance(static::getFacadeAccessor())
という処理をさせたいわけですね。
ここで着目したいのがgetFacadeAccessor()
はどこにあるのか、という話。
これは、はるか昔に書いた、Scheme
クラスの中に出てきます。
<?php
namespace Illuminate\Support\Facades;
class Schema extends Facade
{
protected static function getFacadeAccessor()
{
return static::$app['db']->connection()->getSchemaBuilder();
}
}
$app
はFacade側で定義されています(ややこしい...)
再び戻って確認します。
すると、こんな関数が。
public static function setFacadeApplication($app)
{
static::$app = $app;
}
どうやら、bootstrap中にこのsetFacadeApplication
メソッドを使って値を設定するようです。
The first static property contains a reference to the Laravel IoC container. It’s set during the framework bootstrap process using the setFacadeApplication() method found in the full example above.
で、このプロパティはabstract facadeにくっついているから、Facade
クラスを継承する全てのクラスに適用されるらしい。なるほど。
ということで、コメント部にあるように、
/**
* The application instance being facaded.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected static $app;
という定義で良いようだ。
Illuminate\Contracts\Foundation には Application.php
がある。
そして、これがサービスコンテナの操作をすることができるインスタンスなのだ。
やっとゴールが見えてきた気がするぞ!!
さて、ここで更に寄り道。
return static::$app['db']->connection()->getSchemaBuilder();
これ、なんで配列になっているか、気になりませんか?
クッソ調べまくって見つけました。
https://stackoverflow.com/questions/50530710/how-laravel-uses-object-as-an-array-in-facade-class?rq=1
PHPにあるArrayAccess
インターフェイスを使っているからでした。
http://php.net/manual/ja/class.arrayaccess.php
スッキリした...!
なので、
$app['db']
インスタンスを参照ているわけです。
つまり、下記とニアリーイコールです。
DB
ファサードは Illuminate\Database\DatabaseManager
を参照します。
ここのファサードクラス一覧に書いてあります。
https://readouble.com/laravel/5.5/ja/facades.html
ということで、当該のDatabaseManager.php
をみてみましょう
<?php
namespace Illuminate\Database;
use PDO;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Illuminate\Database\Connectors\ConnectionFactory;
/**
* @mixin \Illuminate\Database\Connection
*/
class DatabaseManager implements ConnectionResolverInterface
{
...
/**
* The application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $app;
...
/**
* 新規の DataBaaseManagerインスタンスを作成する
* @param \Illuminate\Foundation\Application $app
* @param \Illuminate\Database\Connectors\ConnectionFactory $factory
* @return void
*/
public function __construct($app, ConnectionFactory $factory)
{
$this->app = $app;
$this->factory = $factory;
}
...
/**
* Database Connection インスタンス を取得する。
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function connection($name = null)
{
list($database, $type) = $this->parseConnectionName($name);
$name = $name ?: $database;
// If we haven't created this connection, we'll create it based on the config
// provided in the application. Once we've created the connections we will
// set the "fetch mode" for PDO which determines the query return types.
if (! isset($this->connections[$name])) {
$this->connections[$name] = $this->configure(
$this->makeConnection($database), $type
);
}
return $this->connections[$name];
}
...
}
先ほど言ったように、DB
ファサードはインスタンス化されているため、__construct
メソッドは実行ずみです。
では、connection()
メソッドの内部を深掘りしていきましょう。
すると、PHP弱者には初めましての文法がたくさん...
確認していきます。
list($database, $type) = $this->parseConnectionName($name);
これは、List関数です。右辺の配列を順に左側の変数に代入してくれます。
http://php.net/manual/ja/function.list.php
https://www.sejuku.net/blog/24406
こちらの例がわかりやすいですね。引用します。
$fruits_array = ['りんご', 'オレンジ', 'メロン'];
//listの複数変数を配列から代入
list($apple, $orange, $melon) = $fruits_array;
今回の例では、parseConnectionName($name)
で取得した配列の結果を順に代入している、ということのようですね。
parseってことはもともと繋がって入力されたものを分割するんでしょうね。
$name
は今回入力がないため、connectionメソッドのargsデフォルト値である$name = null
が適用されます。
protected function parseConnectionName($name)
{
$name = $name ?: $this->getDefaultConnection();
return Str::endsWith($name, ['::read', '::write'])
? explode('::', $name, 2) : [$name, null];
}
出たな、三項演算子!
?:
と書かれると「ん?」となりますが、怖がることはありません。
$name = $name ?: $this->getDefaultConnection();
は
$name = $name ? $name : $this->getDefaultConnection();
と同義です。
もっというと、
if ( $name ) {
$name = $name;
} else {
$name = $this->getDefaultConnection();
}
です。
ということで、今回は前述のように$name
はnull
なので、getDefaultConnection()
メソッドが発動します。
public function getDefaultConnection()
{
return $this->app['config']['database.default'];
}
appは$appが代入されている箇所があります。つまり、先ほどと同じ、\Illuminate\Foundation\Application
のインスタンスです。
では、$this->app['config']['database.default']
とはなんでしょうか。
またこちらをreferします。
https://readouble.com/laravel/5.5/ja/facades.html
Config
は Illuminate\Config\Repository
らしい。
というところまではわかったのですが、どこの値を参照しているかまでは追えず....
StackOverflowにも投げてみようかと思いまうす。
わかるかた、コメントいただけると嬉しいです。
一度立ち戻って、parseConnectionName($name)
はコメント通り
Parse the connection into an array of the name and read / write type.
つまり、名前と読み書き権限の配列を返してくれるメソッドだと理解して次に進みましょう。
随分上になってしまったのでconnection()
メソッドを再掲します。
/**
* Database Connection インスタンス を取得する。
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function connection($name = null)
{
list($database, $type) = $this->parseConnectionName($name);
$name = $name ?: $database;
// If we haven't created this connection, we'll create it based on the config
// provided in the application. Once we've created the connections we will
// set the "fetch mode" for PDO which determines the query return types.
if (! isset($this->connections[$name])) {
$this->connections[$name] = $this->configure(
$this->makeConnection($database), $type
);
}
return $this->connections[$name];
}
parseConnectionName($name)
のおかげで、名前
と読み書き権限
を$database
と$type
に格納することができました。
そして、もしconnections[$name]
が存在しなかったら、configure()
メソッドを実行して、設定をしてくれます。
/**
* DatabaseConnectionインスタンスを準備する
*
* @param \Illuminate\Database\Connection $connection
* @param string $type
* @return \Illuminate\Database\Connection
*/
protected function configure(Connection $connection, $type)
{
$connection = $this->setPdoForType($connection, $type);
// First we'll set the fetch mode and a few other dependencies of the database
// connection. This method basically just configures and prepares it to get
// used by the application. Once we're finished we'll return it back out.
if ($this->app->bound('events')) {
$connection->setEventDispatcher($this->app['events']);
}
// Here we'll set a reconnector callback. This reconnector can be any callable
// so we will set a Closure to reconnect from this manager with the name of
// the connection, which will allow us to reconnect from the connections.
$connection->setReconnector(function ($connection) {
$this->reconnect($connection->getName());
});
return $connection;
}
そして、注目して欲しいのが戻り値の型。
@return \Illuminate\Database\Connection
となっていますね。
Connection
クラスを確認してみましょう。
vendor/laravel/framework/src/Illuminate/Database/Schema/Connection.php
<?php
namespace Illuminate\Database;
...
use Illuminate\Database\Schema\Builder as SchemaBuilder;
...
class Connection implements ConnectionInterface
{
....
/**
* 接続のために SchemaBuilderインスタンスを作成する
*
* @return \Illuminate\Database\Schema\Builder
*/
public function getSchemaBuilder()
{
if (is_null($this->schemaGrammar)) {
$this->useDefaultSchemaGrammar();
}
return new SchemaBuilder($this);
}
...
}
なんか見覚えのある奴がいませんか?
...そう、'Builder'クラスだ!
覚えていない人のためにもう一度。create()
メソッドがいる場所でしたね。
vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php
<?php
namespace Illuminate\Database\Schema;
use Closure;
use LogicException;
use Illuminate\Database\Connection;
class Builder
{
...
/**
* schema上にテーブルを作成する。
*/
public function create($table, Closure $callback)
{
$this->build(tap($this->createBlueprint($table), function ($blueprint) use ($callback) {
$blueprint->create();
$callback($blueprint);
}));
}
...
}
このBuilder
クラスがConnection
クラスでuse Illuminate\Database\Schema\Builder as SchemaBuilder;
として呼び出されています。つまり、SchemaBuilder
として先ほどのBuilder
クラスが使われているわけですね。
そして、getSchemaBuilder()
というメソッドでSchemaBuilder, すなわち Builder
クラスのインスタンスを作成しているんですね!
(ちなみに名前をわざわざSchemaBuilder
としているのは、 ...\ Query \Builder もuseしているためです。こちらをQueryBuilder
として名前を分けています。)
SchemaBuilder($this)
$this
とは、Connection
クラス自身を指します。
では、改めてSchema/Builder.phpの__constructor
(クラスがインスタンス化された時に呼び出されるメソッド)を確認しましょう。
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->grammar = $connection->getSchemaGrammar();
}
ここで注目するのはgetSchemaGrammer()
です。Builderをインスタンス化するときに、Schemaの文法を取得しろ、と命令しているわけです。
怪しくなってきましたね。
ということで、再びConnection
クラスに戻り、getSchemaGrammer()
を確認します。
public function getSchemaGrammar()
{
return $this->schemaGrammar;
}
このschemaGrammerは setSchemaGrammar()
によって定義されます。
public function setSchemaGrammar(Schema\Grammars\Grammar $grammar)
{
$this->schemaGrammar = $grammar;
}
...頭がごっちゃごちゃになったので一旦ここまでで勘弁してください....
参考
How Laravel Facades Work and How to Use Them Elsewhere
ファサードの概念を掴むのにめっちゃわかりやすい
Laravel Facades
これもファサードの解説
PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応
サービスコンテナの概要はこの本。