0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!その1 「マイグレーションファイルを見てみよう」

Last updated at Posted at 2020-04-13

はじめに

はじめまして。ねねにゃんパパです。
新型コロナの影響で世の中いろいろ大変ですね。
僕も「お家にいよう!」にならって引き籠もっていて、少し時間が取れたのでなにかしてみようと思い、ほとんど触ったことのない「Laravel」を読んでみることにしました。
どうせ読むのでLaravel7です。まだ情報も少ないっぽいのでいいかなと思いました。
公式のクイックスタート を少し触ってみて、なんとなしに「マイグレーションの作成」をテーマに決めました。
読めるところまで読んでいきたいと思います。

前提

僕は「Laravel」はクイックスタートを数回読んだくらいで全く利用経験がありません。普段から使ってる方からしたら見当違いな事を書くかもしれません。
この記事の対象は PHP 中級者でLaravel未経験者及び初心者を想定しています。PHPの基礎的なシンタックスの説明とかは割愛しますのでご了承ください。
migrate」コマンドが叩かれた箇所からなぞるように読んだものを記しているので説明の順序が通常の紹介記事とくらべると分かりづらい部分があるかもしれません。ご容赦ください。

※ コードリーディングの旅は一通り終わってます。清書されていませんが、全容を見たい方はBLOGで読めます。が、多分読みにくいので、整えたこちらが更新されるのをお待ちになられるのが良いと思います。

環境

Laravel Framework 7.2.0
PHP 7.3.15

INDEX

マイグレーションって何?

マイグレーションとは「移行」「移動」等の意味だそうです。PHPのフレームワークとかだと対象が大抵データベースになるイメージです。ある環境があってそれと同じ構造のものを別に構築したい場合や、安定して同じ環境を作りたい場合に、テーブル作成、データのエクスポート、インポートの作業を手作業でやっていると工数も馬鹿になりませんし、なによりミスが起こる可能性があります。そのため、実行するだけで要件通りのデータを構築できる仕組みを作っておきます。その仕組みのことをマイグレーションと言います。今回は基本的な空のテーブルを作る簡易な処理ですが、コマンドを叩くとデータベース上にテーブルが出来るまでの流れを一通り追っていきます。

マイグレーションのイメージ

マイグレーションファイルの生成

公式のクイックスタートに習って試しにcustomersテーブルを定義するためのマイグレーションファイルを生成してみます。

COMMAND-LINE
$ 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で宣言されています。

create_customers_table.php
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 があるので目を通してみると面白いかもしれません。

CreateCustomersTable | Migration

Migrationクラス

生成されたCreateCustomersTableクラスはMigration抽象クラスを継承しています。
Migration抽象クラスは以下のように定義されていました。

Illuminate\Database\Migrations\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 で以下のように定義されています。

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ってくらいですから、これが「正面玄関」なのかもしれませんね!
少しだけコードが長いので中略します。

Illuminate\Support\Facades\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()を引数として渡しています。少しややこしくなってきましたね。一つずつ見ましょう。

Illuminate\Support\Facades\Schema::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クラスでオーバーライドされています。

Illuminate\Support\Facades\Facade::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.');
	}

上書きされなかった場合、例外RuntimeExceptionが「Facade does not implement getFacadeAccessor method.」というメッセージで投げられるように抽象クラスで定義されています。

getFacadeAccessor()メソッドがreturnしているスタティックメソッドで「ん?」と気になるヤツがいます。

$app['db']

こいつはなんでしょう。Laravel使いには当たり前の存在なのかもしれない曲者の匂いがします。正体を知りたくなりますね。調べてみましょう。

次回

次回は**$app['db']**について追ってみたいと思います。

続く☆

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?