PHPのFrameworkを使う時に知っておいた方が良いかもしれないこと

# 概要

Frameworkを使う前でも使った後でも良いんですが、ルーティング、名前空間、オートロードは知っておいた方が良いのかなと思いとりあえずメモ書き。  

# ルーティング

やや雑な説明ですが、リクエストを受けた時に「このアドレスならこのファイルだね！」とリクエストされたアドレスと該当する（該当させたい）ファイルを繋ぐお役目やルーティングの仕事です。リクエスの案内人やナビゲーターという感じですね。LaravelやcakePHP等のFrameworkはこのルーティングを簡単に設定できるようになっています。Frameworkさんさすがっす。
と言うことで、自分でルーティングを作ってみようではありませんか。Frameworkを使う上で必要無い知識ではありますが、裏側ではこんなことやってるんだなーと知っておいて損は無いと思います。各Frameworkがこうやって実装されているという話ではありませんのでご注意ください。もしFrameworkの中身を知りたいのであればFrameworkのコードを読むのが一番確実で間違いないです。

前置きが長くなりました。ルーティングを自前で書いてみます。
ディレクトリ構成は以下の通りです。

```
root
 ├ index.php
 └ hello.php
```

これぐらいであればルーティングは必要ないのですが、ルーティングのイメージということでひとつ。

```php
// index.php
$uri = parse_url($_SERVER['REQUEST_URI'])['path'];
echo $uri;
```
```php
// hello.php
echo 'Hello world';
```

これだけ書いたら`$uri`に何が入るか確認してみます。

```
php -S localhost:8080
```

ビルトインWebサーバを起動して`localhost:8080/hoge/piyo`にアクセスしてみましょう。`/hoge/piyo`と表示されればOKです！ルーティングはこのURIをベースに実装していくことになります。引き続きinde.phpに手を入れていきましょう。

```php
// index.php
const $routing = [
    '/helloworld' => '/hello'
];
$uri = parse_url($_SERVER['REQUEST_URI'])['path'];
require __DIR__.$routing[$uri].'.php';
```

どのURIとファイルを突き合わせを設定して、`/helloworld`にアクセスされたら`hello.php`が呼ばれるようにしました。

これがルーティングの基本となります（実装方法ではなく、考え方の基本という意味です）。ただし、これだけでは$routingで定義されていないURLにアクセスされた場合、requireでエラーが発生してしまいますので、実際には$routingで定義されていないURIの場合は404を返すとか、require対象のファイルが存在するかを確認したりします。

```php
// index.php
$routing = [
    '/helloworld' => '/hello'
];
$uri = parse_url($_SERVER['REQUEST_URI'])['path'];
if (!isset($routing[$uri])) {
    http_response_code(404);
    return;
}

$filePath = __DIR__.$routing[$uri].'.php';
if ($filePath) {
    require $filePath;
} else {
    http_response_code(500);
}
```

書くのであればこんな感じでしょうか。かなり雑ですが。実際にはPHPだけではなくWebサーバのミドルウェア（Apache等）側の設定もかまうことになりますが、今回は割愛します。
Frameworkはこういう面倒くさいことを裏側でやってくれてるわけですね、ありがたし。

# 名前空間

続いて名前空間について簡単に触れておこうと思います。名前空間の説明によく住所が使われますね。学校の学年、クラス、名前でも良いかもしれません。例えば「山田」という人が上級生にも下級生にもいた場合、山田くんと言ってもどちらの山田くんかわかりません。さらに言えば同学年で2人山田くんがいた場合、これはもう困ってしまいます。この時に「x学年のyクラスの山田くん」と指定すれば問題は解決できます。これが名前空間そのものです。
山田くんが瀬文さんとかに改名するのも一手ですが、これは名前空間とは違う方法になるので、今回は置いておきます。

名前空間で衝突が発生するのは「PHP組み込み」「サードパーティ製ライブラリ」「あなた製」のクラス、関数、変数名になります。
実際に衝突するコードを書いてみましょう。

