zend-expressive-skeletonをGoogleAppEngine/PHPにインストール


Zend公式のPSR-7対応マイクロフレームワーク「zend-expressive」を使ってみようと思い、
Google App Engine にインストールしたので、備忘メモです。

https://github.com/zendframework/zend-expressive

インストールした zend-expressive のバージョンは、1.0.0 です。

**--2016.06.10追記 start--**
> この記事の内容を踏まえて、
> 別記事「[zend-expressive-skeletonをGoogleAppEngine/PHP向けにカスタマイズ](http://qiita.com/curious-eyes/items/fbc11e709f2121cc215e)」
> で、簡単にインストールできるカスタマイズ版スケルトンを用意しました。

**--2016.06.10追記 end--**

## skeleton をインストール

動作確認しやすいように、スケルトンをインストールします。
テンプレートエンジンを「Plates」にした以外は、デフォルトのままです。

```bash
$ composer create-project zendframework/zend-expressive-skeleton <project dir>

Minimal skeleton? (no default middleware, templates, or assets; configuration only)
  [y] Yes (minimal)
  [n] No (full; recommended)
  Make your selection (No):

Which router do you want to use?
  [1] Aura.Router
  [2] FastRoute
  [3] Zend Router
  Make your selection or type a composer package name and version (FastRoute):

Which container do you want to use for dependency injection?
  [1] Aura.Di
  [2] Pimple
  [3] Zend ServiceManager
  Make your selection or type a composer package name and version (Zend ServiceManager):

Which template engine do you want to use?
  [1] Plates
  [2] Twig
  [3] Zend View installs Zend ServiceManager
  [n] None of the above
  Make your selection or type a composer package name and version (n): 1

Which error handler do you want to use during development?
  [1] Whoops
  [n] None of the above
  Make your selection or type a composer package name and version (Whoops):
  
```


## project dir の直下に app.yaml を追加

Google App Engine に必要な設定ファイルです。
とりあえず、サンプル画面を表示できる程度の設定にしました。

```yaml
module: default
version: 1
runtime: php55
api_version: 1

handlers:
- url: /favicon.ico
  static_files: public/favicon.ico
  upload: public/favicon.ico

- url: /zf-logo.png
  static_files: public/zf-logo.png
  upload: public/zf-logo.png

- url: /.*
  script: public/index.php

```

## ローカル環境で動作確認（失敗）
参考: https://cloud.google.com/appengine/docs/php/quickstart#test_the_application

恒例のSDKで動作確認してみます。

```bash
$ dev_appserver.py <project dir>

```

ブラウザで、localhost:8080 にアクセスしてみると。
すわ fatal error です。

```
Catchable fatal error: Argument 1 passed to Zend\ServiceManager\Config::__construct() must be of the type array, null given, called in /Users/shuhei/Documents/src/googleAppEngine/phpzend/config/container.php on line 11 and defined in /Users/shuhei/Documents/src/googleAppEngine/phpzend/vendor/zendframework/zend-servicemanager/src/Config.php on line 65

```

config/autoload 配下の定義ファイルを glob することができずに困っているようです。

## Load configuration for Google App Engine

定義ファイルの読み込みは、config/config.php で行っていますが、問題点は以下の２点です。

* glob()関数を使っている
* 定義ファイルの内容をキャッシュする際の出力先がファイル (file_put_contents)

Google App Engine向けの読み込み処理を作成しました。

* glob()関数を opendir()/readdir()関数に変更
* キャッシュ出力先を、ファイルからMemcacheへ変更


以下の内容を
config/config-gae.php
として、配置します。

```php:config/config-gae.php
<?php

use Zend\Stdlib\ArrayUtils;

/**
 * Configuration files are loaded in a specific order. First ``global.php``, then ``*.global.php``.
 * then ``local.php`` and finally ``*.local.php``. This way local settings overwrite global settings.
 *
 * The configuration can be cached. This can be done by setting ``config_cache_enabled`` to ``true``.
 *
 * Obviously, if you use closures in your config you can't cache it.
 */

$cachedConfigKey = 'cache_app_config';

$mc = new Memcached();

// Try to load the cached config
if(!($config = $mc->get($cachedConfigKey))) {
    if ($mc->getResultCode() == Memcached::RES_NOTFOUND) {
        $config = [];
    }
    $confdir = __DIR__ . '/autoload';
    if ($handle = opendir($confdir)) {
        $pattern = '/(global|local)\.php$/';
        $files = [];
        while (false !== ($file = readdir($handle))) {
            if (preg_match($pattern, $file) === 1) {
                $fn = implode('.', array_reverse(explode('.', $file)));
                $files[] = $fn;
            }
        }
        asort($files);
        foreach($files as $file) {
            $fn = implode('.', array_reverse(explode('.', $file)));
            $config = ArrayUtils::merge($config, include $confdir.'/'.$fn);
        }
        closedir($handle);
    }

    // Cache config if enabled
    if (isset($config['config_cache_enabled']) && $config['config_cache_enabled'] === true) {
        $mc->set($cachedConfigKey, $config);
    }
}

// Return an ArrayObject so we can inject the config as a service in Aura.Di
// and still use array checks like ``is_array``.
return new ArrayObject($config, ArrayObject::ARRAY_AS_PROPS);

```


呼び出し元の
config/container.php
を編集します。

```php:config/container.php
// Load configuration
//$config = require __DIR__ . '/config.php';
$config = require __DIR__ . '/config-gae.php';
```


## ローカル環境で動作確認（成功）


SDKのWebサーバが自動的にリロードしてくれるので、
ブラウザをリロードすれば、正常に表示されるはずです。

![Zend Expressive](http://f.cl.ly/items/071t202k1y0V3s1a081m/Home_-_zend-expressive.png)

app_config.php の最後に記載しているように、
エラー画面テンプレートも用意されているので、404 Not Foundの画面も表示できます。



## デプロイ
参考: https://cloud.google.com/appengine/docs/php/quickstart#deploy_your_app

https://console.developers.google.com/
で、プロジェクトを作成して、該当のプロジェクトIDに向けてデプロイしましょう。

```bash
$ appcfg.py -A YOUR_PROJECT_ID update app.yaml
```

http://(YOUR_PROJECT_ID).appspot.com/


今後、開発を進める上でどのような問題が出るか分かりませんが、ひとまず第一歩、完了です。