PHPの実行時とコンパイル時

PHPはプログラムの実行前にソースコードをコンパイルするんだよ、ってことを雑に書いた。

## 函数定義はコンパイル時に

単純にソースコードを上から順に実行してるだけではないことは次の例で確認できます。

```php:hello.php
<?php
echo hello(), PHP_EOL; // hello()の定義はexitの下にある
exit; //ここで処理は終了する

echo "a", PHP_EOL; // この行は実行されない

function hello()
{
    return "Hello!";
}

echo "b", PHP_EOL; // この行は実行されない
```

```sh:結果
Hello!
```

次の例も見てみませう。

```php:hello2.php
<?php
echo "1", PHP_EOL;

function hello()
{
    return "Hello! 1";
}

echo "2", PHP_EOL;

function hello() // 同名の函数は定義できない
{
    return "Hello! 2";
}
```

```sh:結果
Fatal error: Cannot redeclare hello() (previously declared in /home/jail/prog.php:7) in /home/jail/prog.php on line 15
```

`Cannot redeclare hello()`と怒られますが、`1`も`2`も出力されることはありません。

## クラスの解決は実行時に

存在しないクラスを参照しようとしたり静的ではないメソッドを静的に呼ぼうとしても、怒られるのは実行時だ。

```php:method.php
<?php
error_reporting(E_ALL | E_STRICT);
set_error_handler('error_handler');

$foo = new Foo;
echo Foo::bar(), PHP_EOL;

if (false) {
    echo Foo::Buz(), PHP_EOL; // メソッドを静的に呼び出そうとする
    $hoge = new Hoge;         // 存在しないクラスのインスタンス
    $hoge->fuga();            // 存在しないクラスの存在しないメソッド
    hoge_fuga();
}

echo $foo->buz(), PHP_EOL;
hoge_fuga(); // 存在しない函数を読んでみる

echo "Bye.", PHP_EOL; // ↑で止まるので、この行が出力されることはにあ

exit;

final class Foo
{
    public static function bar()
    {
        return "Foo::Bar!";
    }

    public function buz()
    {
        return "foo->Buz!";
    }
}

function error_handler($severity, $message, $file, $line) 
{
    throw new \ErrorException($message, 0, $severity, $file, $line);
}
```

```sh:結果
Foo::Bar!
foo->Buz!
Fatal error: Call to undefined function hoge_fuga() in /home/jail/prog.php on line 16
```

実行時にしか怒られないといふことは、`if (false)`の中身は実行されないので怒られない！

## クラスローダー

PHPのクラスローダーは、実行時に未定義のクラスが登場したときに、初めてそのクラスが定義されたファイルを読み込もうとする仕組みだ。php.netの[PHP: クラスのオートローディング - Manual](http://php.net/manual/ja/language.oop5.autoload.php)も読まれたい。

今回は詳しい説明を省くが、[Composer](https://getcomposer.org/)または[theseer/Autoload](https://github.com/theseer/Autoload)を利用するのが良い。

```sh
$ tree .
.
└── src
    ├── Hoge
    │   └── Fuga.php
    ├── Hoge.php
    └── Piya.php

2 directories, 3 files
```

Autoload Builderは以下のようなPHPを生成する。

```php:autoload.php
<?php
// @codingStandardsIgnoreFile
// @codeCoverageIgnoreStart
// this is an autogenerated file - do not edit
spl_autoload_register(
    function($class) {
        static $classes = null;
        if ($classes === null) {
            $classes = array(
                'zonuexe\\hoge' => '/src/Hoge.php',
                'zonuexe\\hoge\\fuga' => '/src/Hoge/Fuga.php',
                'zonuexe\\piyo' => '/src/Piya.php'
            );
        }
        $cn = strtolower($class);
        if (isset($classes[$cn])) {
            require __DIR__ . $classes[$cn];
        }
    },
    true,
    false
);
// @codeCoverageIgnoreEnd
```

このような仕組みを利用すれば、`require`するタイミングを実行時に遅延できる。

### 最後に

「PHPはコンパイルしない言語」みたいな意味不明な発言はやめよう。

`php hoge.php`のようにコマンドにソースコードのファイルを指定して直接実行してるように見えたとしても、現代的なインタプリタはソースコードをパースしたあと[仮想機械](https://ja.wikipedia.org/wiki/%E4%BB%AE%E6%83%B3%E6%A9%9F%E6%A2%B0)の[バイトコード](https://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%83%88%E3%82%B3%E3%83%BC%E3%83%89)にコンパイルする。RubyもPythonもPerl5もEmacsも同様に。

では「PHPはネイティブコードにコンパイルしない」であれば正しいかといふと、Facebookの開発した[HHVM](http://hhvm.com/)でPHPを実行すると[JITコンパイラ](https://ja.wikipedia.org/wiki/%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9)によってネイティブコードにコンパイルされる。

もっと実装寄りの具体的な話を知りたい方は、ぜひ[PHP による hello world 入門 | 東北ギーク](http://tech.respect-pal.jp/php-helloworld/)といふ神記事をお読みください。