symfonyで携帯サイトの開発を行う場合、デフォルトのフィルタクラスやビュークラスを継承し、携帯向けに改造することでアクションやテンプレート内で機種判定をすることなくスマートなコーディングが可能になります。
今回導入するにあたっては、PC版のサイトが既にあったため、できるだけ既存のアクションファイルを触らず携帯用のテンプレートを置くだけでOK!となるようにしてみました。
クリアする課題
自作のキャリア判別クラスでも可能ですが、メンテナンスが面倒なので今回はPEARのnet_userAgent_mobileを使用。
キャリア判定や端末情報の取得など一通りの機能が揃っています。アップグレードもコマンド一発なので簡単。
インストール方法。コマンドラインで以下のコマンドを実行するだけ。
$pear install -f -o net_userAgent_mobile
アクションのあちこちにif文を書くのは避けたいので、自作のフィルタクラスを作ってそちらを使用するよう設定変更する。
myMobileFilter.class.php
<?php
//symfonyデフォルトのsfFilterクラスを継承
class myMobileFilter extends sfFilter
{
public function execute ($filterChain)
{
if ($this->isFirstCall())
{
//デバッグモードでのエラー回避
$er = error_reporting();
if ($er> E_STRICT) {
error_reporting($er - E_STRICT);
}
require_once('Net/UserAgent/Mobile.php');
$objAgent = Net_UserAgent_Mobile::singleton();
//アクションでも使用できるようAttributeにセットしておく
$this->getContext()->getRequest()->setAttribute('agent', $objAgent);
//これは自作クラス ※後述
$objMobile = new mobileCheck();
//携帯端末の場合
if ($objMobile->isMobile($objAgent))
{
$this->getContext()->getRequest()->setAttribute('PHPSESSID', session_id());
//パラメータ文字コード変換 ※SJISで取得したパラメータをUTF-8に変換
$keys = sfContext::getInstance()->getRequest()->getParameterHolder()->getNames();
foreach($keys as $key)
{
$val = sfContext::getInstance()->getRequest()->getParameter($key);
//配列対応 とりあえず1次元配列まで
if (is_array($val))
{
foreach($val as $key1 => $val1)
{
$val1 = mb_convert_encoding($val1,"utf-8","SJIS");
$val[$key1] = $val1;
}
}
else
{
$val = mb_convert_encoding($val,"utf-8","SJIS");
}
sfContext::getInstance()->getRequest()->setParameter($key,$val);
}
//出力をSJISに
mb_http_output('SJIS');
ob_start("mb_output_handler");
}
}
// execute next filter
$filterChain->execute();
}
}
※設置場所:/apps/アプリ名/lib/の直下(オートロード領域ならどこでも良いかも)
上記ファイルを設置しただけでは有効にならないので、/apps/アプリ名/config/配下のfilters.ymlを以下のように編集し、 symfony ccでキャッシュをクリアする。
filters.yml
rendering: ~
web_debug: ~
security: ~
# generally, you will want to insert your own filters here
myMobileFilter:
class: myMobileFilter ←ここで作成したフィルタクラスを指定
cache: ~
common: ~
flash: ~
execution: ~
これでPC、携帯の入出力を気にすることなくコーディングが可能になります。
アクションもテンプレートもutf-8のままでOK。
mobileCheck.class.php(参考)
<?php
/*
* 携帯チェッククラス
* net_userAgent_mobile必須
*/
class mobileCheck
{
var $objAgent;
function mobileCheck()
{
}
//ユーザエージェントで携帯判別
public static function isMobile($objAgent)
{
$bool = false;
if ($objAgent)
{
if (!$objAgent->isNonMobile())
{
$bool = true;
}
}
/*
switch(true)
{
//DoCoMo
case ($objAgent->isDoCoMo()):
$bool = true;
break;
//softbank
case ($objAgent->isVodafone()):
$bool = true;
break;
//au
case ($objAgent->isEZweb()):
$bool = true;
break;
//PC? これだけで足りるのか?怪しい。
default:
break;
}
*/
return $bool;
}
//ドメインで携帯判別 ※PCから携帯URLにアクセス→携帯の画面を表示したい場合などに使用
public static function isMobileDomain()
{
$bool = false;
//携帯ドメインならtrue
if ($_SERVER["SERVER_NAME"] == sfConfig::get('app_domain_****') ||
$_SERVER["SERVER_NAME"] == sfConfig::get('app_domain_****') ||
$_SERVER["SERVER_NAME"] == sfConfig::get('app_domain_****') ||
$_SERVER["SERVER_NAME"] == sfConfig::get('app_domain_****'))
{
$bool = true;
}
return $bool;
}
//ユーザエージェントで携帯キャリア判別
public static function getUserAgent($objAgent)
{
if ($objAgent)
{
switch(true)
{
//DoCoMo
case ($objAgent->isDoCoMo()):
$steAgent = "i";
break;
//softbank
case ($objAgent->isVodafone()):
$steAgent = "s";
break;
//au
case ($objAgent->isEZweb()):
$steAgent = "a";
break;
//携帯だが、上記3キャリア以外?→とりあえずドコモのテンプレート
case (!$objAgent->isNonMobile()):
$steAgent = "i";
break;
//PC
default:
//$steAgent = "pc";
break;
}
}
return $steAgent;
}
//ユーザエージェントから個体識別番号取得
public static function getMobileUid($objAgent)
{
if ($objAgent)
{
switch(true)
{
//DoCoMo
case ($objAgent->isDoCoMo()):
//softbank
case ($objAgent->isVodafone()):
if(method_exists($objAgent,"getSerialNumber"))
{
$strSerialNumber = $objAgent->getSerialNumber();
}
break;
//au
case ($objAgent->isEZweb()):
if(isset($_SERVER['HTTP_X_UP_SUBNO']))
{
$strSerialNumber = $_SERVER['HTTP_X_UP_SUBNO'];
}
break;
//PC
default:
break;
}
}
return $strSerialNumber;
}
public function setAgent($obj)
{
$this->objAgent = $obj;
}
public function getAgent()
{
return $this->objAgent;
}
}
WEBサイトの文献を読んだ感じでは、net_userAgent_mobileのisDoCoMo()メソッドを使用し、cookieが使えないドコモの時だけsession.use_trans_sid=1 とsesion.use_cookies=0 に設定するのが簡単そう。(他のキャリアはcookieが使える)
ただ、ドコモ以外でも古い機種だとcookieに対応していないようなので、今回は自作のセッションクラスを作って携帯の場合は常にセッションIDを維持するようにしてみました。
mySessionStorage.class.php
<?php
require_once('Net/UserAgent/Mobile.php');
/*
携帯セッション対策クラス
symfonyデフォルトのsfSessionStorageクラスを継承
ヘルパーを使った場合など、PHPSESSIDがスラッシュ区切りのGETパラメータで渡されると、phpがセッションIDとして認識しない罠が。
session_start()前にsession_id()で手動でセットしてあげればOK。
*/
class mySessionStorage extends sfSessionStorage
{
public function initialize($context, $parameters = null)
{
// initialize sfStorage
sfStorage::initialize($context, $parameters);
// set session name
$sessionName = $this->getParameterHolder()->get('session_name', 'symfony');
$objAgent = Net_UserAgent_Mobile::singleton();
$objMobile = new mobileCheck();
//携帯の場合は強制的にセッションを使用
if ($objMobile->isMobile($objAgent))
{
$sessionId = $context->getRequest()->getParameter($sessionName, '');
//セッションIDを手動でセット
//ここでセットしてあげないと、PHPSESSIDがスラッシュ区切りのGETパラメータの時に認識してくれないっぽい
if ($sessionId != '')
{
session_id($sessionId);
}
//PCの場合はデフォルトのsfSessionStorageと同じ処理
}
else
{
$use_cookies = (boolean) ini_get('session.use_cookies');
if (!$use_cookies)
{
$sessionId = $context->getRequest()->getParameter($sessionName, '');
if ($sessionId != '')
{
session_id($sessionId);
}
}
}
$cookieDefaults = session_get_cookie_params();
$lifetime = $this->getParameter('session_cookie_lifetime', $cookieDefaults['lifetime']);
$path = $this->getParameter('session_cookie_path', $cookieDefaults['path']);
$domain = $this->getParameter('session_cookie_domain', $cookieDefaults['domain']);
$secure = $this->getParameter('session_cookie_secure', $cookieDefaults['secure']);
$httpOnly = $this->getParameter('session_cookie_httponly', isset($cookieDefaults['httponly']) ?
$cookieDefaults['httponly'] : false);
if (version_compare(phpversion(), '5.2', '>='))
{
session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly);
}
else
{
session_set_cookie_params($lifetime, $path, $domain, $secure);
}
if ($this->getParameter('auto_start', true))
{
/*
ここでsession_startされてるせいでかなりハマる。
フィルタクラス内でsession_idを手動でセットしても有効にならないので。
*/
session_start();
}
}
}
※設置場所:/apps/アプリ名/lib/の直下(オートロード領域ならどこでも良いかも)
フィルタクラスと同様、上記ファイルを設置しただけでは有効にならないので、/apps/アプリ名/config/配下のfactories.ymlを以下のように編集し、symfony ccでキャッシュをクリアする。
factories.yml
cli:
controller:
class: sfConsoleController
request:
class: sfConsoleRequest
response:
class: sfConsoleResponse
test:
storage:
class: mySessionStorage
param:
session_name: PHPSESSID
all:
# controller:
# class: sfFrontWebController
#
# request:
# class: sfWebRequest
#
# response:
# class: sfWebResponse
#
# user:
# class: myUser
#
storage: ←コメントアウトを外す
class: mySessionStorage ←自作のクラス名を指定
param:
session_name: PHPSESSID ←デフォルトではsymfony。気持ち悪いのでPHPSESSIDに変更。
#
# view_cache:
# class: sfFileCache
# param:
# automaticCleaningFactor: 0
# cacheDir: %SF_TEMPLATE_CACHE_DIR%
これでセッション周りの設定は完了。あとは少々面倒ですが、リンク先URLやアクション内でリダイレクトする場合は末尾にSIDを付けてあげましょう。
例)
PC/携帯両方に対応したサイトの場合、アクションのロジックは統一しても、テンプレートは分ける必要があると思います
その場合、当然アクションにif文を書いてsetTemplateしてあげても良いのですが、全部のアクションでやるのは面倒なので、自作のビュークラスを作って一括で行うようにしてしまうと便利です。
ちなみに、symfonyの処理順の関係上、フィルタクラス内ではsetTemplateできないようです。
myMobileView.class.php
<?php
//symfonyデフォルトのsfPHPViewクラスを継承
class myMobileView extends sfPHPView
{
protected $objAgent;
public function configure()
{
if (!$this->objAgent)
{
$this->objAgent = $this->getContext()->getRequest()->getAttribute('agent');
}
$objMobile = new mobileCheck();
// store our current view
$actionStackEntry = $this->getContext()->getActionStack()->getLastEntry();
if (!$actionStackEntry->getViewInstance())
{
$actionStackEntry->setViewInstance($this);
}
$default_sf_app_module_config_dir_name = sfConfig::get('sf_app_module_config_dir_name');
/*
携帯ドメインの場合は携帯用のview.ymlを読み込む。
symfony内部で正規表現を使って判別しているため、~/config/view.ymlという形式以外は認識されない。
ドメインではなく端末で表示を分けたいときはisMobile()で対応。
*/
if ($objMobile->isMobileDomain())
{
$viewConfigFile = $this->moduleName.'/mobile/'.sfConfig::get('sf_app_module_config_dir_name').'/view.yml';
$module_config = 'mobile/'.$default_sf_app_module_config_dir_name;
sfConfig::set('sf_app_module_config_dir_name',$module_config);
}
else
{
$viewConfigFile = $this->moduleName.'/'.sfConfig::get('sf_app_module_config_dir_name').'/view.yml';
}
$configFile = sfConfigCache::getInstance()->checkConfig(sfConfig::get('sf_app_module_dir_name').'/'.$viewConfigFile, true);
if ($configFile == null) {
$configFile = sfConfigCache::getInstance()->checkConfig($this->moduleName.'/'.$viewConfigFile,true);
}
if ($configFile == null) {
$configFile = sfConfigCache::getInstance()->checkConfig(sfConfig::get('sf_app').'/'.$viewConfigFile,true);
}
if ($configFile == null) {
$configFile = sfConfigCache::getInstance()->checkConfig($viewConfigFile, true);
}
require($configFile);
// set template directory
if (!$this->directory)
{
$strTemp = $this->getTemplate();
//携帯対応処理 テンプレートファイル名に接頭辞を付ける
if ($this->objAgent)
{
if ($objMobile->isMobileDomain())
{
/*キャリア別にテンプレートを分けたい場合
//$strAction = $this->getContext()->getRequest()->getParameter('action');
//$strAddHeader = $objMobile->getUserAgent($this->objAgent);
//判別不明の場合はとりあえずi にする ※PCから携帯の画面が見えるように
//if (!$strAddHeader)
//{
// $strAddHeader = "i";
//}
//3キャリア同テンプレートでOKの場合はこっち
$strAddHeader = "m";
//mobile用のテンプレートディレクトリを指定。PC版と同じでもOK
$strTemp = "mobile/".$strAddHeader."_".$strTemp;
$this->setTemplate($strTemp);
}
}
$this->setDirectory(sfLoader::getTemplateDir($this->moduleName, $strTemp));
}
}
}
これもファイルを設置しただけでは有効にならないので、/apps/アプリ名/config/配下にmodule.ymlを新規に作成し、symfony ccでキャッシュをクリアする。
また、/modules/ディレクトリの配下に携帯用のビュー定義ファイル/mobile/config/view.yml を設置する。全てのモジュールで同じview.ymlを使用する場合は/apps/config/の下あたりに置いて共用できれば良いんですが…現状ではPC版のview.ymlとの切り分けがうまくいかないため保留。
myMobileView.class.php
all:
view_class: myMobile
携帯の個体情報でログインなどをさせたい場合は、net_userAgent_mobileで端末情報を取得して判別する。
取得方法はsymfonyの場合も従来と同じ。ドコモの場合はAタグやフォームにutnを付ける。その他のキャリア初めから取得できる。
※mobileCheck.class.phpのgetMobileUid()参照。