Symfonyの歴史を振り返ってみる

こんにちは。Symfony Advent Calender 2019 の 7日目です。

(すでに8日目になってしまいましたが気にせず書いていきます。)

 

昨日は 【初心者向け】よく使うSymfonyコマンド集でした。自分がよく使うコマンドは debug:router かな。コマンド多くて便利なところは Symfony の特徴ですよね。

 

自分がSymfonyと関わりはじめてから約12年ぐらいたちました。
今日は Symfony の懐かしい思い出話をしてみたいと思います。

 

connect.symfony.com

Symfonysymfony だった

 2007年1月。symfony1がリリースされました。

 2系からは大文字になりましたが、1系のころはsymfonyと小文字でしたね。そんなsymfonyを初めてしった理由はその当時の所属していた会社で使っていた共通のフレームワークMojavi だったのですが、もっと使いやすものを探しているときでした。まだ0.8ぐらいのバージョンだったと思います。

 結果としてPHP5が必須だったsymfonyは多くの種類のクライアントを抱えている職場には厳しくCakePHPに決めたのですが、symfonyのドキュメントの多さ、充実性に惹かれて「これはみんなに知ってほしい」と勝手にドキュメントを翻訳し公開してました。

 その当時は「おやぢ組」という名前のwikiに書いてましたね。。

 (ぐぐってみると自分が書いていたものと同じっぽいミラーサイトが出てきますが、自分が書いたものかどうかわからないのでリンク貼るのは控えておきます)

  バグを見つけたりするとsymfonytracに投げてたりしてましたね。。 (なので12年の繋がりなのですが)

 海外のOSSということもあり、JISでメールするのも大変だったりするのでプラグインを書いたりもしてました。

 そうしていると、周りからは symfony の人として知られるようになっていきました。そして出版社から「symfonyの本を書いてみないか?」と提案を頂き本を書いてみることにしました。たしか書き終わるぐらいの直前に1.0がリリースされて、すべてのキャプチャを動作確認をやり直した記憶があります。。。アシアルさんから本が出たのと大体同じだった記憶です。

 

 

www.amazon.co.jp

 この当時は国内では CakePHP, symfony, Ethna, Maple, Mojavi, ZendFramework あたりがメジャーどころだった気がします。

 

 ちなみにsymfonyはPHP5用MojaviであるMojavi3のforkとしてスタートしたことを知ってる人は少ないですよね。

 

第一回symfony勉強会開催

2008年にやってました。懐かしい...

events.php.gr.jp

ファビアン来日

 そんな2009年のPHPカンファレンスで開発者であるファビアンが来日することになり、その当時にsymfonyを使っていた人たちが集まってファビアンと東京観光しました

 

innx-hidenori.hatenadiary.org

 地下鉄で電車待ってるときにファビアンに「なんでPHPフレームワーク書いたの?」って聞いたら「本当はPythonが好きなんだけどね」とか当時5.3がリリースされたぐらいの時期で「名前空間のセパレータがバックスラッシュなの嫌だ」とか言ってた記憶。

 その人の夜のごはんを中華料理屋で食べているときに、とつぜんファビアンが当時開発してた Syfmony2 のコンセプトをプレゼンしてくれて、「DIだ」と熱く語っていたのを思い出します。

 

日本 Symfony ユーザー会の設立

そして、このときにファビアンさんから、「今年の年末のアドベントカレンダーの内容をいろんな言語で同時に出したい。日本語をやってみないか」と声をかけられ、東京観光していたメンバーでやってみよう!ということで集まったのがユーザー会の最初でした。

 

 それが2009年に発表された 「More with Symfony」でした

symfony.com

brtriver.hatenadiary.org

 ファビアン自身も書いてますが、PHPカンファレンスに来日したことがきっかけなのでほんと凄い話ですよね。

I started to talk about the idea during the PHP conference in Japan, and in a matter of hours the Japanese translation team was ready to work. That was amazing! The response from the authors and translators was equally encouraging and, in a short time, "More with symfony" was born.

 MLでやりとりしながら、本家の記事がなかなか上がってこなくて翻訳間に合わないんじゃないかとか、本家の記事の方に間違いがあるのを指摘したり... と大変だったなぁと。

 

