FuelPHP Hybridを拡張してリクエスト先が`action_`ならAjaxリクエストでも`template`が使える様にする

はじめに
===
`FuelPHP`には`Template`と`REST`、そしてその両方の特性を併せ持つ`Hybrid`というベースコントローラーがありますが、その中で`Hybrid`を継承元とし色々と参考にさせて頂きつつ拡張してみたので紹介です。:open_hands:

前提
===
- PCはMacを使ってます
- FuelPHPのバージョンは`1.8`
- ざっくり作るページをご説明させて頂きますと、
![スクリーンショット 2016-12-13 19.19.59.png](https://qiita-image-store.s3.amazonaws.com/0/19060/17ff584e-ae43-72a2-e337-f1975de441ee.png)
    こんな感じ


準備
===
- [FuelPHPをインストール](http://fuelphp.jp/docs/1.8/installation/instructions.html)しましょう


実装するにあたり決めた自分ルール
===
`Hybrid`は`REST`の機能も持っているので、HTTPメソッドに応じてコントローラのメソッドを限定する事が出来ます。
また、Ajaxによる通信が主になりそうだったので、下記の自分ルールを決めて場合分けしました。

1. Ajaxリクエストの場合→HTTPメソッドに対応したコントローラメソッドに投げる
    - `action_〇〇`の場合はviewファイルの中身(`<body>`とかは含まない)を返却
    - その他の場合はjsonオブジェクトを返却


1. Ajaxリクエストでない場合→対応するviewファイル(`<body>`とかも含む)を返却

1. 何なら`js`や`css`はviewオブジェクトを返却する場合はコントローラ名及びアクション名に対応するファイルがあれば勝手に読み込まれる様にしたい...！！


やってみる
===
#### ①
自分ルール1と2の事を考えてまずは`./fuel/app/views/`配下に以下の2ファイルを作っておきます。

`template.php`

```php
<!DOCTYPE html>
<html lang="ja">
	<head><!-- 省略 --></head>
	<body>
		<?php echo $content; ?>
	</body>
</html>
```

`contents.php`

```php
<?php echo $content; ?>
```

---
#### ②
`Hybrid`には元々そのリクエストがRESTFulか否かを判断する`is_restful()`というメソッドがありますが、メソッド内ではAjaxリクエストか否かを判断しています。
`Hybrid`の`before()`でコレに引っかからないと、このタイミングでview関連の処理が出来ません。
自分ルール1で`ajax`リクエストでもviewファイルを返却したかったので、以下の様にしました。

before

```php
public function is_restful()
{
	return \Input::is_ajax();
}
```

after

```php
public function is_restful()
{
	// Ajaxリクエスト 且つ "HTTPメソッド_アクション名"というメソッドがリクエスト先のコントローラにあるか
	return \Input::is_ajax() && method_exists(Request::main()->controller, mb_strtolower(\Input::method()) . '_' . Request::active()->action);
}
```

これは`router()`でも同じ様な記述がありますが、
RESTFulに対応したメソッドがあるか否かまでチェックを入れる様にしました。

---
#### ③
この処理を入れた事で`before()`の処理をAjaxリクエストじゃなくても通ってしまうケースがありますので、以下の様に補填しました。

before

```php
public function before()
{
	if (!$this->is_restful()) {
		if (!empty($this->template) and is_string($this->template)) {
			$this->template = \View::forge($this->template);
		}
	}
	return parent::before();
}
```

after

```php
public function before()
{
	if (!$this->is_restful()) {
		if (!empty($this->template) && is_string($this->template)) {
			// Ajaxリクエストなら`contents.php`を読む
			if (Input::is_ajax() === TRUE) {
				$this->template = View::forge('contents');
			// Ajaxリクエストでなければ`template.php`を読む
			} else {
				$this->template = View::forge($this->template);
			}
			// `./fuel/app/views/`配下に"コントローラ名/アクション名.php"のファイルがあれば、`template.php`の`$content`へ
			if (File::exists(APPPATH . 'views/' . strtolower(ltrim(mb_strstr(Request::main()->controller, '_'), '_')) . '/' . Request::active()->action . '.php') === TRUE) {
				$this->template->content = View::forge(strtolower(ltrim(mb_strstr(Request::main()->controller, '_'), '_')) . '/' . Request::active()->action);
			}
		}
	}
	return parent::before();
}
```

これで`action_〇〇`はAjaxリクエストで`contents.php`を返してくれる様になりました。
Ajaxリクエストで無ければ`template.php`がそのまま返却されます。
元々の`REST`の機能は`get_〇〇`や`post_〇〇`とキッチリ各コントローラでメソッドを定義しておけば特に影響は無いかと思います。

---
#### ④
最後に自分ルール3について、これについては
[【FuelPHP】コントローラーやアクション毎に自動でcss、jsを読み込む](http://takobonge.hatenablog.com/entry/2015/11/04/234714)
を参考にさせて頂き、
`contents.php`と`template.php`の両方に`Asset::render()`を記述しておきます。


---
#### ⑤
最後にコントローラ側のメソッドについてですが、最終的に以下の様な感じになりました。

```php
/**
 * viewファイルを返却する処理
 */
public function action_index() {
	// viewファイルへ値を渡す
	$this->template->content->foo = "bar";
}

/**
 * データを取得する処理
 */
public function get_record() {
	// データを取得して返却
	return $this->response(/* 返却するデータ */);
}

/**
 * データを追加する処理
 */
public function post_record() {
	// データを追加してその結果を返却
	return $this->response(/* 成功 or 失敗 とか */);
}

/* ...etc */
```

最後に
===
要件次第な所もありますが、最初にルールを決めて
これらのロジックを組んでたお陰で後々楽出来たかなと思います。

Ajaxリクエストでなければ通常の画面遷移でviewファイルも返ってくるし、
Ajaxリクエストでviewを書き換えたりする必要がある時でも
特に意識しなくて良くなったので:raised_hands:バンザーイ:raised_hands:


ただ、もっとスマート:dancer_tone1:なやり方がありそうな気もします:kissing:

が、何かのご参考になれば幸いです...:wave:

###### Created by [株式会社デジタルクエスト](https://www.digiq.co.jp) 杉田 允