はじめに
はじめまして。ねねにゃんパパです。
新型コロナの影響で世の中いろいろ大変ですね。
僕も「お家にいよう!」にならって引き籠もっていて、少し時間が取れたのでなにかしてみようと思い、ほとんど触ったことのない「Laravel」を読んでみることにしました。
どうせ読むのでLaravel7です。まだ情報も少ないっぽいのでいいかなと思いました。
公式のクイックスタート を少し触ってみて、なんとなしに「マイグレーションの作成」をテーマに決めました。
読めるところまで読んでいきたいと思います。
前提
僕は「Laravel」はクイックスタートを数回読んだくらいで全く利用経験がありません。普段から使ってる方からしたら見当違いな事を書くかもしれません。
この記事の対象は PHP 中級者でLaravel未経験者及び初心者を想定しています。PHPの基礎的なシンタックスの説明とかは割愛しますのでご了承ください。
「migrate」コマンドが叩かれた箇所からなぞるように読んだものを記しているので説明の順序が通常の紹介記事とくらべると分かりづらい部分があるかもしれません。ご容赦ください。
※ コードリーディングの旅は一通り終わってます。清書されていませんが、全容を見たい方はBLOGで読めます。が、多分読みにくいので、整えたこちらが更新されるのをお待ちになられるのが良いと思います。
環境
Laravel Framework 7.2.0
PHP 7.3.15
INDEX
- その1「マイグレーションファイルを見てみよう」
- その2「$app['db']って何者?」
- その3「Repositoryクラス」
- その4「Larabelアプリケーションの初期化の流れ」
- その5「リポジトリの読込」
- その6「データベース接続」
- その7「スキーマビルダー」
- その8「migrate コマンドの実行」
- その9「Kernel::handle()」
マイグレーションって何?
マイグレーションとは「移行」「移動」等の意味だそうです。PHPのフレームワークとかだと対象が大抵データベースになるイメージです。ある環境があってそれと同じ構造のものを別に構築したい場合や、安定して同じ環境を作りたい場合に、テーブル作成、データのエクスポート、インポートの作業を手作業でやっていると工数も馬鹿になりませんし、なによりミスが起こる可能性があります。そのため、実行するだけで要件通りのデータを構築できる仕組みを作っておきます。その仕組みのことをマイグレーションと言います。今回は基本的な空のテーブルを作る簡易な処理ですが、コマンドを叩くとデータベース上にテーブルが出来るまでの流れを一通り追っていきます。
マイグレーションファイルの生成
公式のクイックスタートに習って試しにcustomers
テーブルを定義するためのマイグレーションファイルを生成してみます。
$ php artisan make:migration create_customers_table --create=customers
Created Migration: 2020_03_23_050035_create_customers_table
PROJECT_ROOT/database/migrations/
に 2020_03_23_050035_create_customers_table.php
として、以下のようなファイルが生成されました。
どんなファイルが出来ているんでしょう。気になります。とても気になります。見てみましょう!
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCustomersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('customers');
}
}
ん、思ったよりシンプルなファイルですね。これがどんなふうに動くんでしょう!?気になります。
端っこからちょっとずつ読んでみましょう。
フレームワークのクラスファイルの配置場所はどこ?
生成されたマイグレーションファイルには以下の3つの名前空間がuse
で宣言されています。
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
LaravelのパッケージはIlluminate
という名前空間の下で展開されているようです。
これの実体は、PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/
以下にありました。
PROJECT_ROOT/vendor/laravel/framework/
に composer.json
があるので目を通してみると面白いかもしれません。
Migrationクラス
生成されたCreateCustomersTable
クラスはMigration
抽象クラスを継承しています。
Migration
抽象クラスは以下のように定義されていました。
<?php
namespace Illuminate\Database\Migrations;
abstract class Migration
{
/**
* The name of the database connection to use.
*
* @var string|null
*/
protected $connection;
/**
* Enables, if supported, wrapping the migration within a transaction.
*
* @var bool
*/
public $withinTransaction = true;
/**
* Get the migration connection name.
*
* @return string|null
*/
public function getConnection()
{
return $this->connection;
}
}
データベース接続名$connection
とトランザクションを使用するかどうかの$withinTransaction
(デフォルト:true
)の2つの変数と、単純に$connection
が返されるgetConnection()
メソッドがが宣言されています。
もう少し色々書いてあるかなと思いましたがあてが外れました。引き返しましょう。
Schemaクラス
CreateCustomersTable
クラスはMigration
抽象クラスにup()
とdown()
の2つのメソッドを追加しています。どちらもSchema
クラスのstatic
メソッドを呼び出しています。Schema
クラスはIlluminate\Support\Facades\Schema
で以下のように定義されています。
<?php
namespace Illuminate\Support\Facades;
/**
* @method static \Illuminate\Database\Schema\Builder create(string $table, \Closure $callback)
* @method static \Illuminate\Database\Schema\Builder drop(string $table)
* @method static \Illuminate\Database\Schema\Builder dropIfExists(string $table)
* @method static \Illuminate\Database\Schema\Builder table(string $table, \Closure $callback)
* @method static \Illuminate\Database\Schema\Builder rename(string $from, string $to)
* @method static void defaultStringLength(int $length)
* @method static bool hasTable(string $table)
* @method static bool hasColumn(string $table, string $column)
* @method static bool hasColumns(string $table, array $columns)
* @method static \Illuminate\Database\Schema\Builder disableForeignKeyConstraints()
* @method static \Illuminate\Database\Schema\Builder enableForeignKeyConstraints()
* @method static void registerCustomDoctrineType(string $class, string $name, string $type)
*
* @see \Illuminate\Database\Schema\Builder
*/
class Schema extends Facade
{
/**
* Get a schema builder instance for a connection.
*
* @param string|null $name
* @return \Illuminate\Database\Schema\Builder
*/
public static function connection($name)
{
return static::$app['db']->connection($name)->getSchemaBuilder();
}
/**
* Get a schema builder instance for the default connection.
*
* @return \Illuminate\Database\Schema\Builder
*/
protected static function getFacadeAccessor()
{
return static::$app['db']->connection()->getSchemaBuilder();
}
こちらも簡素なクラスですね。なかなか扉が開きません。
Schema
クラスはFacade
抽象クラスを継承してconnection()
と getFacadeAccessor()
の2つのメソッドを定義しています。呼び出しているcreate()
メソッドとdropIfExists()
メソッドは見当たりません。
Facade抽象クラス
Facade
抽象クラスを見てみましょう。
Illuminate\Support\Facades\Facade
で定義されいてます。
ようやく少しだけ色々書かれています!
Facade
ってくらいですから、これが「正面玄関」なのかもしれませんね!
少しだけコードが長いので中略します。
<?php
namespace Illuminate\Support\Facades;
use Closure;
use Mockery;
use Mockery\MockInterface;
use RuntimeException;
abstract class Facade
{
/**
* The application instance being facaded.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected static $app;
/**
* The resolved object instances.
*
* @var array
*/
protected static $resolvedInstance;
/* ========== 中略 ========== */
/**
* Get the root object behind the facade.
*
* @return mixed
*/
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
/**
* Get the registered name of the component.
*
* @return string
*
* @throws \RuntimeException
*/
protected static function getFacadeAccessor()
{
throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}
/**
* Resolve the facade root instance from the container.
*
* @param object|string $name
* @return mixed
*/
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
if (static::$app) {
return static::$resolvedInstance[$name] = static::$app[$name];
}
}
/* ========== 中略 ========== */
/**
* Handle dynamic, static calls to the object.
*
* @param string $method
* @param array $args
* @return mixed
*
* @throws \RuntimeException
*/
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の現場でも、ポリシーや担当によってはマジックメソッドを書く機会が無い方もいるかも知れません。これは、アクセス不能メソッドを静的コンテキストで実行したときに起動するものです。おそらくこれが叩かれるのでしょう。
今回のコードリーディングの旅ではマジックメソッドが3〜4種類読み解く鍵として出てきます。無計画に使うのはおすすめできませんが、とても便利な機能です。馴染みがない方で興味のある方はphp.netを読んでみると楽しいと思います。
__callStatic()
マジックメソッド__callStatic()
を見てみましょう。
第一引数はコールしたメソッド名、第二引数は渡されたパラメータが配列の形で格納されています。__callStatic
のはじめに自身のgetFacadeRoot()
を呼び出して戻り値を$instance
に格納しています。getFacadeRoot()
は自身の resolveFacadeInstance()
に自身のgetFacadeAccessor()
を引数として渡しています。少しややこしくなってきましたね。一つずつ見ましょう。
/**
* Get a schema builder instance for the default connection.
*
* @return \Illuminate\Database\Schema\Builder
*/
protected static function getFacadeAccessor()
{
return static::$app['db']->connection()->getSchemaBuilder();
}
getFacadeAccessor()
は継承されたSchema
クラスでオーバーライドされています。
/**
* Get the registered name of the component.
*
* @return string
*
* @throws \RuntimeException
*/
protected static function getFacadeAccessor()
{
throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}
上書きされなかった場合、例外RuntimeException
が「Facade does not implement getFacadeAccessor method.」というメッセージで投げられるように抽象クラスで定義されています。
getFacadeAccessor()
メソッドがreturn
しているスタティックメソッドで「ん?」と気になるヤツがいます。
$app['db']
こいつはなんでしょう。Laravel使いには当たり前の存在なのかもしれない曲者の匂いがします。正体を知りたくなりますね。調べてみましょう。
次回
次回は**$app['db']**について追ってみたいと思います。
続く☆