symfony1.2で Propel から Doctrineに

 当初のsymfonyのORMはPropelでした。しかし使いやすさという点で Doctrineを利用する人が増えてきたこともあり、symfonyの標準ORMがDoctrineに変わりました。このころは、どちらのORMを使うか指定できた記憶。ちなみに今あるPropelはその当時のPropelとは別物ぐらいに進化してしまっています。

propelorm.org

symfony1.4 で一区切り

ユーザー会ではドキュメントの翻訳したり勉強会をしたりしていましたが、1系の最後のバージョンは長く使われることも考えられたので、ユーザー会のメンバーで自分たちが一番欲しい本を書こう!ということで2011年3月に出版しました。

 

https://www.amazon.co.jp/dp/4798029343

 

Symfony2 はエコシステム志向

 2011年7月についにSymfony2がリリースされます。

 Symfony2 は symfonyと何が違うのか?でいくと本当に違うフレームワークになっていました。バンドルによるフレームワークそのものもバンドルの1部であるという考え方。リクエストを受けレスポンスを返す。という点に注力だけするフレームワークであること。DIの導入。

 

symfonyでダメだったところを完全にゼロから設計しなおすところはファビアンらしいなぁと思いました。その一方で既存のアプリケーションのフレームワークのバージョンアップがほぼ書き換えになってしまうという点は他方から辛いという声が上がってました。それでも過去を引きづらずに書き直せたことが今につながっているんだと思います。

 

Silex

 Symfony2ではコンポーネントによる組み合わせだという点を利用して、マイクロフレームワークであるSilexが作られたのもこのころでした。決して軽量ではないけどシンプルに書ける設計に感動し、これはみんなに知ってほしいとまたドキュメントを翻訳したりWEB DB PRESSに書いたりしていたのを思い出します

 

brtriver.hatenadiary.org

Symfony2系から3系に

1系の頃に比べると緩やかにでも確実に進化していきました。バージョンアップも多少の辛さはあっても絶対無理とまではいかないレベルで進化していきます。

 

とくにコンポーネントがより洗練されて機能が増えたり使いやすくなっていきます。そして疎結合すぎてひたすら当たり前のこともyamlに書かなくてはならなかったDIがautowiringの登場で革命的に楽になったり、ParamConverterで便利になったりと...

 

そしてLaravelの人気が国内で広がっていったのもここ数年のことですよね。Symfonyコンポーネントを利用した別の表現のフレームワークという意味ではSilexと兄弟かもしれませんね。

 

Symfony Flex

Symfony4ではさらにSymfony Flexが用意されました。いわゆるインストーラーの機能であり使いたい機能を追加したり、satandardエディションを利用する。みたいなことが簡単にできるようになりました。

 

フレームワークそのものというよりは環境面のサポートが充実してきたイメージですね。

 

Symfony5

大きく変わったというところはまだわかりませんが、ファビアンのことなのできっと色々考えているでしょう。Symfony5の本はチュートリアルじゃなくて実践的な内容になると言っているので今後もゆっくりと追っていきたいと思います。

 

www.kickstarter.com

 

次の記事もぜひ読んでね!

qiita.com

 

 

 

 

 

 

管理画面に汎用的で便利すぎる機能追加はもしかすると危険かもしれない

 以前、PHPカンファレンスで運用しやすい気づける管理画面という発表*1しましたが、今もこの考え方を大事にしながら日々サービスの運用、開発をしています。 

 その中で、最近思ったことがあったのを社内kibelaにポエムってあったのですが、普通に公開できる内容だったのでここにちょっと加筆して投下しておきます。


 サービスに大きく影響与えるような大事な情報ってありますよね。たとえば広告配信サービスだと広告の単価設定です。そのような項目の設定作業を画面からぽちぽち1つずつ変更するのは大変なのでCSVで一括で変更できる機能をいい感じに開発すると喜ばれるという場面は管理画面を開発している人にはよくある話だと思います。そして、「この項目も設定できるようにしたい」という声がでてきて、CSVからいろんな項目が設定できる便利機能へと発展していったりすることも。

 しかし人間はミスをするものです。一括変更のため変更されるデータが多いので、もし設定を間違って設定してしまったときに気づくのが遅れる可能性もありますよね。そしてその結果サービスに大きな影響を与えることも。

 ただ欲しいと言われたから作るのではなく「その機能って本当に必要か?」をエンジニア自身が意識するのは普通に大事なことですが、「この機能って汎用すぎてなんでもできて事故につながるんじゃないか?」や「間違えて設定したら大きな事故になりそうだけど、そのときってどういう情報を後から知りたいか?」みたいな視点も大事かなと。そして安全に使える仕組みを考えて皆で作っていくことが大事かなと。

 このような意見はエンジニアだから言える部分もあると思う。エンジニアからどんどん提案していきましょう。そういうエンジニアがいるチームは強いと思う。

