zend-expressiveに見るPSR-7時代のPHPフレームワーク

Zendによる新しいマイクロフレームワーク、zend-expressiveのバージョン1.0.0rc2がリリースされました。

今日は、このPSR-7対応のzend-expressiveをインストールしてみます。

zend-expressiveのインストール

最も簡単なインストール方法である、スケルトンプロジェクトをComposerでインストールする方法でインストールします。

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

ルータの選択

まず、以下のようにルータをどれにするか聞かれます。

Installing zendframework/zend-expressive-skeleton (1.0.0rc2)
  - Installing zendframework/zend-expressive-skeleton (1.0.0rc2)
    Loading from cache

Created project in ze
> App\Composer\OptionalPackages::install
Setup data and cache dir
Setting up optional packages

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

デフォルトはFastRouteですが、Aura.RouterやZend Routerも選べます。

とりあえず、デフォルトのまま [return] キーを押して進みます。

  Make your selection or type a composer package name and version (FastRoute): 
  - Adding package zendframework/zend-expressive-fastroute (^0.2)
  - Copying /config/autoload/routes.global.php

zendframework/zend-expressive-fastrouteが追加されました。

DIコンテナの選択

続いてDIコンテナをどれにするか聞かれます。

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

デフォルトはZend ServiceManagerですが、Aura.DiとPimple-interopが選べます。

デフォルトのまま [return] キーを押して進みます。

  Make your selection or type a composer package name and version (Zend ServiceManager): 
  - Adding package zendframework/zend-servicemanager (^2.5)
  - Adding package ocramius/proxy-manager (^1.0)
  - Copying /config/container.php

zendframework/zend-servicemanagerなどが追加されました。

テンプレートエンジンの選択

次はテンプレートエンジンの選択です。

  Which template engine 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): 

デフォルトはなしですが、Plates、Twig、Zend Viewが選択できます。

今回はデフォルトのなしにします。

エラーハンドラの選択

開発時に使用するエラーハンドラの選択です。

  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): 

Whoopsしか選択肢はないので、Whoopsにしておきます。

  Make your selection or type a composer package name and version (Whoops): 
  - Adding package filp/whoops (^1.1)
  - Copying /config/autoload/errorhandler.local.php

filp/whoopsが追加されました。

そして、パッケージのインストールが開始されます。

インストールされたパッケージは以下でした。

$ composer show -i
composer/composer                       1.0.0-alpha10     
container-interop/container-interop     1.1.0             
doctrine/instantiator                   1.0.5             
filp/whoops                             1.1.7             
justinrainbow/json-schema               1.5.0             
nikic/fast-route                        v0.6.0            
ocramius/proxy-manager                  1.0.2             
phpdocumentor/reflection-docblock       2.0.4             
phpspec/prophecy                        v1.5.0            
phpunit/php-code-coverage               2.2.4             
phpunit/php-file-iterator               1.4.1             
phpunit/php-text-template               1.2.1             
phpunit/php-timer                       1.0.7             
phpunit/php-token-stream                1.4.8             
phpunit/phpunit                         4.8.15            
phpunit/phpunit-mock-objects            2.3.8             
psr/http-message                        1.0               
roave/security-advisories               dev-master 43ac5a3
sebastian/comparator                    1.2.0             
sebastian/diff                          1.3.0             
sebastian/environment                   1.3.2             
sebastian/exporter                      1.2.1             
sebastian/global-state                  1.1.1             
sebastian/recursion-context             1.0.1             
sebastian/version                       1.0.6             
seld/jsonlint                           1.3.1             
squizlabs/php_codesniffer               2.3.4             
symfony/console                         v2.7.5            
symfony/finder                          v2.7.5            
symfony/process                         v2.7.5            
symfony/yaml                            v2.7.5            
zendframework/zend-code                 2.5.2             
zendframework/zend-diactoros            1.1.4             
zendframework/zend-escaper              2.5.1             
zendframework/zend-eventmanager         2.5.2             
zendframework/zend-expressive           1.0.0rc2          
zendframework/zend-expressive-fastroute 0.2.0             
zendframework/zend-hydrator             1.0.0             
zendframework/zend-servicemanager       2.6.0             
zendframework/zend-stdlib               2.7.4             
zendframework/zend-stratigility         1.1.2   

