はじめまして。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/path1
include_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はなんでも自分で取り揃えて、記述するルールを規定してはいますが 独自になんでも拡張出来てしまうという柔軟性も備えています。 みんなでちょっと便利なツールを交換したりとかしたいですね。