シンプルな PHP7マイクロフレームワーク Karen

この記事は PHP7 で PSR-7 と Middleware を使うマイクロフレームワークを書いてみた の続編です。

コードは https://github.com/brtriver/karen

前回までの記事の流れをざっくりと書くと

  • Slim3 が PSR-7 と Middleware を採用していたので、PHP7の無名クラスを使ってみた
  • もっと薄いものほが欲しくなり PHP7で PSR-7 と Middleware を使ったマイクロフレームワークを作ってみた(Karen v0.1)

ただ、薄く作りすぎてエンドポイントのコードに色々書かなくてはならなくなって、それは見通しが悪くなったのでもう少し整理しがっつり書いてみた(v0.2)

Karen を使ったコード

解説はあとでやりますが

<?php
$app = new YourFramework(); // Karenアプリケーションを拡張したアプリケーション
$app->run();
$app->sendResponse();

のように書いたり、PHP7なので、無名クラスを利用して

<?php
require __DIR__ . '/../vendor/autoload.php';

$app = new class extends Karen\Framework\Karen {
        public function action($map)
        {
            // hello name controller sample.
            $map->get('hello', '/hello/{name}', function($args, $controller) {
                $name = $args['name']?? 'karen';
                return $controller->render('[Karen] Hello, ' . $name);
            })->tokens(['name' => '.*']);

            return $map;
        }
    };

$app->run();
$app->sendResponse();

のように書くことができます。

Karen の構成

Applicationのインターフェースを用意しました。インターフェースでは

  • 何かしらのサービスコンテナ(DI)を構築する
  • 何かしらのMiddlewareを定義する
  • 何かしらのルーティングからリクエストにマッチするルーティングと処理を決定する
  • 定義したMiddlewareと決定したルーティングの処理(コントローラー)を行う

というルールだけを決めています。
そして、これらは`$app->run()` を呼ぶことで順番にメソッドが呼ばれ、構築されたアプリケ−ションから最後に `$app->sendResponse()` を叩くことで何かしらのレスポンスが返されます。

いわゆるテンプレートメソッドパターンになっていて、こんな感じです。

<?php
abstract class Application
{
   ....
    public function run()
    {
        $this->container();
        $this->middleware();
        $this->route();
        $this->response();
    }
}

そして、Karen はこのApplicationインターフェースを実装したApplicationクラスをベースに、

  • サービスコンテナに Pimple
  • Middleware のライブラリに Relay
  • ルーティングにAura.Router (Karen2のサンプルではFastRoute)

を使うように実装しています。

実際はルーティングにマッチした場合の処理は書かれていないので、このKarenクラスを拡張する必要があります。
これが、最初に書いた独自アプリケーションクラスを使ったコードや、無名クラスを使ったコードになります。

Karen のコントローラー

Karen ではアプリケーションから responseメソッドをコールしたときにルーティングに定義されたcallableなものをMiddlewareをとおして実行されます。この責務をコントローラーにまかせています。そして、アプリケーションで利用するコントローラーを差し替える事を可能にしています。

通常は、containerメソッドでpimple($c)に以下のようにコントローラーを突っ込むだけですが、

<?php
$c['controller'] = new Controller();

Twigテンプレートを使いつつ、そのためのメソッド(renderWithT) を使えるように拡張したものを定義するためには

<?php
$c['controller'] = function($c) {
    $controller = new class extends Controller{
            use Templatable;
        };
    $controller->setTemplate($c['template']);

    return $controller;
};

のようにControllerクラスをTraitを利用したクラスに無名関数で拡張するだけでOKです。PHP7便利ですね。

もちろん独自のコントローラーを定義することもできますし、機能を追加したいのであれば上のようにTraitを用意すれば代替事足りるかもしれません。

