-
Notifications
You must be signed in to change notification settings - Fork 49
workshop
- アプリケーションのインストール
- フレームワークのインストール
- アプリケーションリソースファイルの作成
- 作成したリソースにアクセスしてみる
- Webサイトの確認
- ページリソースファイルの作成
- DIでログを提供
- AOPで実行時間を計測
- リソース表現
- リソースの埋め込み
- マイクロコンテンツ
$ 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
$ 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
の PSR-3 のロガーインターフェイスを使用します。
monolog
はcomposer
で取得します。
$ composer require monolog/monolog "~1.0"
Ray.Diではインターフェイスとその実装をバインディングする方法をいくつか提供していますがここではProvider
バインディングを使います。詳細は BEAR.Sundayのマニュアル や Ray.DiのREADME をご覧ください。
最初にインスタンスを用意するProvider
というファクトリーを作成し、インターフェイス実装に必要なget
メソッドでインスタンスを返します。
src/Module/Provider/MonologLoggerProvider.php
<?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
メソッドを使い行います。
src/Module/AppModule.php
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
オブジェクトを受け取りプロパティに格納します。ログ出力ではそのロガーを使います。
src/Resource/App/Add.php
<?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)します。インターセプターから元のメソッドを呼びだす時に前後に処理を記述することができます。
メソッドの実行時間を計測するインターセプターはこのようになります。
src/Interceptor/BenchMarker.php
<?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(), // メソッドの指定
[$this->requestInjection('Koriym\Work\Interceptor\BenchMarker')] // インターセプター
);
}
}
AbstractModule::requestInjection()
メソッドはここでは、new Koriym\Work\Interceptor\BenchMarker()
と同じ動作をします。
これで、Add
クラスのすべてのメソッドに対してBenchMarker
がバインドされました。実行して画面に実行時間を表示させましょう。
名前によるマッチングでメソッドを指定しましたが、次は@BenchMark
とアノテーションをつけたメソッドにインターセプターをバインドするように変更しましょう。
まずはアノテーションクラスを実装します。BEAR.Sundayでは Doctrine Annotation を使います。
アノテーションファイルを作成します。
src/Annotation/BenchMark.php
<?php
namespace Koriym\Work\Annotation;
use Ray\Aop\Annotation;
/**
* @Annotation
*/
class BenchMark implements Annotation
{
}
バインドの定義をannotatedWith
を使ったものに変更します。
$this->bindInterceptor(
$this->matcher->any(),
$this->matcher->annotatedWith('Koriym\Work\Annotation\BenchMark'),
[$this->requestInjection('Koriym\Work\Interceptor\BenchMarker')]
);
計測するメソッドにアノテートします。
src/Resource/App/Add.php
<?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;
}
}
アノテーションはuse文を使ってフルパスで指定する必要があります。 実行してみて同じように結果が出力されることを確認しましょう。
AOP機能の詳細は BEAR.Sundayのマニュアル や Ray.AopのREADME をご覧ください。
実行時間の出力先を画面ではなくログにするように変更しましょう。
ロガーインターフェイスのバインディングがされているので、新たに設定をすることなく@Inject
をアノテートするだけでログオブジェクトを受け取ることができます。
src/Interceptor/BenchMarker.php
<?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;
}
}
今までは$this->requestInjection('Koriym\Work\Interceptor\BenchMarker')
はnew
と同じ動作でしたがここでは依存解決してインスタンスを生成しました。
実行してvar/log/debug.log
に実行時間のログが出力されることを確認しましょう。
Addリソースは値を提供するだけでしたが表現も提供するアプリケーションリソースを作成してみましょう。
src/Resource/App/User.php
<?php
namespace Koriym\Work\Resource\App;
use BEAR\Resource\ResourceObject;
use BEAR\Sunday\Annotation\Cache;
class User extends ResourceObject
{
private $data = [
0 => ['name' => 'BEAR', 'age' => 10],
1 => ['name' => 'Sunday', 'age' => 3]
];
/**
* @Cache(10)
*/
public function onGet($id = 0)
{
$this->body = $this->data[$id];
return $this;
}
}
次にリソースを表現にするためのテンプレートを作成します。
src/Resource/App/User.twig
<div>
<h2>User</h2>
Name: {{ name }}<br>
Age: {{ age }}<br>
</div>
ユーザーリソースの値はテンプレートと合成されページにセットされます。@Cacheとアノテートしているので指定した10秒間テンプレートの合成も含めてキャッシュされます。
次に@Embed
アノテーションを使ってユーザーリソースを埋め込んで(セットして)みましょう。
src/Resource/Page/User.php
namespace Koriym\Work\Resource\Page;
use BEAR\Resource\ResourceObject;
use BEAR\Resource\Annotation\Embed;
class User extends ResourceObject
{
/**
* @Embed(rel="user", src="app://self/user{?id}")
*/
public function onGet($id)
{
return $this;
}
}
rel
で指定された名前でsrc
で指定したリソースが埋め込まれます。URIにはonGet
で渡された$id
をそのままappリソースに渡しています。
URIに動的要素を加える時は RFC-6570 の URI Template を用います。
この@Embed
アノテーションによるリソースのセットは以下のコードと同じ事を行っています。
public function onGet($id)
{
$this['user'] = $this->resource
->get
->uri('app://self/user')
->withQuery(['id' => $id])
->request();
return $this;
}
HTMLの<img>
や<script>
、それに<iframe>
タグをイメージしてみてください。src
で指定される他のリソースを自身のリソースに埋め込んでいます。@Embed
タグも同じように埋め込んでいます。
src/Resource/Page/User.twig
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Wlecome to {{ user.name }} page</title>
<link href="//netdna.bootstrapcdn.com/bootswatch/3.0.0/united/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div>{{ user }}</div>
</div>
</body>
</html>
ページのテンプレートでは{{ user }}
でユーザーリソースのプレースフォルダを指定します。プレースフォルダにはアプリケーションリソースがテンプレートと合成されたマイクロコンテンツ(部分的なHTML)が展開されます。
<title>
タグでは{{ user.name }}
としてユーザーリソースのnameという要素を利用しています。このようにリソースは他のリソースに埋め込まれ文字列として評価される({{ user }}
)と表現(マイクロコンテンツ)になり、配列として評価される({{ user.name }}
)とその値が取り出されます。
作成したリソースは他のシステムから容易に利用することができます。
my_system.php
<?php
$app = require __DIR__ . '/bootstrap/instance.php';
$user = $app->resource
->get
->uri('app://self/user')
->withQuery(['id' => 0])
->eager->request();
?>
<!DOCTYPE html>
<head>
<title>"<?php echo $user['name']; ?>" in my system</title>
</head>
<body>
<?php echo $user; ?>
</body>
</html>
instance.php
はアプリケーションインスタンスでrequire
で取得します。初期化は必要ありません。次にリソースクライアントを使ってapp://self/user
リソースを取得しています。
Symfony等他のフレームワークやWordPress等他のCMSからBEAR.Sundayで作成したリソースを利用することができます。
コンソールで試してみましょう
$ php my_system.php
<!DOCTYPE html>
<head>
<title>"BEAR" in my system</title>
</head>
<body>
<div class="app-user">
<h2>User</h2>
Name: BEAR<br>
Age: 10<br>
</div>
</body>
</html>