includeって書きたくない僕たちのためのオートローディングとComposer

こんにちはこんにちは、PHP書いてますか？ `include_once` してますか？ それともキミは `require_once` 派？

ところで、現代的なPHPではクラスファイル(ここでは`class`, `trait`, `interface`を含む定義ファイル)では、わざわざファイルを`include`/`require`しなくても自動的に読み込む機能をカンタンに構築できる環境があるので、紹介いたします。

この記事は手を動かして動作確認しながら読めるように構成してありますので、斜め読みするだけではもったいないですよ ヾ(〃＞＜)ﾉﾞ

## はじめに

今回の記事ではクラスの自動ロード(オートローディング)の概要に絞って解説しますが、名前空間の文法や細かい説明を含めて包括的に解説した記事は、既に[WEB+DB PRESS Vol.91｜技術評論社](http://gihyo.jp/magazine/wdpress/archive/2016/vol91)にて「PHP大規模開発入門 第12回 名前空間とオートローディング」として発表済みです。 ~~現代仮名遣いで書かれてるので職場でもオススメしやすいですね！！！１~~

この記事の内容とは重なるところも重ならないところもありますが、業務コードへの適用を含めて検討される型は、できれば雑誌掲載の記事と合せてお読みいただければ理解の助けになるのではないかと思ひます。 <small><!-- 私は原稿料をいただかない記事は絶対にまじめに書かないと決めてますので… --></small>

## オートローディングのための仕組み

PHP公式マニュアルの[PHP: クラスのオートローディング - Manual](http://php.net/manual/ja/language.oop5.autoload.php)を嫁。

以上、で完結する話ではあるのですが、もうすこし噛み砕いて説明を試みます。

## いますぐわかるオートローディングの仕組み

### ここにPHPプロジェクトがあるじゃろ

問題の単純化のために、以下のようなカンタンなPHPプロジェクトを用意しました。

PHPを動かす環境があるひとは、ぜひ自分で手を動かして試してみてくださいね ヾ(〃＞＜)ﾉﾞ☆

```
.
|-- public
|   `-- index.php
`-- src
    |-- User.php
    `-- bootstrap.php

2 directories, 3 files
```

置いてあるファイルも極度に単純化して、こんな感じ。`public`はドキュメントルート(Webサーバーからアクセスされるファイルを置く場所)です。

```index.php
<?php

namespace ZonuProject;

require_once __DIR__ . '/../src/bootstrap.php';

$user = new User("重音テト");
?>
<!DOCTYPE html>
<title>自己紹介</title>
<p><?= htmlspecialchars($user->saySelfIntroduce(), ENT_QUOTES) ?></p>
```

```bootstrap.php
<?php

/**
 * 各ファイルから共通で読み込まれる初期化ファイルだよ
 */

namespace ZonuProject;

include_once __DIR__ . '/User.php';
```

```User.php
<?php

namespace ZonuProject;

class User
{
    /** @var string */
    private $name;

    /**
     * @param string $name
     */
    public function __construct($name)
    {
        $this->name = $name;
    }

    /**
     * @return string
     */
    public function saySelfIntroduce()
    {
        return "こんにちは、私は{$this->name}です。";
    }
}
```

汎用性のないクラス設計だな、ってつっこみは置いておくとして、業務で利用するようなPHPアプリケーションの一番シンプルな形ですね。

Apacheのmod_phpで動かせるひとはそのままでいいんですけど、ローカルで動かすサーバーがなくても、 `cd public; php -S localhost:3939` でサーバーを起動して、ブラウザで http://localhost:3939/ を開いてみてください。

![「こんにちは、私は重音テトです」と表示される](https://qiita-image-store.s3.amazonaws.com/0/4121/dc31a1b0-86ad-29ab-f046-152f0f741814.png)


### なかまを増やそう

さて、このままプロジェクトが成長していくと、`User`と同じようなクラスがたくさん、たっくさん増えてくことが予想できます。そうすると、`bootstrap.php`はどうなりますか？

```bootstrap.php
<?php

/**
 * 各ファイルから共通で読み込まれる初期化ファイルの未来予想図だよ
 */

namespace ZonuProject;

include_once __DIR__ . '/User.php';
include_once __DIR__ . '/Book.php';
include_once __DIR__ . '/Work.php';
include_once __DIR__ . '/Map.php';
include_once __DIR__ . '/Novel.php';
include_once __DIR__ . '/Album.php';
include_once __DIR__ . '/Tool.php';
include_once __DIR__ . '/Music.php';
// …そのほかいっぱい！
```

ファイル数が10個とかそこいらならば良いのですが、クラス数が100とか1000とかになってくると、わざわざ追加するのは大変な作業になってきますね。

人力がたいへんな問題もありますが順番も意外と厄介な問題です。たいてい、クラスどうしは依存関係を持ちます。さらに、実行時に別に必要ではないファイルが常に読み込まれるのは、わかってることではありますが無駄です。

### いまクラスローダーの力が欲しい

そこで話が[`spl_autoload_register()`](http://php.net/manual/ja/function.spl-autoload-register.php)に帰ってきます。クラスローダーを登録してやると、クラスがみつからなかったときにそれが呼ばれます。

クラスローダーは、引数で「(名前空間付きの)クラス名」を受け取って、該当するファイルがあれば読み込んでやる機能を持った函数です。この機能は、わざわざ利用者を脅すほど難しいものではないです。もしファイルがあったら`require`か`include`をする。それだけ。

クラスローダーの実装はグローバル函数でも、メソッドでも、クロージャでも大丈夫です。

```bootstrap.php
<?php

/**
 * 各ファイルから共通で読み込まれる初期化ファイルだよ
 *
 * @copyright 2017 tadsan
 * @license   WTFPL
 */

namespace ZonuProject;

spl_autoload_register(function ($class_name) {
    // 名前空間の先頭が一致しなければこのプロジェクトのクラスではない
    if (strpos($class_name, 'ZonuProject\\') !== 0) {
        return false;
    }

    // 名前空間の \ をディレクトリの / に変換する
    $class_file = strtr(ltrim($class_name, 'ZonuProject\\'), ['\\' => '/']);
    $path = __DIR__ . "/{$class_file}.php";

    if (file_exists($path)) {
        require_once $path;
    }
});
```

これを実装するときは、実行時にエラーにならないように気をつけて実装してくださいね。 ~~この実装は適当に書いたからエラーになるパターンがあるかもね~~

### ではクラスを追加してみよう

えー、こんな怪しげな実装を持ってきただけで本当に動くの？ 試してみてくださいな。

```Item.php
<?php

namespace ZonuProject;

/**
 * @property-read string $name
 */
class Item
{
    /** @var string */
    private $name;

    /**
     * @param string $name
     */
    public function __construct($name)
    {
        $this->name = $name;
    }

    public function __get($prop)
    {
        return $this->$prop;
    }
}
```

```User.php
<?php

namespace ZonuProject;

class User
{
    /** @var string */
    private $name;

    /** @var Item */
    private $item;

    /**
     * @param string $name
     */
    public function __construct($name)
    {
        $this->name = $name;
    }

    public function setItem(Item $item)
    {
        $this->item = $item;
    }

    /**
     * @return string
     */
    public function saySelfIntroduce()
    {
        $msg = "こんにちは、私は{$this->name}です。";

        if ($this->item) {
            $msg .= "持ち物は{$this->item->name}です。";
        }

        return $msg;
        }
}
```

```index.php
<?php

namespace ZonuProject;

require_once __DIR__ . '/../src/bootstrap.php';

$teto = new User("重音テト");

$negi = new Item("ネギ");
$miku = new User("初音ミク");
$miku->setItem($negi);

?>
<!DOCTYPE html>
<title>自己紹介</title>
<p><?= htmlspecialchars($teto->saySelfIntroduce(), ENT_QUOTES) ?></p>
<p><?= htmlspecialchars($miku->saySelfIntroduce(), ENT_QUOTES) ?></p>
```

動きましたな？ 動きましたね？ 完璧です。

![「こんにちは、私は重音テトです。」「こんにちは、私は初音ミクです。持ち物はネギです。」と表示される](https://qiita-image-store.s3.amazonaws.com/0/4121/1f390f8d-35b6-5588-b9fa-fbfc07c58951.png)


じつはここまでの話は、[PSR-4: Autoloader](http://www.php-fig.org/psr/psr-4/)の付属文書である[PSR-4-autoloader-examples.md](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md)を読めばわかることです。といふかこの記事で書いた実装は当てずっぽうで書いたので、この文書の方のサンプル実装をまねした方が絶対いいですよ。

## Composer

さて、ここまでクラスのオートローディングの仕組みを体験していただいたわけですが、これからComposerの世界を体験していただきます。Composerを使ったことがあるひとも、ないひとも試してみてくださいね。

### コンポーザー、キミの番！

ComposerはPHPの依存性管理マネージャーです。要は、PHPの依存ライブラリとかを管理できます。筆者もコンポーザーがだいすきなので、2016年4月にPHPカンファレンス北海道で[仕事で使えるComposer](http://niconare.nicovideo.jp/watch/kn1371)って発表をしたりしました **(←いまじゃなくて後で読んでね)**。

さて、ここからは手順に注意してくださいね。

シェルでプロジェクトのルートディレクトリに移動して、[Download Composer](https://getcomposer.org/download/)の説明の通りにコマンドをコピペして、ディレクトリに`composer.phar`を用意します。

以下のようなディレクトリ構成になってたら、ここまでの説明通りです。せっかくなので、ここで`git init`コマンドを打っておいてください。`git commit`は、してもしなくてもいいです。

```
.
├── composer.phar
├── public
│   └── index.php
└── src
    ├── Item.php
    ├── User.php
    └── bootstrap.php

2 directories, 5 files
```

次に、`composer`を初期化します。シェルで以下のようなコマンドを打ってみます。

```
php composer.phar init
```

説明が~~だる…~~けふん、~~めんどくさ…~~けふんけふん、長くなるのでアニメーションGIFで見てください。`git config`とかちゃんと設定済みなら、結構自動入力されるのでいちいち入れなくて良いです。

![composer-interactive.gif](https://qiita-image-store.s3.amazonaws.com/0/4121/600f67b5-4149-0013-fe0c-02abc1cfcb97.gif)


そして、以下のコマンドを打ちます。

```
php composer.phar install
```

ここまで実行すると、ディレクトリ構成は以下のようになります。

```
.
├── composer.json
├── composer.phar
├── public
│   └── index.php
├── src
│   ├── Item.php
│   ├── User.php
│   └── bootstrap.php
└── vendor
    ├── autoload.php
    └── composer

4 directories, 15 files
```

### 載ろう巨人の肩

さて、Composerは何もしなくても勝手にロードされる… わけではないので、`bootstrap.php`からCompsoserのファイルを読み込んでやります。

```bootstrap.php
<?php

/**
 * 各ファイルから共通で読み込まれる初期化ファイルだよ
 *
 * @copyright 2017 tadsan
 * @license   WTFPL
 */

namespace ZonuProject;

require_once __DIR__ . '/../vendor/autoload.php';

// spl_autoload_register() は不要なので消しちゃってね
```

`spl_autoload_register()`は用済みなので消します。 ~~短い命だったね~~

で、`composer.json`をエディタで開いて編集してやります。

```diff:composer.json
diff --git a/composer.json b/composer.json
--- a/composer.json
+++ b/composer.json
@@ -9,5 +9,10 @@
             "email": "tadsan@zonu.me"
         }
     ],
