BEAR.Sundayで任意のPageリソースでJSON形式のレスポンスを返す


[BEAR.Sunday](http://bearsunday.github.io/index.html)では[Twig](http://twig.sensiolabs.org/)がデフォルトのテンプレートエンジンとして採用されており、通常の画面表示ではPageリソースはTwigでHTML表現にレンダリングされます。しかし、Ajax等でJSON形式のレスポンスが欲しい場合には、PageリソースをJSON表現にレンダリングする必要があります。

## 最も簡単な実装

JSON形式のレスポンスが欲しいPageリソースクラスで、Rendererに```JsonRenderer```を指定します。

```php
namespace My\Project\Resource\Page;

use BEAR\Package\Provide\ResourceView\JsonRenderer;
use BEAR\Resource\ResourceObject;

class Hello extends ResourceObject
{
	/**
	 * @param string $name
	 * @return $this
	 */
	public function onGet($name)
	{
		$this['greeting'] = "hello " . $name;

		$this->setRenderer(new JsonRenderer($this));
		return $this;
	}
} 
```

これで

```javascript
$.ajax({
	type: 'GET',
	url: '/hoge',
	data: { 'name': 'foobar' },
	dataType: 'json'
}).done(function(json) {
	console.log(json.greeting); // "hello foobar"
})...
```

のように動作します。

と、ここで、折角BEAR.Sundayなので、```AOP```で実装してみます。

## AOPでの実装

### 構成

以下のコマンドでプロジェクトを作成した前提です。

```bash
$ composer create-project bear/skeleton My.Project
$ cd My.Project
$ composer install
```

```bash
$ tree My.Project/src
My.Project/src 
├── Annotation
│   └── ResponseJson.php ★追加
├── App.php
├── Interceptor
│   └── ResponseJson.php ★追加
├── Module
│   ├── App
│   │   ├── Aspect.php ★編集
│   │   └── Dependency.php
│   └── AppModule.php ★編集
└── Resource
    ├── App
    └── Page
        ├── Index.php
        ├── Index.twig
        └── Hello.php ★追加
```


### Pageリソースクラス

#### src/Resource/Page/Hello.php

下記のようにPageリソースクラスのメソッドに```ResponseJson```アノテーションを指定することでJSON表現にレンダリングできることを目指します。

```php
namespace My\Project\Resource\Page;

use BEAR\Resource\ResourceObject;
use My\Project\Annotation\ResponseJson;

class Hello extends ResourceObject
{
	/**
	 * @ResponseJson
	 *
	 * @param string $name
	 * @return $this
	 */
	public function onGet($name)
	{
		$this['greeting'] = "hello " . $name;
		return $this;
	}
} 
```


### Annotation 

#### src/Annotation/ResponseJson.php

アノテーションを定義します。
アノテーションを指定する対象（```@Target```）は```METHOD```です。

```php
namespace My\Project\Annotation;

use BEAR\Sunday\Annotation\AnnotationInterface;

/**
 * ResponseJsonアノテーション
 *
 * @Annotation
 * @Target("METHOD")
 */
final class ResponseJson implements AnnotationInterface
{
} 
```

### Interceptor

#### src/Interceptor/ResponseJson.php

インターセプターを実装します。
ここでPageリソースのRendererに```JsonRenderer```を指定します。

```php
namespace My\Project\Interceptor;

use BEAR\Package\Provide\ResourceView\JsonRenderer;
use BEAR\Resource\ResourceObject;
use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;

/**
 * ResponseJsonインターセプター
 */
class ResponseJson implements MethodInterceptor
{
	/**
	 * {@inheritdoc}
	 */
	public function invoke(MethodInvocation $invocation)
	{
		/** @var ResourceObject $page */
		$page = $invocation->getThis();
		$page->setRenderer(new JsonRenderer($page));
		return $invocation->proceed();
	}
}
```

### AOPの設定

#### src/Module/App/Aspect.php

ResourceObjectクラスのサブクラス（＝Pageリソースクラス）の、ResponseJsonアノテーションが指定されたメソッドに対して、インターセプターをを束縛します。

```php
namespace My\Project\Module\App;

use BEAR\Package;
use Ray\Di\AbstractModule;

class Aspect extends AbstractModule
{
    protected function configure()
    {
		$this->installResponseJson();
    }

	private function installResponseJson()
	{
		$this->bindInterceptor(
			$this->matcher->subclassesOf('BEAR\Resource\ResourceObject'),
			$this->matcher->annotatedWith('My\Project\Annotation\ResponseJson'),
			[$this->requestInjection('My\Project\Interceptor\ResponseJson')]
		);
	}
}
```

#### src/Module/AppModule.php

★印の箇所をコメントインし、上記のAOPの設定をアプリケーションに適用します。

※Aspect#installResponseJsonの処理をAppModule#configureに直接書いても同じように動作します。

```php
namespace My\Project\Module;

use BEAR\Package\Module\Package\StandardPackageModule;
use Ray\Di\AbstractModule;
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;

class AppModule extends AbstractModule
{
		:
		: 

    protected function configure()
    {
        $this->install(new StandardPackageModule('My\Project', $this->context, dirname(dirname(__DIR__))));

        // override module
        // $this->install(new SmartyModule($this));

        // $this->install(new AuraViewModule($this));

        // install application dependency
        // $this->install(new App\Dependency);

		// install application aspect
		$this->install(new App\Aspect($this)); ★コメントイン
    }
}
```



これで```ResponseJson```アノテーションをPageリソースクラスのメソッドに付加するだけで、JSON形式のレスポンスを得ることが出来るようになしました。