概要
CodeIgniterなどのフレームワークでは、URLを分かりやすいものにするため、PATH_INFOを利用しています。
これを実現するため、.htaccess
ファイルに以下の記述をします。
RewriteEngine on
RewriteCond $1 !^(index\.php|images|robots\.txt)
RewriteRule ^(.*)$ /index.php/$1 [L]
しかし、レンタルサーバによっては「No input file specified.」というエラーが発生し、PHPが動作しなくなる場合があります。
この解決方法として、記述を以下のように変更する方法があります。
RewriteEngine on
RewriteCond $1 !^(index\.php|images|robots\.txt)
- RewriteRule ^(.*)$ /index.php/$1 [L]
+ RewriteRule ^(.+)$ /index.php?/$1 [QSA,L]
- さくらサーバでNo input file specified. - せつないぶろぐ
- CodeIgniterで、”No input file specified error” エラーがでる | 開発業務日誌
- PHP - CodeigniterでのNo input file specifiedの対処法 - Qiita
- php - No input file specified - Stack Overflow
- php - .htaccess problem: No input file specified - Stack Overflow
- Getting a "No input file specified" error when trying to remove index.php via .htaccess in CodeIgniter | Jon Moore
- ce cache - "no input file specified" - .htaccess - ExpressionEngine® Answers
しかしこの記述は、PATH_INFOをクエリストリングとしてPHPに渡してしまいます。
そのため、$_SERVER['PATH_INFO']
でPATH_INFOが取得できなくなります。
以下のコードは、そのような場合でも$_SERVER['PATH_INFO']
を使えるようにするためのフォールバックコードです。
$_SERVER['PATH_INFO']
を定義することで、フレームワークや自作コードを修正する手間を省きます。
※参考サイトでは[QSA,L]
ではなく[L]
ですが、[L]
の場合はクエリストリングが上書きされるため、クエリストリングを正しく処理できなくなります。
このため、クエリストリングを上書きしないようQSA
を追加しています。
この改変は本投稿で紹介するフォールバックコードには影響しません。フォールバックコードは[L]
でも[QSA,L]
でも正しく動作します。
コード
-
$_SERVER['PATH_INFO']
が未定義の場合に再定義を行います。 -
$_SERVER['QUERY_STRING']
と$_GET
に渡された本来のPATH_INFOを削除します。
注意
-
このフォールバックコードより前で$_SERVER['QUERY_STRING']
や$_GET
を変更しないで下さい。
もし片方のみ変更されていた場合、$_GET
が$_SERVER['QUERY_STRING']
を基準とした値に上書きされてしまい、変更が無効になってしまう可能性があります。 -
filter_inputやfilter_input_arrayなどの関数には対応していません。
具体的には、
filter_input
関数などではPATH_INFOを取得できず、
またクエリストリングに渡された本来のPATH_INFOを取得できてしまいます。-
前者の問題については、
$_SERVER['REQUEST_TIME']
や$_SERVER['PHP_AUTH_USER']
と同じく取得できない変数と考えて下さい。 -
後者の問題については、
PATH_INFOはクエリストリングのキーとして渡されており、偶然取得してしまうケースは極めて稀と考えられます。
また、その値も空文字となっています。/
や/example
、/news/article/my_article
などのパス文字列をキーとするクエリストリングをfilter_input
関数で取得している、などの稀なケースを除き、
クエリストリングに渡された本来のPATH_INFOを取得できてしまうこの問題は考慮する必要はないものと考えます。
-
call_user_func(function(){
if(!isset($_SERVER['PATH_INFO'])){
if(isset($_SERVER['ORIG_PATH_INFO'])){
$path_info=$_SERVER['ORIG_PATH_INFO'];
}else{
/**
* `RewriteRule ^(.*)$ index.php?/$1 [QSA,L]`
* などの記述によりPATH_INFOを取得出来ない場合のフォールバック
*/
$sn_sp=strrpos($_SERVER['SCRIPT_NAME'],'/index.php');
if($sn_sp===false){
$sn_sp=strrpos($_SERVER['SCRIPT_NAME'],'index.php');
}
if(isset($_SERVER['REDIRECT_URL'])){
$pi_urlencode=
$pi_urldecode=ltrim(substr($_SERVER['REDIRECT_URL'],$sn_sp),'/');
}else{
$pi_urlencode=ltrim(substr($_SERVER['REQUEST_URI'],$sn_sp),'/');
$pi =strstr($pi_urlencode,'?',true);
if($pi!==false){
$pi_urlencode=$pi;
}
/**
* REQUEST_URIから取得したPATH_INFOの値をURLデコード
*/
$pi_urldecode=implode(
'/',
array_map(
function($pp){
return urldecode($pp);
},
explode('/',$pi_urlencode)
)
);
}
$query_string=isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : http_build_query($_GET);
/**
* 先頭のスラッシュを統一
*/
$slash_start_query=strncmp($query_string,'/',1)===0;
if($slash_start_query){
$pi_urlencode='/'.$pi_urlencode;
$pi_urldecode='/'.$pi_urldecode;
}
/**
* 相対パスを変換
* @link http://webdesign-dackel.com/2014/06/12/php-relativepath-to-absolutepath/
*/
list($pi_urlencode_absolute,$pi_urldecode_absolute)=array_map(function($pi){
$new_pi=array();
foreach(explode('/',$pi) as $pp){
if($pp==='.'){
array_shift($new_pi);
array_unshift($new_pi,'');
}elseif($pp==='..'){
array_pop($new_pi);
if(count($new_pi)===0){
$new_pi=array('');
}
}else{
$new_pi[]=$pp;
}
}
return implode('/',$new_pi);
},array($pi_urlencode,$pi_urldecode));
/**
* $_SERVER['QUERY_STRING']からPATH_INFOを削除
*/
$pi_query=null;
foreach(array(
//URLエンコードされた相対パス
$pi_urlencode,
//URLエンコードされた絶対パス
$pi_urlencode_absolute,
//URLデコードされた相対パス
$pi_urldecode,
//URLデコードされた絶対パス
$pi_urldecode_absolute,
) as $pi_query){
foreach(array(
$pi_query.'&',
$pi_query
) as $pi_query_amp){
$pi_q_len=strlen($pi_query_amp);
if(strncmp($query_string,$pi_query_amp,$pi_q_len)===0){
$query_string=substr($query_string,$pi_q_len);
break 2;
}
}
}
$_SERVER['QUERY_STRING']=$query_string;
/**
* $_GETからPATH_INFOを削除
*/
parse_str($query_string,$new_get);
if($pi_query===$pi_urlencode || $pi_query===$pi_urlencode_absolute){
$pi_query=urldecode($pi_query);
}
if(!isset($new_get[$pi_query]) && isset($_GET[$pi_query]) && $_GET[$pi_query]===''){
unset($_GET[$pi_query]);
}
/**
* PATH_INFOを取得
* PATH_INFOはURLデコードされ、絶対パスに変換された、先頭にスラッシュが付いている値
*/
$path_info=$pi_urldecode_absolute;
if(!$slash_start_query){
$path_info='/'.$path_info;
}
}
$_SERVER['PATH_INFO']=$path_info;
}
});
より簡潔な手法
上記で提示した方法では、$_SERVER['REQUEST_URI']
からPATH_INFOを取得し、$_SERVER['QUERY_STRING']
と$_GET
からPATH_INFOに相当するクエリストリングを削除しています。
ここで、PATH_INFOをクエリストリングに追加しないようにすれば、$_SERVER['QUERY_STRING']
と$_GET
から削除する処理が不要になり、簡潔になるのではないか?
先程、そう思いつきました。
以下では、その考えによるコードを書き留めておきます。
注意
- 以下のコードは、不完全かもしれません。利用前によく検証してください。
コード
RewriteEngine on
RewriteCond $1 !^(index\.php|images|robots\.txt)
RewriteRule ^(.+)$ /index.php [L]
call_user_func(function(){
if(!isset($_SERVER['PATH_INFO'])){
if(isset($_SERVER['ORIG_PATH_INFO'])){
$path_info=$_SERVER['ORIG_PATH_INFO'];
}else{
$sn_sp=strrpos($_SERVER['SCRIPT_NAME'],'/index.php');
if($sn_sp===false){
$sn_sp=strrpos($_SERVER['SCRIPT_NAME'],'index.php');
}
if(isset($_SERVER['REDIRECT_URL'])){
$pi_urldecode=ltrim(substr($_SERVER['REDIRECT_URL'],$sn_sp),'/');
}else{
$pi_urlencode=ltrim(substr($_SERVER['REQUEST_URI'],$sn_sp),'/');
$pi =strstr($pi_urlencode,'?',true);
if($pi!==false){
$pi_urlencode=$pi;
}
/**
* REQUEST_URIから取得したPATH_INFOの値をURLデコード
*/
$pi_urldecode=implode(
'/',
array_map(
function($pp){
return urldecode($pp);
},
explode('/',$pi_urlencode)
)
);
}
/**
* 相対パスを変換
* @link http://webdesign-dackel.com/2014/06/12/php-relativepath-to-absolutepath/
*/
$new_pi=array();
foreach(explode('/',$pi_urldecode) as $pp){
if($pp==='.'){
array_shift($new_pi);
array_unshift($new_pi,'');
}elseif($pp==='..'){
array_pop($new_pi);
if(count($new_pi)===0){
$new_pi=array('');
}
}else{
$new_pi[]=$pp;
}
}
$pi_urldecode_absolute=implode('/',$new_pi);
/**
* 連続した"/"を1つに置換
*/
$pi_urldecode_absolute=preg_replace('|/{2,}|','/',$pi_urldecode_absolute);
/**
* PATH_INFOを取得
* PATH_INFOはURLデコードされ、絶対パスに変換された、先頭にスラッシュが付いている値
*/
$path_info=$pi_urldecode_absolute;
if(strncmp($path_info,'/',1)!==0){
$path_info='/'.$path_info;
}
}
$_SERVER['PATH_INFO']=$path_info;
}
});