Laravelソースコードの歩き方

タグ: Laravel5.1LTS   Laravel  

Laravelリファレンス発売記念、販促アドベントカレンダーの2015年12月7日分です。

Laravelを始め、PHPフレームワークではソースコードを読めると理解が深まります。ええ。面倒ですよ。確かにドキュメントを読むより、手間が多少かかります。全部ドキュメントに書かれていれば良いのにという気持ちもわかります。ただ、多すぎる情報を読むのは大変ですし、言葉よりコードで説明したほうがたいてい簡潔なんです。そうでなければ私達は自然言語でプログラムしていることでしょう。プログラム言語の代わりに。

ソースを読み始めるにはある程度の知識が必要です。特にどのファイルに目的のコードがあるのかを見つける必要があります。

そこで、どうやって目標とするソースコードファイルを見つけるのか、フレームワーク初心者の方向けに知識と方法を紹介します。ソースが追えるようになれば、もう中級者です。(でも、ドキュメントも読んでおきましょう。)

ローディング規約

LaravelはPSR-4ローディング規約に従っています。クラスの自動ローディング規約です。名前空間を含めたクラス名と、ファイルパスを対応付ける仕組みです。Laravelが使用していたり、開発者がComposer経由でインストールしたりするPHPコンポーネントの中には、PSR-4が設定される前に決まっていた、PSR-0ローディング規約に従っているものもあります。

ざっくりと説明するとPSR-0の指定方法ではディレクトリー構造が深くなってしまうため、改良したのがPSR-4規約です。Composerはどちらもサポートしていますので、問題なく利用できます。

Laravelフレームワークが指定しているローディング規約は、インストールディレクトリーに存在しているcomposer.jsonの中で指定しています。

…前略…
"autoload": {
        "classmap": [
            "database"
        ],
        "psr-4": {
            "App\\": "app/"
        }
    },
…後略…

"psr-4"アイテムでApp\\キーに対してapp/を指定しています。App名前空間に対してappディレクトリーを対応付けています。Laravelのため…というよりも、私達が開発に使用するApp名前空間(デフォルトのまま使用時)を定義づけています。

あるクラスファイルの先頭が次のようになっていれば…

namespace App\Http\Controllers;