+    "autoload": {
+        "psr-4": {
+            "ZonuProject\\": "src/"
+        }
+    },
     "require": {}
 }
```

済んだら、またシェルでコマンドを打ちます。(一回だけ実行すれば大丈夫です)

```
php ./composer.phar dump-autoload
```

この状態でブラウザを開いてみて、動きましたか？ 動きましたよね。

バンザイ！ これでわれわれは、いちいち自前でクラスローダーを実装しなくてもファイルを自動ロードできます。これでこの記事の目的は達成されましたね。

この設定では、お作法よくファイルを追加する限り(つまり規則的な名前空間とクラス名と一致したファイル名を利用する限り)、ずっとずっとファイルが自動ロードされます。 ~~[あたし完璧](http://dic.pixiv.net/a/%E3%81%82%E3%81%9F%E3%81%97%E5%AE%8C%E7%92%A7)~~

### うちのクラスに命名規則はない

さて、ここまで新しく作るプロジェクトにクラスのオートローディングを利用するケースを紹介しました。が、現実の既存プロジェクトでは名前空間がなかったり、クラス名とファイル名が揃ってなかったりします。

例として、プロジェクト内の命名規則を完全に無視したクラスが`src/tools/Hoge.php`にあったとします。

```src/tools/Hoge.php
<?php

