FuelPHPのAssetでZFのheadScript()->captureStart()っぽいことをする

FuelPHPでそのViewでしか使わないJSコードを書くときに、とりあえず外部ファイル化せずにViewの中に書いてしまいたい時がある。

Viewの中にscriptタグを記述するだけでOKな場合もあるが、Templateコントローラを使っていて、htmlの後ろの方でjQueryを読み込んでいる場合などは、先の方法だとViewの中でjQueryが使えなくて困る。

```template.php
<html>
<head>
(中略)
<?= $header ?>
<?= $content ?>
<?= $footer ?>
<script type="text/javascript" src="//code.jquery.com/jquery-1.11.0.min.js"></script>
</body>
</html>
```

```content.php
<div id="content">
  Something content...
</div>

<script type="text/javascript">
// ここではまだjQueryが読み込まれていないので使えない！
jQuery(function(){
  alert("hello1");
});
</script>
```

こんな時にZendFrameworkのビューヘルパーのheadScript()->captureStart() みたいに、Viewの中でJSコードをバッファしておいて、jQueryを読み込んだ後で出力できると便利だったりする。

というわけでAssetクラスを拡張してJSコードをキャプチャできるようにしてみた。

# Asset拡張クラス

```myasset.php
<?php
class Myasset extends \Fuel\Core\Asset
{
  public static function forge( $name='default', array $config=array() ) {
    if ( isset(static::$_instances[$name]) ) {
      if ( \get_class(static::$_instances[$name]) != "Myasset_Instance" ) {
        static::$_instances[$name] = new Myasset_Instance(static::$_instances[$name]);
      }
    } else {
      static::$_instances[$name] = new Myasset_Instance(array_merge(static::$default_config, \Fuel\Core\Config::get('asset'), $config));
    }

    if ( $name == 'default' ) {
      static::$_instance = static::$_instances[$name];
    }
    return static::$_instances[$name];
  }

  public static function instance( $instance=null ) {
    return static::forge();
  }

  /**
   * キャプチャ中判定用
   */
  static protected $capture_lock = false;

  /**
   * JSキャプチャ開始
   */
  public static function begin( $attr=array(), $group=null ) {
    if ( static::$capture_lock ) {
      throw new \Exception("キャプチャはネストできません。");
    }
    if ( static::instance()->begin($attr, $group) ) {
      static::$capture_lock = true;
    }
  }

  /**
   * JSキャプチャ終了
   */
  public static function end() {
    if ( !static::$capture_lock ) {
      throw new \Exception("キャプチャを開始していません。");
    }
    if ( static::instance()->end() ) {
      static::$capture_lock = false;
    }
  }
}

class Myasset_Instance extends \Fuel\Core\Asset_Instance
{
  protected $_attr;
  protected $_group;

  public function groups( $group=null ) {
    if ( $group !== null && isset($this->_groups[$group]) ) {
      return $this->_groups[$group];
    }
    return $this->_groups;
  }

  public function __construct( $config ) {
    if ( $config instanceof \Fuel\Core\Asset_Instance ) {
      // プロパティのコピー
      $this->_asset_paths = $config->_asset_paths;
      $this->_path_folders = $config->_path_folders;
      $this->_asset_url = $config->_asset_url;
      $this->_add_mtime = $config->_add_mtime;
      $this->_groups = $config->_groups;
      $this->_ident = $config->_ident;
      $this->_auto_render = $config->_auto_render;
      $this->_fail_silently = $config->_fail_silently;
      $this->_indent = $config->_indent;
    } else {
      parent::__construct($config);
    }
  }

  public function begin( $attr=array(), $group=null ) {
    $this->_attr = $attr;
    $this->_group = $group;
    return \ob_start();
  }

  public function end() {
    if ( false === ($code = \ob_get_clean()) ) {
      return false;
    }
    $this->_parse_assets('js_code', $code, $this->_attr, $this->_group);
    return true;
  }

  public function render( $group=null, $raw=false ) {
    ($group === null) and $group = '_default_';
    if ( \is_string($group) ) {
      isset($this->_groups[$group]) and $group = $this->_groups[$group];
    }
    \is_array($group) or $group = array();

    $css = '';
    $js = '';
    $img = '';
    foreach ( $group as $key=>$item ) {
      $type = $item['type'];
      $filename = $item['file'];
      $attr = $item['attr'];

      if ( $type != 'js_code' ) {
        // only do a file search if the asset is not a URI
        if ( !\preg_match('|^(\w+:)?//|', $filename) ) {
          // and only if the asset is local to the applications base_url
          if ( !\preg_match('|^(\w+:)?//|', $this->_asset_url) or \strpos($this->_asset_url, \Fuel\Core\Config::get('base_url')) === 0 ) {
            if ( !($file = $this->find_file($filename, $type)) ) {
              if ( $this->_fail_silently ) {
                continue;
              }
              throw new \FuelException('Could not find asset: '.$filename);
            }
            $raw or $file = $this->_asset_url.$file.($this->_add_mtime ? '?'.\filemtime($file) : '');
          } else {
            $raw or $file = $this->_asset_url.$this->_path_folders[$type].$filename;
          }
        } else {
          $file = $filename;
        }
      }

      switch ( $type ) {
        case 'css':
          $attr['type'] = 'text/css';
          if ( $raw ) {
            $css .= \html_tag('style', $attr, PHP_EOL.\file_get_contents($file).PHP_EOL).PHP_EOL;
          } else {
            if ( !isset($attr['rel']) or empty($attr['rel']) ) {
              $attr['rel'] = 'stylesheet';
            }
            $attr['href'] = $file;
            $css .= $this->_indent.\html_tag('link', $attr).PHP_EOL;
          }
          break;

        case 'js':
          $attr['type'] = 'text/javascript';
          if ( $raw ) {
            $js .= \html_tag('script', $attr, PHP_EOL.\file_get_contents($file).PHP_EOL).PHP_EOL;
          } else {
            $attr['src'] = $file;
            $js .= $this->_indent.\html_tag('script', $attr, '').PHP_EOL;
          }
          break;

        case 'js_code':
          $attr['type'] = 'text/javascript';
          $js .= \html_tag('script', $attr, PHP_EOL.$filename.PHP_EOL).PHP_EOL;
          break;

        case 'img':
          $attr['src'] = $file;
          $attr['alt'] = isset($attr['alt']) ? $attr['alt'] : '';
          $img .= \html_tag('img', $attr );
          break;
      }
    }

    // return them in the correct order
    return $css.$js.$img;
  }
}
```

# 使い方

## テンプレート側
```template.php
<html>
<head>
(中略)
<?= $header ?>
<?= $content ?>
<?= $footer ?>
<script type="text/javascript" src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<?= Myasset::render('add_js')  /** ここにキャプチャしたJSコードが展開される */ ?>
</body>
</html>
```

## ビュー側
```content.php
<div id="content">
  Something content...
</div>

<? Myasset::begin(array(), 'add_js')  /** 'add_js'というバッファ名でキャプチャ開始 */ ?>
jQuery(function(){
  alert("hello1");
});
<? Myasset::end()  /** キャプチャ終了 */ ?>
```

# 公開時は外部ファイル化

とはいえ、サービスを公開するときにはきちんと外部ファイル化してブラウザキャッシュされるようにしましょう。
とりあえず開発中は便利に使えるんじゃないかな、と言うことで。