gallu’s blog

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

所謂「URLの擬似静的化」についての諸々

うちとこの生徒さん(達)に質問されたのですが、ちょうど「どっかで書かないとなぁ」って思ってたネタでちょうど良いので。


URLの擬似静的化(または、URLの静的化)ってのは…んと…
http://example.com/program.php?param=param
ってなので動く動的なHTMLを、
http://example.com/program/param/
みたいな感じで動かしたい的なほにゃららです。


実際に見るところで…ECサイトだと(最近ECサイトやってるんで、ちょうど思いつくので)。
商品詳細のPageを見るのに
http://example.com/item_detail.php?item_id=0123
ってのを
http://example.com/item/detail/0123/
http://example.com/item/detail/0123.html
なんて感じにしておきたい的なほにゃらら。


先に「なんでンなことしたいの?」っていうと「SEO的に有利だっていう噂がまことしやかに叫かれているから」。
ぶっちゃけそもそも論としておいちゃん「SEO」にほぼ興味がないので*1、割と切り裂くような発言になりますが。
「えす、い〜お〜!!」って言ってる人達は、とても「URLの擬似静的化」を大切にしたり重要視したり珍重したりしてます。
エビデンスがほとんどないか、或いは「古いエビデンス」だけなんですがね(かつ、当然ながら「数値」とか「検証」とかってのを見た事がない)。
一方で「静的化? いらないんぢゃね?」的な話も、正直ちらほらと、googleさん辺りの方角からは。


ただまぁ「じゃぁシステム開発でそれを盛り込むのにどれくらいコストがかかるのか?」っていうと、知っていればそんなに「莫大なコスト」はかからないのと。
まぁ「信じちゃっている」方々の熱い情熱に水指すのもぶっちゃけ面倒なんで(ってここで書いてたら台無しだよねぇってのはおいといて)。
かかる費用をご負担いただけるのであれば「いいんぢゃね?」とか思うので、そんなに大きく反対するものでもございません。


というわけでまぁ「そもそも何でンなもんが欲しいの?」ってあたりがとりあえず片付いたところで、本題に入り込んでみましょう。


端的には
mod_rewriteなどによる「リクエストURL」の切った張った
・適当な「処理する子」をAbstract Factoryパターンその他的な感じで生成してcall
ってな感じの流れになります。
(昔は PATH_INFO なんてのもございましたのですがねぇ…めっきりmod_rewrite一本槍になった風潮が、なにやら寂しい想いが、全く無いのかと問われると、些か疑問を感じる瞬間も、ふと、あったりするものでございます)

リクエストURLの加工

まずは「リクエストURLの加工」から。
http://example.com/program/param/


ってアクセスしても、もしファイルがなければ当然ながら404で終わって終了です…って終わられると些か困りますので、解決します。
mod_rewrite(もしくはmod_rewrite相当の機能) というものがございましてどこぞで「スイス製のアーミーナイフ」とか「かなりイケてるっぽい黒魔術だが、やっぱり所詮は黒魔術である」とか色々と言われているもの*2がございます。
実際問題「切れ味いいし便利なんだけど乱用すると瞬時でヒドイ事になるので使うのはとても慎重に最低限」という…まぁいつもおいちゃんが話をしている「お道具は最低限を使う」って話ですねぇ。
これを使うと「入ってきたURLを加工できる」とかいう大変な優れものでございます。
細かい使い方は説明しないんで適宜ググれ。


まぁこれをつかって「URLを加工して」「404じゃないようにする」んですねぇ。
「アクセスを(大抵の場合)index.phpに集約」させたりするわけですつまり。


先に、各フレームワークのものを見てみましょう。
大体どこも「.htaccess に記載する」前提で書いています。まぁ「.htaccess嫌い*3」ってレベルの御仁なら「読み替えられる程度のスキルはある」と思われるので、その辺は適宜。
バージョンとか面倒なんで記載しませんが、半年〜1年弱くらい古いバージョンのを見てるので(前に調査したのがあるのでそのまま流用)、その辺加味した上でご覧下さりませ。
あと、有効行だけ切り出しているのも注意。<IfModule mod_rewrite.c>を省略しているのも注意。
よくわからない人は一端読み飛ばしてください。ほんのりと解説っぽいものを下に書いてるんで。


symfony 1系

RewriteEngine On
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]


symfony 2系

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ app.php [QSA,L]


cakephp

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]


fuelPHP

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]


Zend Framework(多分1系)

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]


MagicWeapon(やりたい時の推奨予定の書式)

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-s
RewriteRule ^ index.php [L]


ちなみに、cakePHP

RewriteEngine on
RewriteRule ^$ webroot/ [L]
RewriteRule (.*) webroot/$1 [L]

RewriteEngine on
RewriteRule ^$ app/webroot/ [L]
RewriteRule (.*) app/webroot/$1 [L]

が割と面白いかなぁ、っと。
「共有サーバの事とかを想定している」んだと思うので。


閑話休題


大まか「3つの機能っつか文法」を確認しつつ、状況を理解していきましょう。
まず

RewriteEngine on

これ。
これがないと「ナイフが鞘に入っていて、術士がワンドを持っていない状態」なので、動きませんので必ず書きましょう。