このようにzend-expressiveでは、ルータ、DIコンテナ、テンプレートエンジン、開発時のエラーハンドラが最初から選択可能になっています。

このフレームワークを使っているからルータはフレームワークに付属しているものしか使えないなどというバカバカしいことはありません。

Webサーバの起動

PHPビルトインWebサーバを起動してみましょう。

$ php -S 127.0.0.1:8080 -t public/

http://localhost:8080/にブラウザでアクセスすると、以下のJSONを見ることができます。

{"welcome":"Congratulations! You have installed the zend-expressive skeleton application.","docsUrl":"zend-expressive.readthedocs.org"}

さて、この値はどこから来ているのでしょう?

ルーティング

ルーティングの設定は、以下にありました。

config/autoload/routes.global.php

<?php

return [
    'dependencies' => [
        'invokables' => [
            Zend\Expressive\Router\RouterInterface::class => Zend\Expressive\Router\FastRouteRouter::class,
        ],
    ],

    'routes' => [
        [
            'name' => 'home',
            'path' => '/',
            'middleware' => App\Action\HomePageAction::class,
            'allowed_methods' => ['GET'],
        ],
        [
            'name' => 'api.ping',
            'path' => '/api/ping',
            'middleware' => App\Action\PingAction::class,
            'allowed_methods' => ['GET'],
        ],
    ],
];

キーroutesの最初の配列

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

がさきほどのJSON出力のルートです。クラス名がApp\Action\HomePageActionであることがわかります。

アクション

App\Action\HomePageActionクラスは以下にあります。

src/Action/HomePageAction.php

<?php

namespace App\Action;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Router;
use Zend\Expressive\Template;

class HomePageAction
{
    private $router;

    private $template;

    public function __construct(Router\RouterInterface $router, Template\TemplateRendererInterface $template = null)
    {
        $this->router   = $router;
        $this->template = $template;
    }

    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
    {
        $data = [];

        if ($this->router instanceof Router\AuraRouter) {
            $data['routerName'] = 'Aura.Router';
            $data['routerDocs'] = 'http://auraphp.com/packages/Aura.Router/';
        } elseif ($this->router instanceof Router\FastRouteRouter) {
            $data['routerName'] = 'FastRoute';
            $data['routerDocs'] = 'https://github.com/nikic/FastRoute';
        } elseif ($this->router instanceof Router\ZendRouter) {
            $data['routerName'] = 'Zend Router';
            $data['routerDocs'] = 'http://framework.zend.com/manual/current/en/modules/zend.mvc.routing.html';
        }

        if ($this->template instanceof Template\PlatesRenderer) {
            $data['templateName'] = 'Plates';
            $data['templateDocs'] = 'http://platesphp.com/';
        } elseif ($this->template instanceof Template\TwigRenderer) {
            $data['templateName'] = 'Twig';
            $data['templateDocs'] = 'http://twig.sensiolabs.org/documentation';
        } elseif ($this->template instanceof Template\ZendViewRenderer) {
            $data['templateName'] = 'Zend View';
            $data['templateDocs'] = 'http://framework.zend.com/manual/current/en/modules/zend.view.quick-start.html';
        }

        if (!$this->template) {
            return new JsonResponse([
                'welcome' => 'Congratulations! You have installed the zend-expressive skeleton application.',
                'docsUrl' => 'zend-expressive.readthedocs.org',
            ]);
        }

        return new HtmlResponse($this->template->render('app::home-page', $data));
    }
}

__invoke()メソッド内になんかごちゃごちゃif文がありますが、今はテンプレートエンジンがないので、結局以下のif文だけが実行されていることがわかります。

        if (!$this->template) {
            return new JsonResponse([
                'welcome' => 'Congratulations! You have installed the zend-expressive skeleton application.',
                'docsUrl' => 'zend-expressive.readthedocs.org',
            ]);
        }

テンプレートエンジンをインストールした方がページの見栄えがよかったみたいですね。

まとめ

PHPはPSR-7時代に突入しました。フレームワークの抽象化、コンポーネント化はさらに進みます。すでにルータやDIコンテナさえもこのように組み替え可能です。

参考

Date: 2015/10/23

Tags: php, psr7