zend-expressiveを触ってみよう

この記事は[株式会社アイスタイルアドベントカレンダー](http://qiita.com/advent-calendar/2015/istyle)の10日目の記事です。

PSR-7対応の[zend-expressive](https://github.com/zendframework/zend-expressive)、注目している方やすでに利用している方も多いとおもいます。

PSR-7対応のマイクロフレームワークとしては、本エントリで扱うzend-expressiveを除いて、
現在以下のようなものがあります。
[slim3](https://github.com/slimphp/Slim)
[radarphp/Radar.Project](https://github.com/radarphp/Radar.Project)
[sparkphp/project](https://github.com/sparkphp/project)

zend-expressiveはルータやDIコンテナを選択することができ、
開発者の開発スタイルや要件に合わせて自由にライブラリを組み込むことができるマイクロフレームワークで、
主にHTTPのリクエスト、レスポンスを主軸に設計されています。

zend-expressiveのスケルトンを利用する場合は、
composerのcreate-projectでインストールできます。
いろんな方がブログなどで説明されていますのでここでは割愛します

```bash
$ composer create-project -s rc zendframework/zend-expressive-skeleton expressive
```

スケルトンを使わずに、expressiveを利用して独自のフレームワークを構築する場合は、
zendframework/zend-expressive単体で利用することもできます。

スケルトンを使ってアプリケーションの処理の流れを追いながら、
少しだけライブラリを入れ替えてみましょう。
ここではfastRoute, serviceManagerを選択しています

# エントリポイント public/index.php
public/index.phpでHTTPリクエストを受け取り、アプリケーションが構築されます。

```php
/** @var \Interop\Container\ContainerInterface $container */
$container = require 'config/container.php';

/** @var \Zend\Expressive\Application $app */
$app = $container->get('Zend\Expressive\Application');
$app->run();
```

最初にコンテナの設定が読み込まれています。

# config/container.php

```php
use Zend\ServiceManager\Config;
use Zend\ServiceManager\ServiceManager;

// Load configuration
$config = require __DIR__ . '/config.php';

// Build container
$container = new ServiceManager(new Config($config['dependencies']));

// Inject config
$container->setService('config', $config);

return $container;

```

設定ファイルを読み込みます。
アプリケーションの実行に必要な設定情報やクラスの依存関係などがここで読み込まれます。

config.phpではconfig/autoload配下の設定ファイルを対象に設定値を読み込んでいます。

```php
foreach (Glob::glob('config/autoload/{{,*.}global,{,*.}local}.php', Glob::GLOB_BRACE) as $file) {
    $config = ArrayUtils::merge($config, include $file);
}
```

Zend\ServiceManager\ServiceManagerのインスタンスを生成し、
読み込んだ設定を**config**というサービス名でServiceManagerに登録します。

ServiceManagerインスタンス生成時にZend\ServiceManager\Configクラスの
configureServiceManagerメソッドが実行されます。
ここでは設定ファイルのそれぞれの値をコンテナが管理するための処理が行われます。

## Zend\ServiceManager\Config
例えば設定値の配列に'factories'キーがあれば以下の処理が実行されます。

```php
foreach ($this->getFactories() as $name => $factory) {
    $serviceManager->setFactory($name, $factory);
}
```

config/autoload/dependencies.global.phpを例とすると、

```php
'factories' => [
    Application::class => ApplicationFactory::class,
    // 省略
],
```

下記のように登録されます。

```
  #canonicalNames: array:1 [▼
    "Zend\Expressive\Application" => "zendexpressiveapplication"
  ]
  #allowOverride: false
  #invokableClasses: []
  #factories: array:1 [▼
    "zendexpressiveapplication" => "Zend\Expressive\Container\ApplicationFactory"
  ]
```

最後にエントリポイントでServiceManagerインスタンスを返却します。
このZend\ServiceManager\ServiceManagerクラスは**Interop\Container\ContainerInterface**を実装したクラスで、
expressiveで利用するコンテナは、
この**Interop\Container\ContainerInterface**を実装したコンテナライブラリであれば動作するようになっています。

そのため、phpdocにはServiceManagerではなくインターフェースが記述されています。

```php
/** @var \Interop\Container\ContainerInterface $container */
$container = require 'config/container.php';
```

# アプリケーションインスタンスの取得
index.phpにある下記の行でzend-expressiveのアプリケーションインスタンスを取得します。

```php
$app = $container->get('Zend\Expressive\Application');
```

ここでは**Interop\Container\ContainerInterface**を実装した
Zend\ServiceManager\ServiceManagerクラスのgetメソッドが利用されています。

getメソッドはコンテナへの登録方法によってインスタンス生成の処理が異なりますが、
ファクトリの`Zend\Expressive\Container\ApplicationFactory`クラスが
Zend\Expressive\Applicationクラスのインスタンスを生成して返却します。

```php
    public function __invoke(ContainerInterface $container)
    {
        $router = $container->has(RouterInterface::class)
            ? $container->get(RouterInterface::class)
            : new FastRouteRouter();

        $finalHandler = $container->has('Zend\Expressive\FinalHandler')
            ? $container->get('Zend\Expressive\FinalHandler')
            : null;

        $emitter = $container->has(EmitterInterface::class)
            ? $container->get(EmitterInterface::class)
            : null;

        $app = new Application($router, $container, $finalHandler, $emitter);

        $this->injectPreMiddleware($app, $container);
        $this->injectRoutes($app, $container);
        $this->injectPostMiddleware($app, $container);

        return $app;
    }
```

# アプリケーションの実行
エントリポイントのrunメソッドがアプリケーションを実行します。

```php
    public function run(ServerRequestInterface $request = null, ResponseInterface $response = null)
    {
        $request  = $request ?: ServerRequestFactory::fromGlobals();
        $response = $response ?: new Response();

        $response = $this($request, $response);

        $emitter = $this->getEmitter();
        $emitter->emit($response);
    }

```

このZend\Expressive\ApplicationクラスはZend\Stratigility\MiddlewarePipeクラスを拡張したもので、
フレームワーク全体がミドルウェアによって実行されるように作られています。

# コンテナを変更
処理の流れをみると
**Interop\Container\ContainerInterface**を実装しているコンテナライブラリであれば、
変更できることがわかると思います。
早速 [league/container](http://container.thephpleague.com/) に差し替えてみます。

config/container.phpを下記のように変更します。
ファクトリクラスでは `__invoke()`メソッドが定義されていますので、
ここでは__invokeを実行してインスタンスを返却するようにしています。
それ以外は単純なインターフェースと具象クラスのバインドです。

```php
$config = require __DIR__ . '/config.php';

$container = new League\Container\Container;

$container->delegate(
    new League\Container\ReflectionContainer
);

$dependencies = $config['dependencies'];
foreach($dependencies['invokables'] as $abstract => $invoke) {
    $container->add($abstract, $invoke);
}

foreach($dependencies['factories'] as $class => $factory) {
    $container->add($class, function () use ($factory, $container) {
        $factory = (new $factory);
        return $factory($container);
    });
}

$container->add('config', $config);

return $container;

```

これでアプリケーション実行の準備が整いました。
実際にコンテナを入れ替えたアプリケーションを実行してみましょう。
config/autoload/routes.global.phpに下記のルートを追記します。

```php

    'routes' => [
        [
             'name' => 'home',
             'path' => '/',
             'middleware' => App\Action\SimpleAction::class,
             'allowed_methods' => ['GET'],
        ],
    ],
```

つぎにapp\Action\SimpleActionクラスを作成します。

```php
<?php
declare(strict_types = 1);

namespace App\Action;

use App\Domain\MessageService;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ServerRequestInterface;

/**
 * Class SimpleAction
 */
class SimpleAction
{
    /**
     * @param ServerRequestInterface $request
     * @param ResponseInterface      $response
     * @param callable|null          $next
     * @return ResponseInterface
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        callable $next = null
    ) : ResponseInterface
    {
        return new JsonResponse(['message' => 'zend-expressive']);
    }
}


```

ブラウザなどからこのルートにアクセスしてみましょう。
jsonでレスポンスが返却されます。

最後に、このクラスで簡単なコンストラクタインジェクションを利用してみます。


```php
<?php
declare(strict_types = 1);

namespace App\Service;

/**
 * Class MessageService
 */
class MessageService
{
    /**
     * @return string
     */
    public function get() : string
    {
        return 'zend-expressive';
    }
}

```

SimpleActionクラスを少しだけ変更します。

```php
class SimpleAction
{
    /** @var MessageService */
    protected $service;

    /**
     * SimpleAction constructor.
     *
     * @param MessageService $service
     */
    public function __construct(MessageService $service)
    {
        $this->service = $service;
    }

    /**
     * @param ServerRequestInterface $request
     * @param ResponseInterface      $response
     * @param callable|null          $next
     * @return ResponseInterface
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        callable $next = null
    ) : ResponseInterface
    {
        return new JsonResponse(['message' => $this->service->get()]);
    }
}

```

簡単にコンストラクタインジェクションが利用できました。

PHPはコンポーネント化が進み、
フレームワークやライブラリを組み合わせて高品質なアプリケーション開発が行えるようになっています。
最初にあげたフレームワークやこのzend-expressiveのコードを読んで、
次世代フレームワークを学ぶのも非常に有益なのではないかと思います。