```php
// index.php
class Error {
    public function __construct() {
        echo 'construct';
    }
}
$e = new Error(); // Fatal error: Cannot declare class Error, because the name is already
```

Errorクラスは既に定義済みのため、エラーとなってしまいます。となると自分で「Error」というクラスは作れない？となりますが、名前空間を使うことによって自分でErrorクラスを作ることができるようになります！このエラーは自分で書いたErrorクラスとPHPのErrorクラスが同じところ（同じ名前空間）にいるのが原因なので、PHPのErrorクラスと異なるところ（異なる名前空間）に自分のErrorクラスを置いてあげれば解決できます。

```php
// index.php
namespace phpTest;
class Error {
    public function __construct() {
        echo 'construct';
    }
}
$e = new Error();
```

先程のコードの先頭に`namespace phpTest;`を追加しました。これは、自分の作ったErrorクラスはphpTestという領域の下にあるクラスであって標準の（領域にある）Errorクラスとは違います！という意味になります。わかりやすく？するために曖昧な言葉を使っているので正確な説明ではありませんが概ねあっているはず。

となると自分で作ったErrorクラスを見ていると標準のErrorクラスは使えない？となりますが、もちろん標準のErrorクラスも使うことができます。

```php
// index.php
namespace phpTest;
class Error {
    public function __construct() {
        echo 'construct';
    }
}
$e1 = new Error();
$e2 = new \Error();
```

クラス名の前にバックスラッシュ（\）を入れることで利用可能です。他のファイルから使う場合は`use`を利用します。index.phpと同階層にuse.phpを作成し、以下のコードを記述して実行してみましょう。

```php
// use.php
require_once 'index.php';
use phpTest\Error;
$e = new Error();
```

実行結果として`constructconstruct`と表示されたかと思います。最初のconstructはindex.php側での結果で、後ろのconstructはuse.phpでErrorクラスをインスタンス化した時の処理結果です。`use phpTest\Error;`をコメントアウトし、実行すると実行結果は`construct`となり、use.phpでインスタンス化したErrorクラスはPHP標準のErrorクラスということがわかります。asキーワードでエイリアスを付与できます。

関数や定数も名前空間化できるようですが、基本的に使うことはないと思うので割愛します。

# オートロード

自分でオートローダーもどきを作る必要が出てくることはそう無いと思いますが、実際作るとなるとどうやって実装するのか、Frameworkの裏側ではこんなことをやってるんだなーというのが何となくわかるように一応やっていこうと思います。
PHPでオートロードを実装する時にはspl_autoload_register関数を利用します。spl_autoload_register以外にもオートロードを実装する関数はあるのですが、spl_autoload_registerが一般的かなと思います。

```php
// index.php
spl_autoload_register(function ($class) {
    $autoLoad = [
        'phpTest\Hoge' => __DIR__.DIRECTORY_SEPARATOR.'hoge.php',
        'phpTest\Piyo' => __DIR__.DIRECTORY_SEPARATOR.'piyo.php'
    ];
    if(isset($autoLoad[$class])) {
        include $autoLoad[$class];
    }
});
$hoge = new phpTest\Hoge();
$hoge->say();
$piyo = new phpTest\Piyo();
$piyo->say();
```
```php
// hoge.php
namespace phpTest;
class Hoge {
    public function say() {
        echo 'hoge!';
    }
}
```
```php
// piyo.php
namespace phpTest;
class Piyo {
    public function say() {
        echo 'piyo!';
    }
}
```

※hoge.phpとpiyo.phpはindex.phpと同階層に配置します。

spl_autoload_registerの使い方に関してはPHP公式リファレンスを見てもらえればわかりますが、上記の例で言えば難しいことはしていません。
ただ、いちいちクラスを追加する度に定義をspl_autoload_registerに登録した関数内に追加していくのは非常に手間でメンテンスが面倒です。名前空間と実際のディレクトリ構成を連動させて、'\\'でexplode/implodeし、定義不要で名前の通りオートロードするようにするのが良いと思います。

# 最後に

基本 is 大事。