「Re:デザイナーとの協業での工夫 Smartyプリフィルタの活用法」実際に行なってみたソースなど その1

昨年の11月のエントリで、デザイナーとの協業での工夫というエントリを書きました。
デザイナーとの協業での工夫 Smartyプリフィルタの活用法 - maru.cc@はてな
この案件が実際にリリースを向かえ、一通りの作業がひと段落がついたので、感想と今後行なっていこうと思っていることをまとめてみます。


また、実際に行なった仕組みのソースを公開したいと思いましたが、案件のソースは公開できません(あたりまですが)。
そこで、他のサイトを実際に作成しながら、許せる範囲で使ったものか、同等の機能を別途実装して公開したと思います。


どんなサイトにしようか迷ったのですが、長らく放置してしまっていた Ethna-users.jp がちょうどありましたので、そちらを題材に実装しようと思います。
github で公開中なので、ぜひ参考にしてください。もちろん forkしてコードにつっこみも大歓迎です。
http://github.com/marucc/ethna-users.jp/

仕組みとして作ったもの

こちらを、何回かにわけて実際の実装方法をソースをまじえて説明をしたいと思います。

  • 1. .htmlファイルをエントリポイントにする
  • 2. Smartyのデリミタを変える
  • 3. Smartyのデリミタの種類増やす
  • 4. http<->https絶対パスで書かなくても動くようにする
  • 5. 部品のincludeを出来るようにする

1. .htmlファイルをエントリポイントにする

これは、かなり成功しました。

実は、モバイルサイトを作る場合などで実績のある仕組みなので、予想通りという感じです。サイトを作るデザイナーさんには、PHPプログラムで動くということをまったく意識せずに、aタグやimgタグの画像などに関しても、通常のhtml同士のリンクと同様に相対パスで記述できるため、マージ時に作業が発生しません。

これが、例えば、PathInfoなどを使う形式や、テンプレートファイルが .tpl などの場合に、実際に作成しているものと設置時で差異が出てしまうため、余計な手間が発生します。
テンプレートファイルを .htmlに無理やりしたとしても、画像やCSSと、テンプレートファイルを別の場所にアップする必要が出てきてしまうため、やはり余計な手間や判断が必要になってきてしまいます。


また、ほぼ静的だが、一部だけ、DBから取得したデータなどを動的に表示させたい場合に、actionは必要ないが、phpの処理が書きたいという場合にも対応をしました。

まずは、.htaccessの設定

http://github.com/marucc/ethna-users.jp/blob/e210e107e0b3a880d2aea5b33339b1b9cc8da37c/www/.htaccess
以下の部分が肝心の箇所です。

AddType application/x-httpd-php .html
AddType application/x-httpd-php .htm
 
<FilesMatch "\.html?">
php_value auto_prepend_file "./_page.php"
AcceptPathInfo Off
</FilesMatch>

auto_prepend_file という設定はご存知でしょうか?
PHP: コア php.ini ディレクティブに関する説明 - Manual: auto_prepend_file


PHP勉強会の懇親会などで話していて感じたたことですが、この auto_prepend_file の設定ですが、相対パスで書けるというのは意外に知られていないようです。
もちろん、相対パスで書いた弊害として、下層ディレクトリで動かなくなってしまうので、階層が違う場合にはいずれかの対応が必要になります。

  • a.auto_prepend_file をフルパスで書く<今回はこれを採用しました
  • b.全ての下層ディレクトリに _page.php を作る
  • c.全ての下層ディレクトリに .htaccess を作り、auto_prepend_fileの相対パスを調整する

ま、常識的に考えて、フルパスでしょう。
なぜ、このような相対パスの形式の書き方をしているかというと、やはり、開発時のサーバ、ステージングサーバ、本番サーバと順に反映していく上で、フルパスが書かれてしまっているというのは、環境依存になりリリース作業の弊害になります。
.htaccessには、別の設定も記述することになるので、反映ファイルから除外しておくと、本番環境での設定漏れ等が発生してしまう可能性があります。


特に、モバイルサイトでIP制限をしている場合、.htaccessで行なうことが多いのですが、そのIP帯域追加時などに問題になったりします。
それよりは、ファイル数が少ないのであれば、全てフラットにし、多くなるとしてもディレクトリの分け方を少なくし、b,cのどちらかの対策を取る方が得策な場合もあるでしょう。

次に、auto_prepend_file で呼ばれる _page.php

http://github.com/marucc/ethna-users.jp/blob/e210e107e0b3a880d2aea5b33339b1b9cc8da37c/www/_page.php

<?php
require_once dirname(__FILE__) . '/../app/Ether_Controller.php';
 
Ether_Controller::main('Ether_Controller', array('page'), 'notfound');
exit;
?>

Ethna特有になってしまいますが、Ether_Controller::main の第二引数を array指定にし、pageアクションのみが動くようにしているところも注意すべき点です。また、exit; も必須になります。
Ethna エントリポイント毎に実行可能なアクションを制限する