コントローラーはルーティングに一致したときに呼ばれるcallableなものを把握していますが、このクロージャーは引数として $args と $controller を受け取ります。
$argsはパスで定義され取得されたパラメータが入っていて、名前をkeyとしてアクセスできます。
また、$controllerはコントローラークラス自身です。RequestとResponseには $controller->request, $controller->responseでアクセスできます。
もちろん、このReuqestとResponseは Middleware が適用された後のオブジェクトが入ってきます。

あとは Middleware の仕様に従って $response を返すようにします。

<?php
        $map->get('hello', '/hello/{name}', function($args, $controller) {
            $name = $args['name']?? 'karen';
                return $controller->render('[Karen] Hello, ' . $name);
            })->tokens(['name' => '.*']);

Traitで追加したメソッドなども$controllerを通して呼び出すことができます。

<?php
            // with twig
            $map->get('render_with_twig', '/template/{name}', function($args, $controller) {
                return $controller->renderWithT('demo.html', ['name' => $args['name']]);
            });

また、Jsonのレスポンスを返したい場合はreturn がJsonResponseになっていればいいので

<?php
$map->get('json', '/json/{name}', function($args, $controller) {
    return new \Zend\Diactoros\Response\JsonResponse(['name' => $args['name']]);
});

のようにすれば、まぁできます(ただし、Middlewareで適用されてきた $controller->response を破棄しちゃいますが)

Karenを利用してオレオレフレームワークの作る

で、Karen は Applicationインターフェースを実装したテンプレートパターンに従った何かに過ぎないので、このパターンに従ってさえすれば好きなものを書けばいいと思います。

途中で違うライブラリ(コンポーネント)に差し替える.. なんてことあんまりやらないと思うので、最初に使いたいコンポーネントをある程度決めて書いてしまうとかでいいんじゃないでしょうか。

たとえば、サンプルとして Aura.Router ではなく FastRoute を使う Karen2 を作る場合は、routeメソッドが代わり、route結果を使うresponseメソッドもそれに伴って書き換えるんですが、それだけであとの処理は同じです。

  • Aura.Router 版
<?php
class Karen extends Application
{
    ....
    public function route()
    {
        $map = $this->c['router']->getMap();
        // define routes at an action method in an extended class
        $map = $this->action($map);
        $this->route = $this->c['router']->getMatcher()->match($this->request);
    }
    public function response()
    {
        if (!$this->route) {
            $response = $this->response->withStatus(404);
            $response->getBody()->write('not found');
            return;
        }
        // parse args
        $args = [];
        foreach ((array)$this->route->attributes as $key => $val) {
            $args[$key] = $val;
        }
        // add route action to the queue of Midlleware
        $this->addQueue('action', $this->c['controller']->actionQueue($this->route->handler, $args));
    }
}
  • FastRoute 版
<?php
class Karen extends Application
{
    ....
    public function route()
    {
        $this->c['handlers'] = function () {
            return $this->handlers();
        };
        $dispatcher = $this->c['dispatcher'];
        $this->route = $dispatcher->dispatch($this->request->getMethod(), $this->request->getUri()->getPath());
    }
    public function response()
    {
        switch ($this->route[0]) {
            case \FastRoute\Dispatcher::NOT_FOUND:
                echo "Not Found\n";
                break;
            case \FastRoute\Dispatcher::FOUND:
                $handler = $this->route[1];
                $args = $this->route[2];
                $this->addQueue('action', $this->c['controller']->actionQueue($handler, $args));
                break;
            default:
                throw new \LogicException('Should not reach this point');
        }
    }
}

ちなみに、ローカルでベンチ取ると、圧倒的に FastRoute 速いです。

Middleware ライブラリを導入してみる

Karen は Middleware を持っているので、psr7-middlewares を簡単に使えます

composer require oscarotero/psr7-middlewares

でインストールすれば、エンドポイントのコードでmiddlewareをqueueに追加するだけです

<?php
require __DIR__ . '/../vendor/autoload.php';

$app = new class extends Karen\Framework\Karen {
        // middleware を追加する
        public function middleware()
        {
            $this->addQueue('responseTime', Psr7Middlewares\Middleware::responseTime());
        }

        public function action($map)
        {
            // hello name controller sample.
            $map->get('hello', '/hello/{name}', function($args, $controller) {
                $name = $args['name']?? 'karen';
                return $controller->render('[Karen] Hello, ' . $name);
            })->tokens(['name' => '.*']);

            return $map;
        }
    };

