GAEで原始的なPHP7のプロジェクトを動かすのに苦労した話

##動機
Google App EngineでPHP7のサンプル的なプロジェクトを動かそうとしてえらく苦労したのでその顛末をここに記す。
構成としてはルートに複数のPHPファイルと背景画像、その下のフォルダにPHPファイルやhtmlファイル、画像などがあるようなものだ。HerokuやAWS ElasticBeanstalk、Azureではローカルで動いていたPHPのプロジェクトをそのままデプロイすればそのまま動いた、実に簡単だった。しかしGoogle App Engineではそうはいかなかった。

##app.yaml
まず、アプリの設定情報を記述するapp.yamlを書かないといけない。どのバージョンを使うのかとか、アクセスされたパス、拡張子に応じてどのような動作するのかとか。いちいち言わんでもわかるやろと思ってしまうが。
で、app.yaml、phpという単語でググるとこの[PHP5用のページ](https://cloud.google.com/appengine/docs/standard/php/config/appref?hl=ja)がトップに表示されるのだが、PHP5とPHP7とでは動作が異なるので、PHP7のときはこれは使えない。上の方に「PHP7の使用を強くお勧めする」と書いてあるが、字が小さいので気が付かなかった。
PHP7のときはどんなふうに書けばいいのかというと[こちら](https://cloud.google.com/appengine/docs/standard/php7/config/appref?hl=ja)。
違いは拡張子phpの場合で、PHP5のときはphpのファイルをスクリプトとして処理せよという形になっているが、PHP7のときは拡張子の条件は消え、全てのファイル(.*)に対し、autoになっている。

```yaml:php5
# Serve php scripts.
- url: /(.+\.php)$
  script: \1
```

```yaml:php7
# Serve your app through a front controller at index.php or public/index.php.
- url: .*
  script: auto
```

コメントには「index.phpのフロントコントローラを介してappを提供しろ」と書いてある。
はてどういうことだろうと思いながら、とりあえず動かそうとすると、
ルートに置いたindex.phpのスクリプトは正常に動作するが、同じ階層にある別のphpファイルや下の階層のphpファイルにアクセスしようとしても、index.phpにリダイレクトされる。なんでや。

##フロントコントローラ
いろいろ調べてみるとどうやらindex.phpを入り口（フロント）として、そこから他のファイルへのアクセスを振り分けてやる（コントロールする）のがお作法らしいということがわかる。元々あったindex.phpはそのままにしておきたいので、ここではcontroller.phpという名前で新たにファイルを作り、そこを入り口にしてやる。入り口の名前はapp.yamlにentrypointとして書けば良い。


```yaml:app.yaml
entrypoint: serve controller.php

```

そしてcontroller.phpはどんなふうに書いたらいいのか。いろいろさまよって[ここ](https://cloud.google.com/appengine/docs/standard/php7/runtime#application_startup)にたどり着いた。
なんだここに大事なことが大体書いてあるじゃないか。Googleのマニュアルは広大すぎてなかなか必要なところにたどり着けない。
そしてサンプルコードがGitHubに上がっている。
https://github.com/GoogleCloudPlatform/php-docs-samples/tree/master/appengine/php72/front-controller

ここでは

- Slim Frameworkを使用する場合
- WordPressを使用する場合
- 正規表現でやる場合

の３パターンを上げてくれてある。迷わず[正規表現の例](https://github.com/GoogleCloudPlatform/php-docs-samples/blob/master/appengine/php72/grpc/index.php#L11)を見る。
正規表現は何回やっても覚えられないので、調べながら格闘、このサンプルコードがやっているのは
spanner.php、monitoring.php、speech.phpにアクセスしてきた時はそのファイルを応答として返すということのようだと理解する。

```php:controller.php
// Static list provides security against URL injection by default.
$routes = [
    'spanner',
    'monitoring',
    'speech',
];
// Keeping things fast for a small number of routes.
$regex = '/\/(' . join($routes, '|') . ')\.php/';
if (preg_match($regex, $_SERVER['REQUEST_URI'], $matches)) {
    $file_path = __DIR__ . $matches[0];
    if (file_exists($file_path)) {
        require($file_path);
        return;
    }
}

```

とりあえず拡張子phpのファイルは全部同じように処理して欲しいので、
正規表現の式を以下のように書き換えた。
先頭の「/\/」は意味がわからなかったので/にした。

```php:controller.php
$regex = '/.+\.php/';
if (preg_match($regex, $_SERVER['REQUEST_URI'], $matches)) {
    $file_path = __DIR__ . $matches[0];
    if (file_exists($file_path)) {
        require($file_path);
        return;
    }
}
```

##app.yaml再び
これでとりあえずphpのアプリはみんな動いた。だが普通のhtmlファイルが表示されない。
拡張子がhtmlのときの動作がapp.yamlで定義がされていないからである。
再びapp.yamlを修正。
Googleのマニュアルで例として書かれていたものは、stylesheet以下のファイルと拡張子gif
、png、jpgの場合は静的リソースとしてそのまま配信しろ、それ以外はscript: autoという内容なので、htmlはscript: autoで処理されてしまうようである。何故だ。

```yaml:app.yaml
handlers:
# Serve a directory as a static resource.
- url: /stylesheets
  static_dir: stylesheets

# Serve images as static resources.
- url: /(.+\.(gif|png|jpg))$
  static_files: \1
  upload: .+\.(gif|png|jpg)$

# Serve your app through a front controller at index.php or public/index.php.
- url: .*
  script: auto
```
やりたいのは拡張子がphpの時だけスクリプトとして処理して、それ以外は全部そのまま配信して欲しいので、以下のようにした。上に書いてあるものが優先的に適用されるようである。

```yaml:app.yaml
handlers:
- url: /.+\.php
  script: auto

- url: /(.+)$
  static_files: \1
  upload: .+$
```

.\*（任意の０文字以上）ではなく.+（任意の１文字以上）にしたのはルートにアクセスされた際にindex.phpに飛んで欲しいから。.*だとルートにアクセスされた際もそのまま静的ファイルで応答しようとしてエラーになった。

##結論
controller.phpの方にもルートにアクセスされた場合にindex.phpに飛ばす処理を追加し、最終的にapp.yaml、controller.phpは以下の形になった。これでやっと思い通りの動きをするようになった。
こんな簡単な記述でいいのに、ここまでたどり着くのに3日かかった。Googleのマニュアル難しすぎ。なんでみんなあれで理解できるんだろう。

```yaml:app.yaml
runtime: php73
entrypoint: serve controller.php

handlers:
- url: /.+\.php
  script: auto

- url: /(.+)$
  static_files: \1
  upload: .+$
```

```php:controller.php
if($_SERVER['REQUEST_URI'] == '/'){
    require('index.php');
    return;
}

$regex = '/.+\.php/';
if (preg_match($regex, $_SERVER['REQUEST_URI'], $matches)) {
    $file_path = __DIR__ . $matches[0];
    if (file_exists($file_path)) {
        require($file_path);
        return;
    }
}

http_response_code(404);
exit('Not Found');
```