もちろん、他のフレームワークを使用している場合には、そのフレームワークを呼ぶエントリポイントになればいいかと思います。


viewを判断する actionの処理

呼ばれる htmlごとに特殊な処理をしないのであれば、こちらは必要ありません。
例えば、トップページのみ最新のニュースの一覧を表示したい、といったページを作るのに、使えるかと思います。
もちろん、ニュースの一覧表示ぐらいだったら、Smartyのカスタムプラグインとして実装するのもありですけどね。ひとつの方法として参考にしてみてください。

http://github.com/marucc/ethna-users.jp/blob/e210e107e0b3a880d2aea5b33339b1b9cc8da37c/app/action/Page.php

<?php //色づけ

    /**
     * page action implementation.
     *
     * @access public
     * @return string forward name.
     */
    function perform()
    {
        $base_view_name = 'page';
 
        $action = preg_replace('!^' . $this->backend->ctl->getDirectory('www') . '/(.+)\.html?$!i', '\1', $_SERVER['SCRIPT_FILENAME']);
        $action = preg_replace('![^a-z0-9/]!', '', $action);
        $action = str_replace(array('/'),array('_'),$action);
        $view_name = "{$base_view_name}_{$action}";
        $view_dir = $this->backend->ctl->getViewdir();
        $view_path = $this->backend->ctl->getDefaultViewPath($view_name, false);
        if (file_exists("$view_dir$view_path")) {
            return $view_name;
        }
        return $base_view_name;
    }

コードを解説すると

  1. $_SERVER['SCRIPT_FILENAME'] からアクセスされたページを判別し、$this->backend->ctl->getDirectory('www') の部分のパスを取り除くことで、ファイルへのパスを判別します
  2. 英数字とスラッシュ以外は取り除きます これは、サイトのファイル命名規則によっては、かぶってしまい問題がでるかもしれません
  3. スラッシュを Ethnaのディレクトリ区切りのアンダーバーに置換します
  4. アクセスされたファイル専用のViewファイルが存在するか判別します もしあれば、その Viewを使用します
  5. 専用のViewが無い場合には、共通のpage Viewを使用します
共通の page View

実際に、アクセスされたファイルをテンプレートとしてどのように使っているかですが、以下のような指定の仕方をしています。
http://github.com/marucc/ethna-users.jp/blob/e210e107e0b3a880d2aea5b33339b1b9cc8da37c/app/view/Page.php

<?php //色づけ
    /**
     * 遷移名に対応する画面を出力する
     *
     * 特殊な画面を表示する場合を除いて特にオーバーライドする必要は無い
     * (preforward()のみオーバーライドすれば良い)
     *
     * @access public
     */
    function forward()
    {
        $this->forward_path = $_SERVER['SCRIPT_FILENAME'];
 
        parent::forward();
    }

forward() メソッドは、通常継承して上書きする必要はありませんが、今回は、テンプレートの forward_pathを変更するために使用します。
Ethnaでは、Viewクラスのforward_pathプロパティに入っているファイル名が、そのままSmartyに渡されるため、こちらを変更することで無理やりですがテンプレートを変更することが出来ます。
また、フルパスで指定することにより、templateディレクトリ以外のファイルを使うことも出来ます。

特定のページ用のView

特定のページ用のViewは、app/view/Page/ 以下に作成します。
例として、もし、/sample/index.html用の Viewを作成したければ、次のように作成することが出来ます。

ethna add-view page_sample_index


これらのpage以下に作成した Viewは、元となる page Viewクラスを継承する必要があります。
ページ先頭で page Viewを require_once して読み込みます。この場合に、BASEからのパスを記述している理由は、下階層のviewを作成しても、全て同一の記述をするためにこのような書き方をしています。

<?php //色づけ
require_once BASE . '/app/view/Page.php';

次に、classの継承をします。

<?php //色づけ
/**
* page_index view implementation.
*
* @author {$author}
* @access public
* @package Ether
*/
class Ether_View_PageIndex extends Ether_View_Page
{
()
}


今回は、直下の index.html なので、page_index がView名になります。
http://github.com/marucc/ethna-users.jp/blob/e210e107e0b3a880d2aea5b33339b1b9cc8da37c/app/view/Page/Index.php

<?php //色づけ
    /**
     * preprocess before forwarding.
     *
     * @access public
     */
    function preforward()
    {
        // 処理いろいろ
    }

あとは、Ethnaの通常の流儀なので、View の preforwardメソッドには自由に処理を記述し、テンプレートとなる htmlファイルに、「ここだけはいじらないでね(はーと)」とデザイナーさんと意識あわせをして タグを書いてしまえばOKです。


もし、このpage以下全ての Viewクラスで特定の処理を行ないたい場合には、page Viewクラスの setDefaultメソッドを使用するといいでしょう。
Ethna 遷移時のデフォルトマクロを指定する。


とりあえず

この Ethna-users.jp程度ならば、ここまでの仕組みはまったく必要ないと思います。ただ、座組みやファイルを触る人が分かれる場合には、有用な場合もあるのではないでしょうか?


続きは、また書きます。