php オリジナルフレームワーク Lightning Tone モバイルセッションライブラリ
PHP3から10年近くphpを使い続け、その間にコツコツ作っていたオリジナルのphpのオリジナルフレームワーク『Lightning Tone』。
ずっと整理がつかずそのままになっていて公開する機会がなかったのですが、何かの参考になるかもしれませんのでその一部を公開します。
まずはこのフレームワークから、セッションライブラリを抜粋して紹介します。
このセッションライブラリの特徴は、携帯端末IDベースのセッションを実現しているところです。
- 作者: 伊藤祐策,富田陽介,三上喜之
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2010/04/30
- メディア: 大型本
- 購入: 3人 クリック: 201回
- この商品を含むブログ (10件) を見る
携帯端末IDベースのセッションをphpで作ろうとするとphp標準のセッション関数をカスタマイズすることでは実現ができない為、セッションの仕組みそのものをphpで一から作るしかありません。このライブラリではphpの標準セッション関数のパラメータをそのまま利用しつつ、携帯端末IDに対応したphp互換のセッション関数を自前で実装しています。
特徴
使い方
- 呼び出し方
<?php $ss = new ss();
- セッションに値を入れる
<?php $ss->count=1; //php標準のセッション変数でも可能 $_SESSION['count']=1;
- セッションのパラメータ設定
<?php // _(アンダーバー)をprefixに付けるとセッションのパラメータを設定できる) $ss->_name='sess_id'; //ini_setでphp標準のセッションパラメータ変更もできる。 ini_set('session.name','sess_id');
免責事項
- 公開するにあたって何個か独自定義の関数がない為このままでは動きません。参考程度にして頂ければと思います。
- 携帯端末の判定に$ENV['IS_MOBILE']を利用しています。docomo,au,softbankが入ります。事前にキャリアのIPアドレス帯をチェックして入れておきます。
<?php class ss{ const serialize_php = 0; // phpのserialize関数 const serialize_session = 1; // phpのセッション互換のserialize関数(デコードが不完全) const session_id_custom = 0; // 超圧縮・高レベル乱数系・サムチェック付き const session_id_php = 1; // phpのセッション互換のセッション関数(サムチェックなし) var $_prefix = 'sess_'; var $__id = ''; var $_check = array("ua"); var $_type; // 利用するセッションIDの種類 get post cookie SN guid subno vuid var $_serialize = self::serialize_php; var $_session_id = self::session_id_custom; var $_fname_enc = true; // セッションの圧縮オプション #var $_compress = "gzcompress"; //デバックがめんどいから一時的に外す var $_guid_domain = true; // ドメイン毎にguidデータを分ける var $_is_lock = true; // ロックを使う var $_fp; // ファイルポインタ var $_is_through; // デストラクタでセッションの保存をスルーする為のフラグ // 主にリダイレクトで使用する。 var $_is_setcookie; // cookieを送るかどうか var $__name = "sid"; // PHPSSIDは長すぎる var $_is_debug = true; // デバックモード function __construct($id = null,$name = null,$save_path = null){ if(isset($id )){ $this->_id = $id; } if(isset($name )){ $this->_name = $name; } if(isset($save_path)){ $this->_save_path = $save_path; } // 環境変数自動読み込み $this->_trust_domains = $GLOBALS['ss']["trust_domains"]; if(!$this->_save_path){ $this->_save_path = HOME."/tmp"; } // 永続的cookie $this->_cookie_lifetime = strtotime("2037/01/01"); ini_set("url_rewriter.tags", "a=href,area=href,frame=src,input=src,form=fakeentry,fieldset="); // ドメイン設定 // レンタルサーバーのサブドメイン利用の場合 if(eregi("sakura.ne.jp$",$_SERVER["HTTP_HOST"])){ $this->_cookie_domain = $_SERVER["HTTP_HOST"]; }else{ $dp = explode('.',$_SERVER["HTTP_HOST"]); while(count($dp)>2){ array_shift($dp); } $this->_cookie_domain = implode('.',$dp); } // 大手プロバイダのスペースを使わない場合は問題なし $this->_cookie_path = '/'; //$sessPath = ini_get('session.save_path'); //$sessCookie = ini_get('session.cookie_path'); //$sessName = ini_get('session.name'); if($this->__name){$this->_name = $this->__name;} if($_ENV["IS_MOBILE"]){ $init_method ='_init_'.$_ENV["IS_MOBILE"]; if(method_exists($this,$init_method)){ call_user_func(array($this,$init_method)); } // PC }else{ // session_start() // trans_sidはoff $this->_is_set_cookie = true; } if($this->__id){ // 端末IDを常に取得できる場合 $this->_prefix =''; }else{ if(count($_COOKIE)>0){ // クッキーが使える場合 if($_COOKIE[$this->_name]){ $this->__id = $_COOKIE[$this->_name]; $this->_type = "cookie"; if($_GET[$this->_name]){ $this->is_through = true; $this->del_trans_sid_redirect(); } } }else{ if($_POST[$this->_name] or $_GET[$this->_name]){ if( $_POST[$this->_name]){ $this->_type = "post"; $this->__id = $_POST[$this->_name]; }elseif($_GET[$this->_name]){ $this->_type = "get"; $this->__id = $_GET[ $this->_name]; } }else{ if($this->_is_set_cookie){ $this->setcookie(); } $this->is_through = true; $this->add_trans_sid_redirect(); } } } $this->_read(); $_ENV["rewrite_vars"][session_name()] = session_id(); if(count($_ENV["rewrite_vars"])>0){ ob_start(array($this,"url_rewrite")); } // PHP4だったらデストラクタをエミュレート if(version_compare(PHP_VERSION, '5.0.0','<')){ register_shutdown_function(array(&$this, '__destruct')); } } function __get($name){ switch($name){ case '_name':{ return session_name(); } case '_id':{ $this->__id = session_id()? session_id():( $this->__id? $this->__id: $this->create_session_id() ); session_id($this->__id); return $this->__id; } case '_save_path':{ return session_save_path()? session_save_path(): $this->_save_path; } case '_gc_probability': // 1 case '_gc_divisor': // 100 case '_gc_maxlifetime': // 1440 case '_cookie_lifetime': case '_cookie_path': case '_cookie_domain': case '_cookie_secure': case '_cookie_httponly':{ return ini_get('session.'.ltrim($name,'_')); } default:{ return $_SESSION[$name]; } } } function __set($name,$value){ //http://www.php.net/manual/ja/function.ini-get-all.php //ini_get_all("saession"); switch($name){ case '_name' :{session_name($value);break;} case '_id' :{session_id( $value);break;} case '_save_path':{if(session_save_path()){ session_save_path($value); }else{ $this->_save_path=$value; }break;} case '_gc_probability': // 1 case '_gc_divisor': // 100 case '_gc_maxlifetime': // 1440 case '_cookie_lifetime': case '_cookie_path': case '_cookie_domain': case '_cookie_secure': case '_cookie_httponly':{ return ini_set('session.'.ltrim($name,'_'),$value); } default:{ return $_SESSION[$name] = $value; } } } function __call($name,$args){ } function __destruct(){ if(!$this->_is_through){ $this->_write(); } } function _init_docomo(){ $_ENV["rewrite_vars"]["guid"] = "on"; //output_add_rewrite_var("guid","on"); //非SSLの場合 if($_GET["guid"] == "on"){ if ( $_SERVER["HTTP_X_DCMGUID"]){ $this->__id = $_SERVER["HTTP_X_DCMGUID"]; $this->_type = "guid"; }elseif(!$_GET[$this->_name]){ $this->is_through = true; $this->add_trans_sid_redirect(); } }else{ $this->is_through = true; self::add_guid_on_redirect(); } // guid // ser icc // docomo_uid } function _init_au(){ if ( $_SERVER["HTTP_X_UP_SUBNO"] ){ $this->__id = $_SERVER["HTTP_X_UP_SUBNO"]; $this->_type = "subno"; }else{ $this->_is_set_cookie = true; } } function _init_softbank(){ // 非SSL if ( $_SERVER["HTTP_X_JPHONE_UID"]){ $this->__id = $_SERVER["HTTP_X_JPHONE_UID"]; $this->_type = "juid"; }elseif(preg_match('|/(SN[0-9A-Za-z]{15})|', $_SERVER["HTTP_USER_AGENT"],$match)){ $this->__id = $match[1]; $this->_type = "SN"; }else{ $this->_is_set_cookie = true; } } function setcookie(){ setcookie( $this->_name, $this->_id, $this->_cookie_lifetime, $this->_cookie_path, $this->_cookie_domain, $this->_cookie_secure, $this->_cookie_httponly ); } static function add_guid_on_redirect(){ self::change_queries_redirect(array("guid"=>"on")); } function add_trans_sid_redirect(){ self::change_queries_redirect(array($this->_name=>$this->_id)); } function del_trans_sid_redirect(){ self::change_queries_redirect(array($this->_name)); } static function change_queries_redirect($change_queries){ // リダイレクト先のURLからクエリーを抽出 $rurl = $_SERVER["REQUEST_URI"]; //$query = parse_url($rurl,PHP_URL_QUERY); $urls = parse_url($rurl); $query = $urls["query"]; parse_str($query,$queries); // リダイレクトURLからクエリーを取り除く $urls = explode('?',$rurl); $rurl = array_shift($urls); // 成果トラッキング用にセッションIDを引き渡す // 渡された配列が『数字配列』だったら『要素の削除』 if(is_numeric(current(array_keys($change_queries)))){ foreach($change_queries as $key){ unset($queries[$key]); } }else{ // 渡された配列が『文字配列』だったら『要素の上書き』 if ( is_array($change_queries) ){ $queries = array_merge($queries,$change_queries); } } if(count($queries)>0){ $query = "?".http_build_query($queries); if ( $_ENV["IS_MOBILE"] == "softbank" ){ self::softbank3g_query_escape($query); } }else{ $query=''; } $loc = 'http://'.$_SERVER["HTTP_HOST"].$rurl.$query; //echo $loc;print_r($change_queries);exit; header("Location: ".$loc); exit; } // 公式CPに予約されているクエリーをURLエンコードして // 回避する関数 // see ... http://labs.unoh.net/2006/10/softbank.html // see ... http://www.geeklog.jp/forum/viewtopic.php?showtopic=9027 static function softbank3g_query_escape(&$query){ $softbank3g_reserved_queries = array( 'pid','sid','uid','lid','gid','rpid', 'rsid','nl','cl','ol','pl','jsky(*)', 'prc','cnt','reg','vsekey','vsernk', ); $tmp_regex = '('.implode('|',array_map('preg_quote',$softbank3g_reserved_queries)).')'; //$regex = '/('.$tmp_regex.'=/e'; $regex = array( '/^()' .$tmp_regex.'([=&])/e', // 行頭 '/([\?&])'.$tmp_regex.'([=&])/e', // 中間 '/([\?&])'.$tmp_regex. '()$/e', // 行末 '/^()' .$tmp_regex. '()$/e', // 行頭行末(単一) ); $query = preg_replace($regex,"'\\1'.allurlencode('\\2').'\\3'",$query); } static function unserialize($str){ if($str=='') return $str; // 正規表現で頑張るにはちとツライ // 補正かけて頑張っているが、まだ漏れがあるかも // 本当は字句解析すべきだが... $array = array(); //正規表現で行頭};が引っかかるのでそれのfix $fix_chr=''; if(in_array($str[0],array('}',';'))){ $fix_chr = $str[0]; $str = substr($str,-(strlen($str)-1)); } $a = preg_split("/([^|};][^|;]*)\|/", $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $a[0]=$fix_chr.$a[0]; for($i = 0; $i < count($a); $i = $i+2) { // 配列のキーの行頭が{;だった時の対策 // unserializeが、行末のゴミを無視することを利用して // 差分からfix文字列を取得する if(serialize(unserialize($a[$i+1]))!=$a[$i+1]){ // 文字列の差分を取得 $fix_chr = str_replace(serialize(unserialize($a[$i+1])),'',$a[$i+1]); $a[$i+2]=$fix_chr.$a[$i+2]; } $array[$a[$i]] = unserialize($a[$i+1]); } return($array); } static function serialize($array){ foreach($array as $key => $value){ $str[]= $key.'|'. serialize($value); } return implode('',$str); } // ハッシュ取得 function get_file_hash($str){ return anagram(rtrim(base64_url_encode(sha1($str,true)),',')); } function _read($key=null){ $key = $key ?$key :($this->_prefix.$this->_id); if($this->_fname_enc) $key = $this->get_file_hash($key); $path = $this->_save_path.'/'.$key; if(is_readable($path)){ //get_object_varsは使えない //$_SESSION = unserialize(file_get_contents($path)); if($this->_is_lock){ $this->_fp = fopen($path,"r+"); // 書き込みバッファを0にセット stream_set_write_buffer($this->_fp,0); // ファイルをロック flock($this->_fp,LOCK_EX); $data=''; while(!feof($this->_fp)){ $data .= fgets($this->_fp,4096); } }else{ $data = file_get_contents($path); } if($this->_compress){ $data = gzuncompress($data); } switch($this->_serialize){ case self::serialize_php:{ $_SESSION = unserialize($data); break; } case self::serialize_session:{ $_SESSION = self::unserialize($data); break; } } if(is_array($this->_check) and count($this->_check)>0){ foreach($this->_check as $check){ switch($check){ case "ua":{ if($_SESSION["HTTP_USER_AGENT"]!= $_SERVER["HTTP_USER_AGENT"]){ $_SESSION = array(); $this->__id = null; } break; } } } } }elseif($this->_is_lock){ $this->_fp = fopen($path,"w"); // 書き込みバッファを0にセット stream_set_write_buffer($this->_fp,0); return false; }else{ return false; } } function _write($key=null,$value=null){ // $_SESSIONをマージしておく $value = $value?$value:$_SESSION; $value["HTTP_USER_AGENT"] = $_SERVER["HTTP_USER_AGENT"]; $value["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"]; if($_SERVER["HTTP_X_DCMGUID"] ) $value["HTTP_X_DCMGUID"] =$_SERVER["HTTP_X_DCMGUID"]; if($_SERVER["HTTP_X_JPHONE_UID"]) $value["HTTP_X_JPHONE_UID"]=$_SERVER["HTTP_X_JPHONE_UID"]; $key = $key ?$key :($this->_prefix.$this->_id); if($this->_fname_enc) $key = $this->get_file_hash($key); $path = $this->_save_path.'/'.$key; switch($this->_serialize){ case self::serialize_php:{ $str = serialize($value); break; } case self::serialize_session:{ $str = self::serialize($value); break; } } if($this->_compress){ $str = gzcompress($str,9); } if($this->_is_lock){ // ファイルポインタを先頭に移動 rewind($this->_fp); fwrite($this->_fp,$str); // ファイルをアンロック flock($this->_fp,LOCK_UN); fclose($this->_fp); }else{ file_put_contents($path,$str); } $this->_gc_trigger(); } // すべてのセッション変数を開放する //function unset(){ // $_SESION = array(); // session_unset(); // session_destroy(); //} // すべてのセッションを破棄する function destroy(){ $_SESSION = array(); session_destroy(); } function create_session_id(){ switch($this->_session_id){ case self::session_id_custom:{ // mt_srand(microtime()*100000); 必要なくなったらしい $raw = sha1(uniqid(mt_rand(),true),true); $sum = chr(crc32($raw)<<24); $raw = $raw.$sum; $session_id = anagram(rtrim(base64_url_encode($raw),',')); return $session_id; } case self::session_id_php:{ // PHP互換 return md5(uniqid((isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''), true)); } } } static function check_session_id($session_id){ $decode = (base64_url_decode(deanagram($session_id))); $raw = substr($decode,0,(strlen($decode)-1)); $sum = substr($decode,-1,1); if(chr(crc32($raw)<<24)==$sum ){ return true; }else{ return false; } } function _gc_trigger(){ //echo (rand(1,$this->_gc_divisor)."/".$this->_gc_probability)."<br>"; if(rand(1,$this->_gc_divisor) <= $this->_gc_probability){ $this->_dc(); } } // ガベージコレクション function _gc(){ clearstatcache(); // ファイル情報をフラッシュ // 基本的にセッション系のみ除去 foreach( glob($this->_save_path."/".$this->_prefix."*") as $f){ $elapsed = (int)(time()-filemtime($f)); if($elapsed > $this->_gc_maxlifetime){ if(is_writeable($f)){ unlink($f); } } } } /* バッファ関数 */ function check_add_vars($urls){ //cookieありのときと無しの時で動作が異なる //cookieはクロスドメインでないので //SSL 切り替え時やトラストドメインの場合だけ // trans_sidの場合は、上記の場合と // 相対アドレス、自ホストの場合 // Cookie = on の時 // (1) 信頼ドメイン // (2) SSL切り替え時 // trans_sid empty($_COOKIE[session_name()]) // (3) 相対アドレス // (4) 自ホスト // 絶対パス extract($urls); extract($_SERVER); if( (in_array($scheme,array("http","https")) and // 同一ドメインでSSL ⇔ 非SSL 切り替え時 ( $HTTP_HOST == $host and ( ($HTTPS == "on" and $scheme == "http") or ($HTTPS != "on" and $scheme == "https") // 信頼ドメインだった場合 // 文末matchだから実際はregex使ったりとちと面倒 ) or in_array($host,$this->_trust_domains) or (empty($_COOKIE[session_name()]) and $HTTP_HOST == $host ) ) )or(empty($host) and empty($_COOKIE[session_name()])) ){ return true; } return false; } function get_pram_from_matches($m){ if ($m[2]){ $url = $m[2];$q=''; }elseif($m[3]){ $url = $m[3];$q='"'; }elseif($m[4]){ $url = $m[4];$q="'"; } return array($url,$q); } function link_rewrite($matches){ // formの場合だけ、コンテンツと閉じタグ取得 $close_tag_etc = $matches[6]; if($close_tag_etc){ $html = new html($matches[0]); $form = ($html->tag("form")); } list($url,$q) = $this->get_pram_from_matches($matches); $urls = url::parse($url); if($this->check_add_vars($urls)){ $add_entry=''; foreach($_ENV["rewrite_vars"] as $name => $value){ // 携帯でmethodがpostの場合はactionに付加 if( empty($close_tag_etc) or (strtolower($form["method"]) == "post" and $_ENV["IS_MOBILE"])){ $urls["queries"][$name] = $value; // それ以外の場合は通常通りhiddenを追加 }else{ $add_entry.='<input type="hidden" name="'.$name . '" value="'.$value.'">'; } } if(empty($add_entry)){ $url = url::build($urls); } } $tag = $matches[1].$q.$url.$q.$matches[5].$add_entry.$close_tag_etc; return $tag; } function url_rewrite($buf){ //url_rewriter.tags //a=href,area=href,frame=src,input=src,form=fakeentry,fieldset= //とりあえずfieldsetは放置意味分からん $tag = ini_get("url_rewriter.tags"); $tmps = explode(",",$tag); foreach($tmps as $tmp){ list($k,$v) = explode('=',$tmp); $tags[$k] = $v; } foreach($tags as $tag => $elm){ //$tag = 'a'; $elm = 'href'; if($elm and $elm!="fakeentry"){ $regex = "%(<".$tag.".*?".$elm."\s*=\s*)" . "(?:([^\s\"'>]+)|\"([^\"]+)\"|'([^']+)')" . "((?:[^'\">]|\"[^\"]*\"|'[^']*')*>)%is"; //preg_match($regex,$buf,$matches); print_r($matches); //regexテスト $buf = preg_replace_callback($regex,array($this,'link_rewrite'),$buf); }elseif($elm=="fakeentry"){ $tag = "form"; $elm = "action"; //form hidden $regex = "%(<".$tag.".*?".$elm."\s*=\s*)" . "(?:([^\s\"'>]+)|\"([^\"]+)\"|'([^']+)')" . "((?:[^'\">]|\"[^\"]*\"|'[^']*')*>)(.*?</".$tag.">)%is"; //preg_match($regex,$buf,$matches); print_r($matches); //regexテスト //$buf = preg_replace_callback($regex,array($this,'link_rewrite'),$buf); $buf = preg_replace_callback($regex,array($this,'link_rewrite'),$buf); } } //echo $buf."\n<br>"; return $buf; } }