無名クラス(PHP7)をマイクロフレームワークで使ってみた
PHP7からは無名クラスが使えるようになりました。
どういった場合に無名関数を使う機会があるかなぁと考えていると、ふとマイクロフレームワークの無名関数部分の代わりに使うと良いんじゃないかと思いやってみましたというのがこの記事。
[追記]
- 実際にSlim3 のアプリで使えるようにcomposerでインストールできるようにコードを整理しなおして用意してみた。
- Slim3 ではなく、もっと薄いPHP7フレームワークを書き始めてみた記事はこっち。
無名関数 vs 無名クラス
また、PSR-7でリクエストとレスポンスのインターフェースが決められ、それに従ったフレームワークやミドルウェアが出てきています。そこで、PSR-7 のサポートをした Slim3 と PHP7 試してみます。
まずは、Slim3 のドキュメント通りに Hello World すると:
<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require __DIR__ . '/../vendor/autoload.php'; $c = new \Slim\Container; $c['greet'] = 'Hello '; $app = new \Slim\App($c); $app->get('/hello/{name}', function (Request $request, Response $response, $args) use ($app){ $response->write($app->getContainer()['greet'] . $args['name']); return $response; }); $app->run();
リクエストとレスポンスのオブジェクトが無名関数に渡されてくるので、レンダリングするときは Slimに用意されているレスポンスの `write` メソッドを呼びます。
シンプルで良いのですが、毎回 Request と Response を意識してコード書かなくても済むぐらいの抽象化が欲しくなります。そこで、MyControllerクラスを用意し、そこで RequestとResponseへの処理を行うようにしてみたのが以下のコード:
<?php ... $app->get('/hello/{name}', new class($app) extends MyController { public function action($args) { return $this->render($this->container['greet'] . $args['name']); } });
MyControllerを抽象クラスとして定義し、かならずactionメソッドが呼ばれるようにしてあります。
なので、この無名クラスでは actionメソッドを書くことで各ルーティングごとに処理が行われるという仕組みです。
また、レスポンスオブジェクトは直接さわらずrenderメソッドを用意して動作するようにしてみました。
これでマイクロフレームワークで無名関数では大変だった抽象化も簡単に行えますし、ある意味オレオレフレームワークが簡単にできます。
マイクロフレームワークだけではルーティング数が増えてくると無名関数だけでは実装が難しくなってくる部分を、毎回クラスを定義し`__invoke`を利用する方式ではなく、ある程度無名クラスでゆるく書けるという方式はメリットがありそうです。
Twigテンプレートを使うためにTraitで
Slim3 は Pimpleベースのサービスコンテナがあるので、テンプレートエンジン(twig)を `view` に以下のようにセットすることで
<?php $app = new \Slim\App(); // Get container $container = $app->getContainer(); // Register component on container $container['view'] = function ($container) { $view = new \Slim\Views\Twig('path/to/templates', [ 'cache' => 'path/to/cache' ]); $view->addExtension(new \Slim\Views\TwigExtension( $container['router'], $container['request']->getUri() )); return $view; };
ルーティングで以下のようにviewを使ってレンダリングができます。
<?php $app->get('/hello/{name}', function ($request, $response, $args) { return $this->view->render($response, 'profile.html', [ 'name' => $args['name'] ]); });
これも、トレイトを使って違ったアプローチで使えるようにしてみました。
トレイトを用意して、そこでTwigの設定関連を書いてしまいます。
サービスコンテナは利用せず直接 viewプロパティを用意しています。
- TwigTemplatable.php
<?php namespace Karen\Controller; use \Psr\Http\Message\ResponseInterface as Response; trait TwigTemplatable { private $templatePath; private $cachePath; private $view; public function useTwig() { $this->templatePath = __DIR__ . '/../../templates/'; $this->cachePath = '/tmp/'; // Register component on container $this->view = new \Slim\Views\Twig($this->templatePath, [ 'cache' => $this->cachePath ]); $this->view->addExtension(new \Slim\Views\TwigExtension( $this->container['router'], $this->container['request']->getUri() )); } public function renderTwig($path, $args) { $this->useTwig(); $this->view->render($this->response, $path, $args); } }
これをルーティングのMyControllerでuseし、renderTwigを通してレンダリングします。
<?php $app->get('/hello/{name}', new class($app) extends MyController { use TwigTemplatable; public function action($args) { return $this->renderTwig('web.html', ['name' => $args['name']]); } });
アプリケーションが大きくなるとコンテナ自身が膨れていく問題も無名クラス + トレイトを利用することである程度うまく整理できそうな感じがします。が、ここまでやらなくてもなぁとも思ったり。
試してみる
PHP7がインストールされていればgithubからコード持ってくれば色々遊べます。
$ git clone git@github.com:brtriver/slim3-anonymous-class.git $ make setup $ make install $ php -S localhost:8888 -t ./web // あとはブラウザから http://localhost:8888/hello/brtriver にアクセスすればOK