次に出てくるのが「RewriteCond」と「RewriteRule」。


まず「RewriteCond」は簡単に書くと「if文」。
ここに書かれている条件が「falseだったらこの時点で処理終了」となります。
なので「書き換えたくないURL、の時の条件」があるんなら書く感じですねぇ。


「RewriteRule」は書き換えのルール。
「こんな風に書き換えて」ってお願いをします。


ちなみに「httpでのアクセスを一切弾くっていうか転送する」ような場合、こんなのを書いておくと楽っちゃぁ楽です。

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [NC,R,L]

これは
・%{HTTPS} !=on(もしhttpsがonじゃなかったら:ようはhttp://によるアクセスだったら)
・「^.*$」の条件に当てはまるURLを(正規表現で^と$の間が.*なんでようは「全部」)、https://%{HTTP_HOST}%{REQUEST_URI} [NC,R,L] に書き換えて
って感じです。
%{変数名}はまぁ色々なものが入ってくるのですが、興味があったら各自調査。


閑話休題(多いな今回…)


さて。
http://example.com/program/param/
を、今回とりあえず「DocumentRoot直下の index.php」に向けたい、と考えますと。
端的には、こうなります。

RewriteEngine On
RewriteRule ^ index.php [L]

RewriteRuleの[L]は「ここで変換終了!」って意味あいなので、今回はあんまり意味は無いですが、事故防止でよく使われます。
またRewriteRuleですが、正直、「^.*$」も「^(.*)$」も「^」も意味は一緒で「確実になんであろうがマッチする」ので、文字数少ない方が好みなんで雑に書いてます。


さて。これを実際に仕込んでみるとわかるのですが、あっという間に「画像ファイルもCSSJavaScriptファイルもなんにも見れなく」なります。
そりゃそうですなDocumentRootでこれ入れたら「あらゆるURLのアクセス先をindex.phpにする」ので、あらゆるの中には、画像ファイルもCSSファイルも全部、一切合切含まれますから B-p


なので、各フレームワークとも微妙に差異があるのですが、
・REQUEST_FILENAMEがディレクトリでなければ
・REQUEST_FILENAMEがシンボリックリンクでなければ
・REQUEST_FILENAMEがファイルでなければ
って形で「実在しないアクセスなら」って条件を、RewriteCond 使って指定しているわけなのですね。
これによって「画像ファイルとか、あるファイルならわざわざURLの書き換えとかしねぇよ」ってなるわけです。


故に、色々違いますが、大雑把には

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-s
RewriteRule ^ index.php [L]

ってな感じになるわけです。
特にRewriteRuleの、[]の中の細かいこだわりの有無は、有り無しともに「その人達の考え方」が見て取れて面白いですねぇ、っと。


かくして、存在しない
http://example.com/program/param/
が、無事に
http://example.com/index.php
に書き換えられた結果、index.phpが晴れ晴れしくcallされるわけですな。

index.phpでやるべき最低限の処理

さて。
index.phpを叩いていただいたのはいいのですが「実際にはどんなURLで来てるのよ?」ってのがわからないと以降処理がでけないので、どうにかします。


fuelの

RewriteRule ^(.*)$ index.php/$1 [L]

この系の書式のほうが、少なくとも昔は、多かったように記憶しているのですが…大体10年弱くらい前。
最近は

RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule ^(.*)$ app.php [QSA,L]
RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule ^.*$ index.php [NC,L]
RewriteRule ^ index.php [L]

ってな感じでいずれも「URLの大本のパス名の情報とは設定もしてくれやがらねぇ」感じなので、自力でなんとかしましょう。


まぁ端的には

$_SERVER['REQUEST_URI']

で「本来のURL」なんざ余裕で把握が可能なので。
あとは自力で切った張った

その後はぶっちゃけ設計次第。


まずは自分の環境で適宜

var_dump($_SERVER['REQUEST_URI']);

とか書いてみて、それに対して自分が「どーゆー処理を書きたいのか」を考えて妄想して設計してから、ですな。


ちなみに、最近の多くの、本当に多くのPHPフレームワーク
http://example.com/コントローラ名/アクション名/
ってなってるので。
そうすると、超絶壮絶雑にソレっぽく書くと

$awk = explode('/', $_SERVER['REQUEST_URI']);
クラス名 = $awk[1];
メソッド名 = $awk[2];
//
$obj = new クラス名();
$obj->メソッド名();

こんな感じの処理になるわけです。
ちなみに上のコードのまま書くと、びっくりするくらいバグるしセキュリティホールあるから「自力で組もうとかすんな」って感じではあるのですが。
超絶大雑把には「こーゆー処理になってる事が多いよ」的なイメージでとらえてくだしゃんせ。


なんか質問とかありましたら、適宜連絡などいただければ。

*1:某社で一時期ご一緒だった、とある女史…ってには若い女性だたのですが、彼女のSEOが、おいちゃん的には今のところ「唯一」許容も共感も出来るSEOでしたねぇ、ってのは懐かしいお話です

*2: http://net-newbie.com/trans/mod_rewrite.html

*3:例:おいちゃん