-
Notifications
You must be signed in to change notification settings - Fork 49
workshop
$ mkdir ~/bear-workshop
$ cd ~/bear-workshop/
$ composer create-project bear/skeleton Koriym.Work
Installing bear/skeleton (0.10.2)
- Installing bear/skeleton (0.10.2)
Loading from cache
BEAR.Sunday
フレームワークを依存としてインストールします。
$ cd Koriym.Work
$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
- Installing aura/installer-default (1.0.0)
Loading from cache
...
Writing lock file
Generating autoload files
src/Resource/App/Add.php
<?php
namespace Koriym\Work\Resource\App;
use BEAR\Resource\ResourceObject;
class Add extends ResourceObject
{
public function onGet($a, $b)
{
$this['result'] = $a + $b;
return $this;
}
}
コンソールからアクセスしてみます。まずはエラー、必要な引き数を渡していません。
$ php bootstrap/contexts/api.php get 'app://self/add'
400 Bad Request
…
[BODY]
40Xのエラーはリクエストに問題があるエラーです。 次は引き数をつけて正しいリクエスト
$ php bootstrap/contexts/api.php get 'app://self/add?a=1&b=2'
200 OK
content-type: ["application\/hal+json; charset=UTF-8"]
cache-control: ["no-cache"]
date: ["Fri, 30 May 2014 15:16:49 GMT"]
[BODY]
result 3,
[VIEW]
{
"result": 3,
"_links": {
"self": {
"href": "http://localhost/app/add/?a=1&b=2"
}
}
}
計算結果が[BODY]
に入りその表現が[VIEW]
として表されてます。
これをWeb APIサービスにしてみましょう。
$ php -S 0.0.0.0:8081 bootstrap/contexts/api.php
RESTクライアント(Chromeアプリの Advanced REST client など)で http://0.0.0.0:8081/add?a=1&b=2にGETリクエストを送り確かめてみましょう。
次はWebサイトです。最初から用意されているIndexページのスクリプトを使用します。
まずはコンソールでアクセスします。
$ php bootstrap/contexts/dev.php get /
200 OK
cache-control: ["no-cache"]
date: ["Fri, 30 May 2014 15:36:12 GMT"]
[BODY]
greeting Hello BEAR.Sunday,
[VIEW]
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>BEAR.Sunday</title>
<link href="//netdna.bootstrapcdn.com/bootswatch/3.0.0/united/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<h2>Hello BEAR.Sunday</h2>
<p>template engine: twig</p>
</div>
</body>
</html>
Webで確認するためには同じスクリプトでビルトインウェブサーバーを起動します。
$ php -S 0.0.0.0:8082 -t var/www/ bootstrap/contexts/dev.php
Webブラウザで http://0.0.0.0:8082/ にアクセスします。
計算ページをつくるために、Addリソースにアクセスするページリソースを作成します。
src/Resource/Page/Calc.php
<?php
namespace Koriym\Work\Resource\Page;
use BEAR\Resource\ResourceObject;
use BEAR\Sunday\Inject\ResourceInject;
class Calc extends ResourceObject
{
use ResourceInject;
public function onGet($a, $b)
{
$add = $this->resource
->get
->uri('app://self/add')
->withQuery(['a' => $a, 'b' => $b])
->request();
$this['a'] = $a;
$this['b'] = $b;
$this['add'] = $add;
return $this;
}
}
src/Resource/Page/Calc.twig
<!DOCTYPE html>
<html lang="ja">
<head>
</head>
<body>
<div class="container">
<h2>{{ a }} + {{ b }} = {{ add.result }}</h2>
</div>
</body>
</html>
アクセスしてみましょう。
http://0.0.0.0:8082/calc?a=1&b=2
この結果をログする機能を追加してみましょう。ログには monolog を使いますが利用するときに直接作成しないで、作成されたログオブジェクトを受け取るようにします。
このように必要なものを自らが取得するのではなく、外部からの代入に期待する仕組みを DI といいます。
ロガーインターフェイスはvendor/psr/log
のPSR3のロガーインターフェイスを使用します。
monolog
はcomposer
で取得します。
$ composer require monolog/monolog "~1.0"
Ray.Diではインターフェイスとその実装をバインディングする方法をいくつか提供していますがここではProvider
バインディングを使います。詳細は BEAR.Sundayのマニュアル や Ray.DiのREADME をご覧ください。
最初にインスタンスを用意するProvider
というファクトリーを作成し、インターフェイス実装に必要なget
メソッドでインスタンスを返します。
<?php
namespace Koriym\Work\Module\Provider;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Ray\Di\ProviderInterface;
class MonologLoggerProvider implements ProviderInterface
{
public function get()
{
$log = new Logger('monolog');
$log->pushHandler(
new StreamHandler(__DIR__ . '/../../../var/log/debug.log',
Logger::DEBUG)
);
return $log;
}
}
次にインターフェイス束縛の定義をAppModule
のconfigure
メソッド内でbind
メソッドを使い行います。
class AppModule extends AbstractModule
{
// ...
protected function configure()
{
$this->install(new StandardPackageModule('Koriym\Work', $this->context, dirname(dirname(__DIR__))));
$this->bind('Psr\Log\LoggerInterface')
->toProvider('Koriym\Work\Module\Provider\MonologLoggerProvider');
// ...
}
}
この定義はキャッシュされる事に注意してください。webサーバーで確認する時には変更が反映されるようにキャッシュをクリアします。
$ php bin/clear.php
アプリケーションスクリプトの中で毎回クリアするためにはコメントアウトを外します。
bootstrap/contexts/dev.php
//
// The cache is cleared on each request via the following script. We understand that you may want to debug
// your application with caching turned on. When doing so just comment out the following.
//
require $appDir . '/bin/clear.php';
Add
クラスのコンストラクタでmonolog
オブジェクトを受け取りプロパティに格納します。ログ出力ではそのロガーを使います。
<?php
namespace Koriym\Work\Resource\App;
use BEAR\Resource\ResourceObject;
use Psr\Log\LoggerInterface;
use Ray\Di\Di\Inject;
class Add extends ResourceObject
{
private $logger;
/**
* @Inject
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function onGet($a, $b)
{
$this['result'] = $a + $b;
$this->logger
->debug(sprintf('%d + %d = %d', $a, $b, $this['result']));
return $this;
}
}
フレームワークはメソッドについた@Inject
を発見すると "ここには依存が必要だ" という事を理解し、モジュールで束縛したインスタンスを代入します。そのメソッドがコンストラクタの時はコンストラクタインジェクション、その他のセッターメソッドのときはセッターインジェクションと呼ばれます。
実行してみて、var/log/debug.log
に結果が出力されていることを確認しましょう。
メソッドの実行時間を出力するインターセプターを作成しましょう。
インターセプターは指定されたメソッドを横取り(intercept)します。インターセプターから元のメソッドを呼びだす時に前後に処理を記述することができます。
メソッドの実行時間を計測するインターセプターはこのようになります。
<?php
namespace Koriym\Work\Interceptor;
use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;
class BenchMarker implements MethodInterceptor
{
public function invoke(MethodInvocation $invocation)
{
$start = microtime(true);
$result = $invocation->proceed(); // 元のメソッドの実行
$time = microtime(true) - $start;
var_dump($time);
return $result;
}
}
MethodInvocationインターフェイスの$invocation
に"メソッド実行オブジェクト"が渡され$invocation->proceed()
とすると対象のメソッドを実行します。
例えばこのインターセプターをAdd
クラスのonGet
メソッドにバインドすると$invocation->proceed()
で足し算の結果が返ります。
対象メソッドの指定にはmatcherを使います。matcherで指定した条件でメソッドが検索されマッチしたメソッドに、1つまたは複数のインターセプターがバインドされます。まずはクラスやメソッドを名前で検索するstartsWith()
を使用してみましょう。前方一致文字列でマッチします。
src/Module/AppModule.php
<?php
namespace Koriym\Work\Module;
use BEAR\Package\Module\Package\StandardPackageModule;
use Koriym\Work\Interceptor\BenchMarker;
use Ray\Di\AbstractModule;
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;
class AppModule extends AbstractModule
{
// ...
protected function configure()
{
// ...
// マッチするメソッドにインターセプターをバインド
$this->bindInterceptor(
$this->matcher->startsWith('Koriym\Work\Resource\App\Add'), // クラスの指定
$this->matcher->any(), // メソッドの指定
[new BenchMarker()] // インターセプター
);
}
}
これで、Add
クラスのすべてのメソッドに対してBenchMarker
がバインドされました。実行して画面に実行時間を表示させましょう。
インターセプターによるAOPが実装できましたが、別のメソッドでも実行時間の計測をしたくなった時に、今の実装ではバインドの定義を都度追加する必要があります。
そこで、@BenchMark
とアノテーションをつけたメソッドにインターセプターをバインドするように変更しましょう。
まずはアノテーションクラスを実装します。このアノテーション自体は何も処理をしないので、中身は空でよいです。
<?php
namespace Koriym\Work\Annotation;
use Ray\Aop\Annotation;
/**
* @Annotation
*/
class BenchMark implements Annotation
{
}
次に、バインドの定義を変更します。先ほどはstartsWith
でマッチングしましたが、今度はannotatedWith
を使います。
$this->bindInterceptor(
$this->matcher->any(),
$this->matcher->annotatedWith('Koriym\Work\Annotation\BenchMark'),
[new BenchMarker()]
);
最後に、Add::onGet
メソッドにアノテーションを付けてあげれば完成です。
<?php
namespace Koriym\Work\Resource\App;
use BEAR\Resource\ResourceObject;
use Psr\Log\LoggerInterface;
use Ray\Di\Di\Inject;
use Koriym\Work\Annotation\BenchMark;
class Add extends ResourceObject
{
private $logger;
/**
* @inject
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* @BenchMark
*/
public function onGet($a, $b)
{
$this['result'] = $a + $b;
$this->logger->debug(sprintf('%d + %d = %d', $a, $b, $this['result']));
return $this;
}
}
実行してみて、先ほどと同じように結果が出力されることを確認しましょう。
AOP機能の詳細は BEAR.Sundayのマニュアル や Ray.AopのREADME をご覧ください。
今の実装では、インターセプターで計測した実行時間はvar_dump
で画面に出力されてしまいますが、これをログに出力するように変更してみましょう。
もちろん、ロガーはDIによってインジェクトします。
まずは、BenchMarker
を、ロガーをインジェクトできるように拡張しましょう。
<?php
namespace Koriym\Work\Interceptor;
use Psr\Log\LoggerInterface;
use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;
use Ray\Di\Di\Inject;
class BenchMarker implements MethodInterceptor
{
private $logger;
/**
* @Inject
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function invoke(MethodInvocation $invocation)
{
$start = microtime(true);
$result = $invocation->proceed(); // 元のメソッドの実行.
$time = microtime(true) - $start;
$this->logger->debug(sprintf('%f sec elapsed', $time));
return $result;
}
}
Psr\Log\LoggerInterface
に対するmonologのバインドは既に定義してあるので、これで完成・・・ではありません。
今のままだと、インターセプターとしてバインドされるBenchMarker
のインスタンスは、bindInterceptor
の引数内でnew
して作ってしまっているため、DIでロガーをインジェクトすることが出来ません。
$this->bindInterceptor(
$this->matcher->any(),
$this->matcher->annotatedWith('Koriym\Work\Annotation\BenchMark'),
[new BenchMarker()]
);
なので、この部分をnew
ではなくDIコンテナからインスタンスをもらってくるように書き換える必要があります。
AppModule
のrequestInjection
メソッドがその機能を提供しています。
$this->bindInterceptor(
$this->matcher->any(),
$this->matcher->annotatedWith('Koriym\Work\Annotation\BenchMark'),
[$this->requestInjection('Koriym\Work\Interceptor\BenchMarker')]
);
これで、インターセプターにロガーがインジェクトされるようになりました。
実行してみて、var/log/debug.log
に実行時間のログが出力されることを確認しましょう。