はじめまして。KAYAC技術部に留学中のshinoutです。
今日はsymfony1.4系で動作するプラグイン(もどき)、sfAppChangeについて紹介します。 symfonyでは異なるapp同士での共有はmodelのみに限られており、 たとえば 「backend処理だけど、front側のURLを取得したい」といったことや 「このパーツは別のappでも使いたい」といったことがあったと思います。 そんな時に便利になるのが今回作成したこのsfAppChangeです。
sfAppChangeでできること
- 別なappのroutingが使えて、URLを生成できる。
- 別なappのpartialが使える。
ダウンロード、インストール
ダウンロードは以下の2つのファイルをコピペしてください。
- AppChange.class.php
<?php
class AppChange{
private $app;
private $app_old;
private static $routings=array();
private function __construct($app){
$this->app = $app;
$this->app_old = sfContext::getInstance()->getConfiguration()->getApplication();
if( !sfContext::hasInstance($app)){
sfContext::createInstance(ProjectConfiguration::getApplicationConfiguration(
$app,
sfContext::getInstance()->getConfiguration()->getEnvironment(),
sfContext::getInstance()->getConfiguration()->isDebug()
));
}
$this->start();
}
public static function getInstance($app){
return new AppChange($app);
}
private function start(){
sfContext::switchTo($this->app);
}
private function end(){
sfContext::switchTo($this->app_old);
}
public function __call($method,$args){
$method = '_'.$method;
$this->start();
$ret = call_user_func_array(array($this,$method),$args);
$this->end();
return $ret;
}
/* for the use of app routing */
private function _getURL($format,$show_controller=true,$protocol=false){
$env = sfContext::getInstance()->getConfiguration()->getEnvironment();
if($env == 'prod'){
$phpname = ($show_controller) ? '/'.$this->app.'.php' : '';
}else{
$phpname = '/'.$this->app.'_'.$env.'.php';
}
$url = $phpname;
if($protocol===true){
$protocol = 'http';
}
if($protocol){
$url = $protocol.'://'.sfContext::getInstance()->getRequest()->getHost().$url;
}
if(!isset(self::$routings[$this->app])){
$config = new sfRoutingConfigHandler();
$routes = $config->evaluate(array(sfConfig::get('sf_apps_dir').'/'.$this->app.'/config/routing.yml'));
$routing =new sfPatternRouting(new sfEventDispatcher());
$routing->setRoutes($routes);
self::$routings[$this->app] = $routing;
}
$routing_old =sfContext::getInstance()->getRouting();
sfContext::getInstance()->set('routing',self::$routings[$this->app]);
$ret = $url.url_for($format);
sfContext::getInstance()->set('routing',$routing_old);
return $ret;
}
/* use other app's partial */
private function _getPartial($templateName,$parameter=array(),$app){
// partial is in another module?
if (false !== $sep = strpos($templateName, '/'))
{
$moduleName = substr($templateName, 0, $sep);
$templateName = substr($templateName, $sep + 1);
}
else
{
$moduleName = sfContext::getInstance()->getActionStack()->getLastEntry()->getModuleName();
}
$actionName = '_'.$templateName;
$view = new sfPartialview(sfContext::getInstance(),$moduleName,$actionName,'');
$view ->setPartialVars($parameter);
return $view->render();
}
}
- AppChangeHelper.php
<?php
function url_for_app($format,$show_controller=true,$appname='frontend',$protocol=false){
return AppChange::getInstance($appname)->getURL($format,$show_controller,$protocol);
}
function include_partial_app($name,$parameters=array(),$appname='frontend'){
echo AppChange::getInstance($appname)->getPartial($name,$parameters);
}
これらを
- /path/to/sfRoot/lib/AppChange.class.php
- /path/to/sfRoot/lib/helper/AppChangeHelper.php
に配置しましょう。 autoload文化の発達したsymfonyですから、別な場所に配置してみてもきっとうまくいきます、好きに配置しちゃってください。 plugins/sfAppChangePlugin/lib/ 下に置いてenablePluginするのが一番かっこいいですかね。
使い方
ヘルパーの呼び出し@view
<?php use_helper('AppChange') ?>別なappのURLを使いたいときは...
<a href="<?php echo url_for_app('@rule1', true, 'frontend') ?>"> frontend appのrouting使ったリンクです </a>別なappのpartialを使いたいときは...
<?php include_partial_app('modname/partialname', array('a'=>'b'), 'frontend') ?>
この二つだけです。link_to_app()やget_partial_app()などのインターフェイスは備えておりません。 欲しい方は改良してみてください。
詳しい使い方
url_for_app($format, $show_controller = true, $appname = 'frontend', $protocol = 'http' )
$format : url_for()の第一引数と全く同じものをいれてください。
- $show_controller : フロントコントローラ名を出力するかどうか、のフラグです。
ここみんな勘違いします。きっと。
url_for_app('@path1', true, 'frontend'); / prod環境では http://example.com/frontend.php/path1 と返します。 dev環境では http://example.com/frontend_dev.php/path1 と返します。 */ url_for_app('@path1', false, 'frontend') ; /* prod環境では http://example.com/path1 と返します。
dev環境では http://example.com/frontend_dev.php/path1 と返します。 (dev環境では$absolute=falseでもフロントコントローラ名が出力される!)
/ - $appname : appの名前。 デフォルトがfrontendなのは僕がJobeetからsymfonyを学んだからです。 それ以上の意味はないので、都合に応じて書き変えてもいいです。
$protocol : 絶対パスにするかどうかのフラグ。絶対パスの場合、プロトコルを指定。 trueの場合、httpに設定される。 例:
url_for_app('@rule1', true, 'frontend', false) // /frontend.php/path1 url_for_app('@rule1', true, 'frontend', 'http')// http://example.com/frontend.php/path1 url_for_app('@rule1', true, 'frontend', true) // http://example.com/frontend.php/path1include_partial_app($name, $vars = array(), $appname = 'frontend' )
$name: include_partial()の第一引数と同じですが、必ずmodule名から指定してください。 たとえばkayacというモジュールの中にあるshinoutというpartialを指定する場合、 'kayac/shinout'と指定してください。単に'shinout'だとどこの誰だかわからないので。
- $vars : include_partial()の第二引数と全く同じ。
- $appname : appの名前。 デフォルトはやっぱりfrontend。これ変えたいときは lib/helper/AppChangeHelper.php のfrontendという文字列を置換するだけでOK。
どうやって実現した?
sfContext::switchTo($appname) でコンテキストを変えています。 各種関数実行前にswitchToして、実行後に元のに戻す、という単純な原理です。
ただしroutingに関しては、それだけではうまくいかなかったので、加えてrouting.ymlも読ませています。 これはsfWebRequestの値がsfRoutingに影響を及ぼすというsymfonyの仕様にさかのぼるのですが、 興味があれば私まできいてください。
困ること
- include_javascripts()やinclude_stylesheets()などは、読み込み先partialに書いてあった場合正常に動作しません。 それらをパース中のsfContextに登録されたもののみが表示されます。
最後に
symfonyはprotectedな文化でもあります。 不満ならextendsしてしまえばいいのです。 symfonyはなんでも自分で取り揃えて、記述するルールを規定してはいますが 独自になんでも拡張出来てしまうという柔軟性も備えています。 みんなでちょっと便利なツールを交換したりとかしたいですね。