class UltraHoge
{
    public static function fugafuga()
    {
        return "ふがふが";
    }
}
```

それを`index.php`から呼びたいとします。

```html+php:index.php
<p><?= htmlspecialchars(\UltraHoge::fugafuga(), ENT_QUOTES) ?></p>
```

そんなときは… こうじゃ。

```diff:composer.json
diff --git a/composer.json b/composer.json
--- a/composer.json
+++ b/composer.json
@@ -10,6 +10,7 @@
         }
     ],
     "autoload": {
+        "classmap": ["src/tools"],
         "psr-4": {
             "ZonuProject\\": "src/"
         }
```

`composer.json`を編集したら、例によってシェルで`php composer.phar dump-autoload`します。さて、動きましたか？

`classmap`は、既存プロジェクトに対して理性的な命名規則整理ができないときの**最終手段**です。新規プロジェクトでいきなりこの設定をつっこむのは、あんまり賢明ではないです。この方式の弱点は、クラスを追加するたびに`php composer.phar dump-autoload`する必要があることです。

### 函数は自動ロードできない

さて、クラスファイル(`class`, `trait`, `interface`)はオートローディング機能による自動ロード対象ですが、クラスに属さない函数は自動ロードできないです。なので、必要なら前もって読み込んでおくのが定石です。

ここで、`functions.php`を追加するとします。

```php
<?php

namespace ZonuProject;

/**
 * htmlspecialchars() の短縮記法
 *
 * @param string $input
 */
function h($input)
{
    return \htmlspecialchars($input, \ENT_QUOTES);
}
```

`bootstrap.php`に `include_once __DIR__ . '/functions.php';` とか追記しても良いですけど、あまりにも藝がないので、今度もComposerの機能を利用します。

```diff:composer.json
diff --git a/composer.json b/composer.json
--- a/composer.json
+++ b/composer.json
@@ -11,6 +11,7 @@
     ],
     "autoload": {
         "classmap": ["src/tools"],
+        "files": ["src/functions.php"],
         "psr-4": {
             "ZonuProject\\": "src/"
         }
```

`files`に記述したファイルは自動で読み込まれます。クラス定義用のファイルは自動ロードした方がいいので、ふつうここには追加しません。

### 依存ライブラリは自動ロードされる(たいてい)

さて、せっかくなので<q>PHP errors for cool kids</q>こと、僕らのイカしたエラー画面である[whoops!](http://filp.github.io/whoops/)を導入してみます。

シェルでこんなコマンドを打つと良いよ。

```
php ./composer.phar require filp/whoops
```

すると`composer.json`の中身が勝手に更新されるので、**そのまま弄らずに** `git add composer.json`してあげるのがオススメ。

あと、このコマンドを打つと`composer.lock`が生まれます。いま作ってるのはライブラリじゃなくてアプリケーションなので、これも`git add composer.lock`してあげます。大事Daiji。

そしたら今度は、`bootstrap.php`の中身を更新してあげます。追加するのは三行だけ。

```bootstrap.php
<?php

/**
 * 各ファイルから共通で読み込まれる初期化ファイルだよ
 *
 * @copyright 2017 tadsan
 * @license   WTFPL
 */

namespace ZonuProject;

require_once __DIR__ . '/../vendor/autoload.php';

$whoops = new \Whoops\Run;
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
$whoops->register();
```

おめでとう、これであなたもクールなPHPerの仲間入りです。どういふことかって？

どこでもいいから、適当な場所で`throw new \Exception("ひゃっはーーーーーーーーー");`とか書いてやればわかります。

![whoops!を使った、わりとカッコイイエラー画面](https://qiita-image-store.s3.amazonaws.com/0/4121/a6138f74-8233-0192-f66f-619eba5715e0.png)


カッコイイ画面になりましたね。

では、こんどは`boostrap.php`に追加した行を消すか、コメントアウトすると、どうなりますか？

![PHP7 標準エラー画面](https://qiita-image-store.s3.amazonaws.com/0/4121/c6572ce1-79be-1bc7-9097-4110a93a26f8.png)


どちらが好きかは、それぞれ好み次第ですね……。

## まとめ

- PHPのクラスはいちいち`include_once`しなくても、自動ロードの仕組み(オートローディング)があるよ
- オートローディングのためのクラスローダーは、わざわざ自前で実装しなくてもComposerでいいよ
- [Composer](https://getcomposer.org/)があれば、この世はパラダイス
- この記事のサンプルコードは https://github.com/zonuexe/no-include-php にあります

ちなみにオートローディングの仕組みが理解できれば、[へんてこなメタプログラミングの温床](http://qiita.com/tadsan/items/ea6fca9cbc5bc79b3994)にできるので、とってもオススメ~~しません~~。

### 参考文献

公式の資料とかです。

- PHPマニュアル(公式・日本語)
  - [PHP: spl_autoload_register - Manual](http://php.net/manual/ja/function.spl-autoload-register.php)
  - [PHP: クラスのオートローディング - Manual](http://php.net/manual/ja/language.oop5.autoload.php)
- PSR (PHP標準勧告)
  - [PSR-4: Autoloader](http://www.php-fig.org/psr/psr-4/)
  - [PSR-4-autoloader-examples.md](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md)
- [Composer](https://getcomposer.org/)
  - [Download Composer](https://getcomposer.org/download/)
  - [The composer.json Schema](https://getcomposer.org/doc/04-schema.md)

### 参考にしろ文献

筆者が書いたやつです。勝手に参考にしてもいいし、しなくてもいいです。

- スライド ([黒點 さんのマイページ - ニコナレ](http://niconare.nicovideo.jp/users/5962))
  - [たのしい独自フレームワーク](https://www.slideshare.net/tadsan/ss-55459322)
  - [仕事で使えるComposer](http://niconare.nicovideo.jp/watch/kn1371)
- Qiita
  - [インスパイヤされて掲示板を作りたくなった(1)](http://qiita.com/tadsan/items/cdbbb5b08591af2b110d)
     - ↑ このシリーズでいろいろ書いてるよ ヾ(〃＞＜)ﾉﾞ
  - [PHPのモダンな開発環境を紹介する](http://qiita.com/tadsan/items/a0a14e58aa4d88f19624)
  - [名前空間付き函数 VS 静的メソッド ﾌｧｲｯ](http://qiita.com/tadsan/items/5bba4bbd697379cc4b4a)
  - [~~どこでもクラスが参照できるふしぎなクラスローダー~~](http://qiita.com/tadsan/items/ea6fca9cbc5bc79b3994)

-------

この記事は深夜のテンションで、けものフレンズ第10話を見ながら、おなかすいた気持ちで書きました。もしこの記事が読者の業務に役立つことがあるなら、<u>焼肉でも奢られたい気持ちでいっぱい</u>です ヾ(〃＞＜)ﾉﾞ