class PostController extends Controller
{
…

このクラスの名前はPostControllerですが、App\Http\Controllers名前空間下に位置づけると宣言しているわけです。完全なクラス名はApp\Http\Controllers\PostControllerです。

プロジェクトトップにあるcomposer.jsonで宣言している通り、App名前空間はappディレクトリーへ位置づけられていますので、名前空間が分かれば、ファイル位置も同時に分かるわけです。

appディレクトリー
└── Httpディレクトリー
   └── Controllersディレクトリー
      └── PostContoller.phpファイル

ローディングの基本的なおさらいをしました。Composerはinstallやupdateコマンド実行の結果、必要と判断したパッケージをvenderディレクトリー下へ設置します。vendor直下にはベンダー名(提供する人やグループの名前やライブラリー、フレームワークなどのコンポーネントの通称)、その下にパッケージ名のディレクトリーが設置されます。このパッケージ名のディレクトリー下にもcomposer.jsonが含まれており、それぞれのパッケージが登録しているローディング規約は、その中に見つけられます。

Laravelの場合、ベンダー名はlaravel、パッケージ名はframeworkになっています。その中のcomposer.jsonを覗いてください。

…前略…
"autoload": {
    "classmap": [
        "src/Illuminate/Queue/IlluminateQueueClosure.php"
    ],
    "files": [
        "src/Illuminate/Foundation/helpers.php",
        "src/Illuminate/Support/helpers.php"
    ],
    "psr-4": {
        "Illuminate\\": "src/Illuminate/"
    }
},
…後略…

名前空間Illuminateをsrc/Illuminateディレクトリーへ結びつけています。ここから「LaravelのパッケージはIlluminate名前空間下にある」こと、そして「Illuminate名前空間下のPHPファイルはsrc/Illuminateディレクトリー下に存在している」ことの2つが把握できます。このファイルの置いてある相対ディレクトリーは"vendor/laravel/framework/"ですから、実際のIlluminate名前空間下のファイルは、"プロジェクトディレクトリー/vendor/laravel/framework/src/Illuminate"ディレクトリー下ということになります。

ちなみに、パッケージ名と名前空間には別の名前を付けられるのですが、それは好ましくないと考えられており、Laravelが非難を受けた点の一つです。LaravelはIlluminateという、Laravelとは別のベンダー名を使用しています。名前からLaravelだとは分かりません。もし仮に、Illuminateという名前空間を使用するパッケージが別に作られると、名前空間やクラス名が衝突する可能性が起きるからです。PSR規約では「ベンダー名として知られている名前」を名前空間のトップで使用するようになっています。曖昧な表現です。もうちょっと厳格に決めるべきだったのかもしれません。"Illuminate"も今となっては「知られている」名前になっています。逆に、初めて公開するパッケージは全部「知られていない」ベンダー名を持つでしょう。自分のベンダー名は忸怩のものを付けるようにしましょう。既に有名なパッケージとダブっていないか、決める前にチェックしてください。

これで、Laravelのコンポーネントについては、名前空間とクラス名からファイルの設置場所が分かるようになりました。Laravel以外のPHPコンポーネントのローディング規則も同様に調べられます。

実際にはどのパッケージであろうとも、vendor/ベンダー名のディレクトリ、その下のパッケージ名のディレクトリと移動後、composer.jsonを確認しなくても、srcとかlibとかそれらしい名前があるので、その下を見ればソースファイルは見つかります。理論的な方法と、実践的な方法があるわけですよ。なにごとも。

(注目:それぞれのIlluminationコンポーネントもcomposer.jsonを持っています。そこでもPSR-4規約に基づいた自動ローディングの定義が行われています。それを見て、こんがらがらないでください。各IlluminationコンポーネントのPSR-4定義は、Laravelフレームワークとして定義している上記の宣言と互換性があります。フレームワークとしてだけでなく、コンポーネント単位でも定義しているため、各コンポーネントをLaravelから独立させて使用することが可能になっています。)

Laravelコンポーネント

プロジェクトファイルのvendor/laravel/framework/src/Illuminateディレクトリーを覗いてください。ディレクトリーがずらりと入っています。

各ディレクトリーがLaravelのコンポーネントです。名前を確認してください。どんなコンポーネントがあるのか、頭の片隅に入れておきましょう。

コードを探る

プログラムを組んでいて、「はて、ここはどうだったかなあ?」とドキュメントを確認する。でも、見つからない。そこでLaravel本体の該当するコードが確認したくなるわけです。

当然のことながら、私達が直接変更するapp下のソースには、存在していません。(たまに、時間が経って、自分でLaravelっぽい名前付けといて、Laravelのどこかにあるだろうと探しても見つからない…、おかしいと思ったらapp下にあったなんてことは、あるかもしれません。名前空間を使っていなければ、ありえますね。)

たとえば、ユーザー管理ページのコントローラーでミドルウェアを設定するため、コンストラクタで$this->middlewearの動作を調べたくなり、ドキュメントを見るのは面倒だからソースでも見るかと、追いかけ始めます。

たいてい、コントローラーの冒頭はこんなコードです。

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UserController extends Controller {

    public function __construct() {
        $this->middleware('auth.first');
    }

    …(省略)…

extendsでControllerクラスを拡張しています。Controllerクラスはクラス定義前のuse宣言をされていません。宣言されていないということは、名前空間がこのクラスと同じApp\Http\Controllersであるとわかります。名前空間がわかりますので、クラスファイルはapp/Http/Controllers/Controller.phpだとわかります。

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

abstract class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

この抽象クラスもBaseControllerクラスをextendsしています。use文でBaseControllerクラスはIlluminate\Routing\Controllerクラスのことだと宣言していますので、コードを追いかけるには、更にvender/laravel/framework/src/Routing/Controller.phpファイルを開く必要があります。

このファイルではクラス内のuse文もあります。トレイトを3つ使っています。トレイトもクラスと同様に、名前空間とクラス名を元にローディングされます。つまり、BaseControllerを含めて4つのファイルを調べる可能性があるわけです。(もちろん、トレイトやクラスには役割に応じた名前が付けられていますので、どこを見ればよいか、たいていわかります。調べる対象のmiddlewearメソッドは、名前からすると3つのトレイトとは関連なさそうなので、この場合、まずBaseControllerを見ればいいんじゃないかとアタリを付けられるわけです。)

IDEを使いましょう

ソースを追いかけるにはIDEが便利です。初心者は多機能でかえって面倒だと思うかもしれませんが、既存のソースを追い駆けたい場合はずいぶんと楽ができます。

今回の場合、UserControllerのコンストラクタで使用しているmiddlewearのメソッド定義を確認したいだけでした。PHPStormならcommand/ctrl+bで一気にIlluminate\Routing\Controllerのファイルを開き、middlewarの定義箇所へカーソルが移動してくれます。PHPStormとNetBeansで同じ動作をctrlキーを押したままmiddlewear()の上で左クリックすることでも行えます。

初心者こそ、IDEを使いましょう。

インターフェイス

テストや交換性をあげるために、具象クラスを直接指定するのではなく、インターフェイスが活用されます。Laravel的には契約(コントラクト)と呼んでいます。PHP言語の機能ではインターフェイスですが、設計的には呼び出し元と呼び出し側で使用するメソッドと引数をあらかじめ決めておく「契約」だからです。

Laravelのコンテナによりインスタンス化されるクラスでは、コンストラクタにタイプヒントで使用したい(依存している)クラスを指定すれば、インスタンスが引数に渡されますが、Laravelのコアコンポーネントのクラスでは、インターフェイスも指定できます。

たとえばあるクラスメソッドで、動的に新しいルートを追加するためにルーターが必要ならば、次のように書きます。Illuminate\Contracts下は、その名の通りコントラクトのための名前空間ですから、クラスの代わりにインターフェイスが含まれています。

namespace app\Example;

use Illuminate\Contracts\Routing\Registrar as Router;

class SampleClass {

    /** @var Illuminate\Contracts\Routing\Registrar **/
    private $router;

    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    public function addGetRoute($uri, $method)
    {
        $this->router->get($uri, $method);
    }
}

IDEも入れたし、宣言へのジャンプを使ってみるかと、ルーターのgetの宣言元へ移動しても、インターフェイスのためメソッドの宣言があるだけで、実装を確認することはできません。自分で作成したインターフェイスであれば、実装の位置はすぐに分かるでしょうが、Laravelで使われている契約の実装はどこにあるんでしょうか。

Laravelは「アプリケーション」としてインターフェイス(契約)と実装クラスを対応付けています。つまりこれは、Laravelアプリケーション自身を表すIlluminate\Foundation\Applicationクラスです。registerCoreContainerAliasesメソッドで対応付けを行っていますので、ここを参照すれば一発でわかります。(実際に見て、Illuminate\Contracts\Routing\Registrar契約に結合されている実体のクラスを確認してください。クラス名を見ればそれらしい名前が付いています。ほんわりした説明をしていますが、名前空間やクラス名から「これは〜だ」なと想像するのも、コードリーディングのスキルです。Laravelではでたらめな名前は付けられていないため、たいてい理解できますよ。)

他に各Illumitateコンポーネントでも、インターフェイスやクラス名をコンテナに登録しています。たとえば上記のルータークラスの実体は、LaravelのRoutingコンポーネントに含まれています。コンポーネントはそれぞれのサービスプロバイダーで初期処理を行っていますので、コンポーネントのディレクトリーの中の"〜ServiceProvider.php"というファイルを読めば、定義状況がわかります。Routingコンポーネントでは、"RoutingServiceProvider.php"になります。設定ファイルのapp.phpファイルで起動するプロバイダーを登録していますので、登録している完全名前空間付きクラス名から、場所を探し当てることもできます。

これが理論的な探し方です。面倒であれば、vender/laravelディレクトリ以下に対し、名前空間付きクラス名で検索するか「Search Everything」タイプの検索を使えばリストアップされるでしょうから、それを順番に確認していっても、さほど時間はかからないでしょう。

ファサード

技工も知識も必要ありません。公式ドキュメントにまとまっています。

ドライバー

セッションやデータベースなどは、「ドライバー」を使用し保存対象を切り替えます。実際に切り替えているのは、保存対象ではなくロジックなわけです。

ドライバーはハードウェアが統一されていない時代(今もほとんどそうですが)に、ハードウェア毎の差異をOSに全部持つのは効率的ではないため、使用するハードウェアに合わせた操作ソフトウェアを簡単に追加できるようにする仕組みのことです。

Laravelではハードウェアだけでなく、たとえばデータベースエンジン毎の差異に対応するように、切り替えて使用するソフトウェアをドライバーと呼んでいます。

ドライバーとして切り替えているコンポーネントには、設定ファイルが用意されています。そして、コンポーネントのクラスファイルを見ると"〜Manager"という名前のファイルが存在するので、ファイル構成を見るだけでも、ドライバー使っているんだと分かります。

設定ファイルで使用しているドライバーの名前が付いたディレクトリーやファイルを中心に見ていきましょう。メソッド名で検索すると同じ名前のメソッドが複数クラスファイルに見つかりますが、そのときも自分が使用しているドライバー名を含んだディレクトリー/ファイルを中心に確認すれば、余計なファイルを見る必要はありません。

IDEヘルパー

IDE使用時に、メソッド名の補完効率をあげるために、IDEヘルパーを使用している方も多いと思います。IDEヘルパーはファサード記法を使用時のコード補完ヘルパーです。

IDEヘルパーを使用時に宣言元へ移動すると、IDEヘルパーが生成したファイルへ移動することになるでしょう。そのときもオリジナルのクラスメソッドがreturn文で返されているはずです。静的メソッドで定義されていますが、定義元へ移動可能です。本当にstaticであるかどうかに関係なく、宣言元へ移動できます。

つまり、定義元へ移動するためにひと手間かかります。