$app->run();
$app->sendResponse();

これでレスポンスヘッダに処理時間を追加することができました。

X-Response-Time:8.789ms

便利。

Karen を作ってみて

  • 無名クラスはさくっとやるのには有りな場面はある。たとえばテストで無名クラスを使って呼び出し順序が正しいかどかのコードも書ける。
<?php
class ApplicationTest extends \PHPUnit_Framework_TestCase
{
    public function testRunOrder()
    {
        $app = new class extends Application{
                public $passed = '';
                public function container()
                {
                    $this->passed .= 'container->';
                }
                public function middleware()
                {
                    $this->passed .= 'middleware->';
                }
                public function route()
                {
                    $this->passed .= 'route->';
                }
                public function response()
                {
                    $this->passed .= 'response';
                }
            };
        $app->run();
        $this->assertSame('container->middleware->route->response', $app->passed);
    }
}
  • Middleware に乗っかておけば、色んなライブラリがそのまま使えるメリットは大きい。たとえばpsr7-middlewares
  • PSR-7 に準拠したRequestやResponseってそんなに無いし、差し替えたくなる場面って思い浮かばない。
  • ある程度準備されたフレームワークのほうがオレオレフレームワークより楽。
  • メソッドの戻りの型を定義できるので、ちゃんと落ちてくれるのは楽(ただし実行時)

PHP7 で PSR-7 と Middleware を使うマイクロフレームワークを書いてみた

コードはこちら。https://github.com/brtriver/karen

[追記]
この記事に書いてあるコードからさらに改良加えてApplicationレイヤーを作りました(v0.2)
詳しくはこっちの記事を参照をば
http://d.hatena.ne.jp/brtRiver/20160106/karen_framework

この記事の時点のコード(v0.1)を見たい場合は
https://github.com/brtriver/karen/tree/v0.1.3
からどうぞ。

なにこれ?

PSR-7が用意されてからコンポーネントを色々好きなのを選択できる時代が来つつあります。
たとえばzend-expressiveとか。

ただフレームワークががんばってこれらを抽象化しようとしてるのですが、もっとシンプルでもいいなぁと。

というわけで、コンポーネントをむき出しにして、ざぁーっと書いたらどうなるかをヤッてみたかったので書いただけという。

結果こんな感じに
https://github.com/brtriver/karen/blob/master/web/index.php

試してみたい人は

php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
php composer.phar create-project brtriver/karen  ./<パス>
cd <パス>
make server
// http://localhost:8888/hello/karen_girls

で。

Karenでやったこと

PSR-7 とMiddleware は自由に差し替えられるように

逆にいうと、サービスコンテナ(DI)、ルーティングはフレームワークとして蜜になっててもいいかなと。

フレームワークのコントローラーは薄く

ルーティングのクロージャーで少し書きやすくするためのControllerクラスとTemplatableトレイトだけを用意。

「こういう処理をやる」ということをラベル(コメント)ブロック付けた

スコープが閉じられるわけじゃないです。あくまでもラベル。
ただインラインコメントを残すよりもわかりやすかなと。

<?php
container: {
    $c = new Container();
    ....
}

middleware: {
     $relayBuilder = new RelayBuilder();
      ...
}
さすがにサービスコンテナ無いと辛そう

コンテナは Container Interop というのも用意されてたりしますが、まだ公式というほどでもないのと、好きなの使えばいいじゃんということで使い慣れた Pimpleを選択。

別のコンテナ使ったとしても、Karen程度のフレームワークであればそんなに書き換えるのは大変じゃないと思う。

ルーティングは Aura.Router

ルーティングはクロージャーが定義できればいいので、使ったことがある Aura.Routerで

<?php
...
    $map->get('hello', '/hello/{name}', function($args, $controller) {
        $name = $args['name']?? 'karen';
        return $controller->render('Hello, ' . $name);
    })->tokens(['name' => '.*']);

PHP7だと issetじゃなくて "??" 使えるの便利ですね。
KarenではControllerクラスを用意して、便利関数やRequest, Responseオブジェクトにクロージャーからアクセス出来るようにしてあります。

コントローラークラスの拡張を無名クラスでもできるように

必要があればControllerをextendsした専用のコントローラーを用意すればいいのですが、PHP7で無名クラス使えるので無名クラスでControllerを拡張できるようにしてみました。

たとえば通常は

<?php
...
$c['controller'] = function($c) {
	return new Controller($c['request'], $['response']);
};

と、コントローラーの用意しますが、
Templatable トレイトを利用できるようにしたControllerを用意したい場合は

<?php
...
$c['controller'] = function($c) {
    $controller = new class($c['request'], $c['response']) extends Controller{
            use Templatable;
        };
    $controller->setTemplate($c['template']);

    return $controller;
};

Middleware は Realy で

詳しくはRealyを使ってみた。
これって知ってる人は知ってる Mojavi のアレですよね。(懐かしい

書いてみてどうだったの?

  • まだもう少しコードを整理して書くつもり。
  • コード書くのが疲れたので、感想はいつかのLTのネタとして。

無名クラス(PHP7)をマイクロフレームワークで使ってみた

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メソッドを用意して動作するようにしてみました。

MyControllerのコード

これでマイクロフレームワークで無名関数では大変だった抽象化も簡単に行えますし、ある意味オレオレフレームワークが簡単にできます。

マイクロフレームワークだけではルーティング数が増えてくると無名関数だけでは実装が難しくなってくる部分を、毎回クラスを定義し`__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

もしくは、実際に Slim3 アプリケーションに組み込むなら、整理したこちらのパッケージをどうぞ

Loggerクラスをテストのたびにmockを用意しなくて済むための工夫

この記事は、Symfonyアドベントカレンダー2015の19日目の記事です。でも今日は20日ですね。(ごめんなさい。。忘れてました。。

18日はnaoyesさんのSymfonyプロジェクトのテストにCodeceptionを使ってみるでした。

今日は11/24 に開催された PHP BLT #1 でLTしてきた内容ですが、まだ発表内容をまとめていなかったので書きます。

Symfony では yaml でサービス構成の定義ができる

Symfony で 何かサービスを作るとき、必要な外部サービスを次のようにyamlで定義することができ、DI コンポーネントによって hoge_manager が用意されます。

    hoge_manager:
        class: HogeBundle\Service\HogeManager
        arguments:
            - @doctrine.orm.entity_manager
            - @report
            - @slack_notification
            - @loger

そして、HogeManager クラスはargumentsで定義した何かをコンストラクタで受け取るようにすればいいわけです。

<?php
/**
 * Hoge Manager
 */
class HogeManager
{
    public function __construct($em, $reporter, $slack, $logger)
    {
        $this->em = $em;
        $this->reporter = $reporter;
        $this->slack = $slack;
	$this->logger = $logger;
    }

コントローラーからは InjectParams が便利

また、このように名前をつけた何かは、コントロラーからなどは `InjectParams` を使えばコンストラクタなどで利用することもできます。

<?php

namespace HogeBundle\Controller\Hoge;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use JMS\DiExtraBundle\Annotation as DI;

/** @Route("/hoge") */
class HogeController extends Controller
{
    private $hoge;

    /**
     * @DI\InjectParams({
     *     "hoge" = @DI\Inject("hoge_manager")
     * })
     */
    public function __construct($hoge)
    {
        $this->hoge = $hoge;
    }

外部の何かを利用したクラスのテストはmockが活躍

さて、このHogeManager クラスのテストを書いてみましょう。このマネージャのテストを書きたいので、
外部の何かはmockにすることになります。Phakeを使うとこんな感じでしょうか

<?php
namespace HogeBundle\Tests\Service;

use HogeBundle\Service\HogeManager;

class HogeManagerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * setup
     */
    public function setUp()
    {
        $this->logger = \Phake::mock('Psr\Log\LoggerInterface');
    	...

ここで `$this->logger` を毎回用意しないといけなくなります。
もはや mock を毎回書かなくてもテスト書けるように慣れると幸せですよね。というわけで Loggable Trait を作ってみます。

Loggable Trait

<?php
namespace Hoge\HelperBundle\Logger;

use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Log\NullLogger;

trait Loggable
{
    /** @var LoggerInterface */
    private $logger;

    public function setLogger($logger)
    {
        $this->logger = $logger;
    }

    protected function logger()
    {
        if (is_null($this->logger)) {
            $this->logger = new NullLogger();
        }

        return $this->logger;
    }
}

コードはたったこれだけです。
loggerのsetterを用意しているのと、getterではsetされていなければNullLoggerを返しています。
なので、setされていなくてもエラーを履かずに動きます。

なので、yaml定義を以下のようにします。

    hoge_manager:
        class: HogeBundle\Service\HogeManager
        arguments:
            - @doctrine.orm.entity_manager
            - @report
            - @slack_notification
            - @loger
		calls:
			- [setLogger, ["@logger"]]

コンストラクタではなくsetLoggerをコールするように指定します。
HogeManager は Trait を使うので以下のようなコードに修正します

<?php
/**
 * Hoge Manager
 */
class HogeManager
{
use Loggable;
    public function __construct($em, $reporter, $slack)
    {
        $this->em = $em;
        $this->reporter = $reporter;
        $this->slack = $slack;
    }

コンストラクタでloggerを渡さなくてもよくなりましたし、logger関連のメソッドは全部Traitにあるのでuse Loggable するだけです。
これで、テストもmock作らなくてもNullLoggerが勝手に作成されるのでテストもすっきりします。

コンストラクタで渡す何かが増えすぎたら設計を考えなおす時期かもしれない

あと、このLoggableを使うとわかることは、そのサービス全体が依存しているものはコンストラクタで渡すのは自然で良いのですが、
サービスの一部だけが依存しているようなものまでもコンストラクタで渡すようにするとテストが辛いものになりやすくなります。

サービスの一部が利用するような外部の何かは、その一部で利用するメソッドで渡すなど別の手段を考えてみたほうが良いです。
Symfony では簡単に依存関係をコンストラクタで渡せるのでついついなんでも書いてしまいがちですが、
コンストラクタに大量に何か渡すような設計になってきたら、本当にそれはコンストラクタで渡すべきなのか、またはクラスの仕事が多すぎるなど設計を再度考える時期なのかもしれませんね。

というわけで、20日はimunewさんで「Doctrine DBALで取得したデータをキャッシュする」です!(既に公開されていますね!すばらしい!いや、ほんとごめんなさい。。
DBの問い合わせがWebアプリケーションで一番重くなることはよくあるので、キャッシュ大事ですよね!

勉強会で発表するときに役立つ正しいマイクの扱い方

Microphone

この記事はAdvent Calendar 2015 - VOYAGE GROUP 6日目の記事になります.

こんにちは!社内で音響エンジニアしてる @brtriver です。
今日はPA*1屋さん的視点でマイクの正しい扱い方についてお話できればなと思い、アドベントカレンダーで書くことにしました。


IT関連では勉強会も多く開催されており、勉強会で発表される機会がある方も多いと思います。
そのときに普段利用しないけど使うことが多いのがマイクです。


でも、そのマイクも正しく使えずに損をしている方がチラホラ見受けられます。
正しく使えることで、発表内容をより参加者に聞こえやすくなり伝えたい事もより伝わると思います。
ある意味カラオケでも活用できるネタですが色々書いてみます。

やってはいけないことを覚えるだけ

i can has hit record?

正しい持ち方を覚えるよりは、やってはいけないこと覚えると自然と良い持ち方になるということで
やっちゃダメなことについて説明してきます。

マイクが入っているかどうか確認するときにヘッド(マイクの頭の部分)を叩いてはいけない

Speak up!


マイクのヘッド部分を叩いて確認する人がいますが、あれはNGです。
マイクの音を拾う部分は衝撃に強くありません。最悪マイクが壊れてしまう可能性があります。
じゃあどうするか。ツメサキでヘッドの部分を "カリカリ" してみましょう。
それでスピーカーから聞こえていれば入っているということですね。

マイクをスピーカーに向けてはいけない

loudspeaker


これを説明するためには "ハウリング" (通称ハウる) について簡単に説明しておく必要があります。
ハウリングとは "ピーン" とか "キーン" とかなるアレのことです。
最悪スピーカーが壊れてしまう可能性があります。またお客さんも不快ですよね。
ハウリングは特定の音域が マイク => (増幅) => スピーカー => マイク => (増幅) => スピーカー とループすることで発生します。
色んな要因があるのですが、一番分かりやすく避ける方法はループしないようにすることです。
なので、マイクをスピーカーに向けるようなことをやってはいけません。
通常の勉強会だとスピーカーとマイクがそれほど近くにあるということは無いと思いますが知っておいてください。


また、勉強会でハウリングがよく発生するときは、スピーカーの位置と発表者のマイクの位置を調整しましょう。
もし、スピーカーが発表者の後ろにあるような場合は、スピーカーの位置より前に置くようにすることでハウリングは防げます。


あと、手にマイクを持って話しながら移動したりするときも、スピーカーの位置を把握しておいて
スピーカーにマイクを向けないようにしましょう。


ちなみに、よくライブやイベントの現場で "ワンツー、ワンツー..." といった発言している人達がいますが、
彼らはマイクチェックとモニターのスピーカーのチューニングをしているPAさんたちです。
"ワン" の部分で低い音域、 "ツー" の部分で高温域を発声してスピーカーがハウリングするポイントを探してるんですね。
ハウりやすい音域のレベルを下げたりしてます。

おっと、話がそれたので戻します。

マイクから離れすぎてはいけない

Andy with Mic 1


さきほどハウリングについて説明しました。
話す人がマイクから離れていると、スピーカーから聞こえるようにするためにスピーカーのボリュームを上げたり、
マイクの感度(ゲイン)を上げる必要があります。
そうすることで、周りの余計な音も増幅されてしまいハウリングしやすくなってしまいます。
なので、マイクにはできるだけ近づいた方が良いです。
どれほど近づいた方が良いかは地声の大きさなどもありますので色々探ってみてください。
少なくとも、マイクが遠くて声が小さいときにはマイクに近づくようにしましょう。
スピーカーのボリュームを上げるのはその次の手段です。


演台だとミニスタンドにマイクが設置されていることがありますよね。
自分は観客側を見て話したり、スクリーン側を見て話しをしたりすることがある場合は手に持って話すようにしています。
そうすることで、マイクとの距離を一定にして話すこともできますしUSTREAMなどの配信されている場合でも一定の音量で流れるからです。

マイクのヘッドの下半分を持たない

rap


マイクには指向性というものがあります。
発表者が使うマイクで手に持つようなタイプは大体単一指向性というマイクになると思います。
これはマイク正面の音を一番よく拾います。横からの音はほとんど拾わないような仕組みになっています。
そうすることで余計な音を拾わずにハウリングにも強くなっているのです。
が、マイクのヘッドの下半分を手で覆ってしまうと指向性がなくなり無指向性になってしまいます。
簡単にハウります。声質もモコモコとこもった感じになります。

参考正しいマイクの握り方


よくバンドマンがマイクのヘッド部分を持って歌っていたりしますが、全部覆わないような握り方をしているはずです。
とはいえ、勉強会ではそういうラッパー的な持ち方をされている方は見たことないので大丈夫だと思います。が。

突然ケーブルを抜かない

Standby


これはマイクに限らずですが、スピーカーのボリュームが上がった状態でケーブル抜くと場合によっては
"ボンッ" と大きなノイズが出ることがあります。
これは下手するとスピーカーを壊してしまいますのでケーブルは勝手に抜かないようにしましょう。

正しいマイクの扱い方

Man holding microphone


というわけでまとめると以下のようにマイクを使うとリスナーも聞きやすくて発表の感想が良くなるかもしれません。

  1. スピーカーの位置を確かめる。
    • スピーカーにはマイクを向けるとハウるので位置確認する
    • マイクを直接向けないように気をつける
  2. 演台からマイクを手に持つ
    • ラッパー持ちしない
  3. 爪でカリカリして繋がっているかを確認する
    • 叩かない
  4. できるだけ口に近づけて話す
    • 音が大きすぎるなと思ったらマイクを少し離して調整する
  5. 発表が終わったら演台に戻す
    • 勝手にケーブルを抜かない

なにはともあれ、出来る限り声は大きめではっきりと滑舌よく話せすことはマイクの使い方以前に大事です。はい。

おまけ

懇親会の乾杯の挨拶とかで、突然叫びたくなった場合は、マイク使わず叫んでください。
普通の勉強会の会場の音響設備ではコンプレッサー通されてないので大体スピーカー痛めます。


これで皆さんの発表もより参加者に伝わりつつ、カラオケでも高得点が狙えますね!

*1:PAとは Public Addressの略でコンサートなどで客席の一番いいところで大きな機械さわってオーディエンスにステージの音を伝えるお仕事してたりします