PHPのpathinfo()について質問です

PHPのpathinfo()について質問です

- Tatsuya Shirai の投稿
返信数: 8
ここはPHPの質問をする場所では無いのかも知れませんが,moodleに深く関わることであるのでご容赦願います.

以前からマルチバイトを含むパスに対するpathinfo()の挙動がなんとなくおかしいと感じており,自作のpathinfo()を作っていました.出来上がってそれをmoodleの改造に活用していたのですが,pathinfo()について詳しく知らないでいるのもいけないだろうと思い,PHPマニュアルのページや手持ちの書籍などを調べてみたのですが,みな同じサンプルばかりで参考になりません.

では,実際にどのように実装されているのか,それと同じ挙動をするマルチバイト対応のpathinfo()を作れば問題ないだろうと思い,添付したようなphpソースを作成し,Windows用PHP5.2.0でapache2経由で動かしてみました.以下がその結果です.


Case1(C:/xampplite/htdocs/index.html):dirname=[C:/xampplite/htdocs],basename=[index.html],extension=[html]
Case2(C:/xampplite/htdocs/):dirname=[C:/xampplite],basename=[htdocs],extension=[]
Case3(C:/xampplite/htdocs):dirname=[C:/xampplite],basename=[htdocs],extension=[]
Case4(C:/):dirname=[C:\],basename=[C:],extension=[]
Case5(/):dirname=[\],basename=[],extension=[]
Case6():dirname=[],basename=[],extension=[]

見づらくて申し訳ありません.Case1からCase6まで試してみました.当方,Windowsの環境ですのでドライブレターを含んでいます.Case2とCase4! これで正しいのですか?! (もちろん,私の自作のpathinfo()ではなく正式なpathinfo()を使った結果です).

[Case2] 'C:/xampplite/htdocs/' :お尻に/が付いています.
dirname=[C:/xampplite]
basename=[htdocs]
extension=[]
この場合,basenameは''で,dirnameに'C:/xampplite/htdocs'と入るべきだと思うのですが...
[Case4] 'C:/':ルートディレクトリを示しています.
dirname=[C:\]
basename=[C:]
extension=[]
dirnameの/が\に変るのは愛嬌だとしても,basenameがC:になるのはどういうメカニズムなのか.dirnameが''ならばまだ分からなくは無いのですが.
パスの最後に/が付いていると妙なことになるのでしょうか.そこでCase7を追加しました.
Case7(C:):dirname=[C:],basename=[C:],extension=[] (顔文字に化けてしまうので全角'C:'にしましたが,実際には半角です)

頑なです.dirnameから\が消えただけです.ドライブレターがいけないの?
[Case2] '/xampplite/htdocs/':ドライブレターを取り除きました.
dirname=[/xampplite]
basename=[htdocs]
extension=[]

うーん,ベースネームなしという状況は無いのでしょうか.
basenameの含まれて居ないパス名が渡されたかどうか,pathinfo()で判断しようと考えるのプログラマは多いと思うのですが,strpos($path, strlen($path) - 1, 1) が '/'であるかどうかで判断しろということでしょうか?
-----
moodleのソースを見ていると,どうもパスの扱いで四苦八苦している形跡があちこちにあります.パス中の:を全て_に置換した後に,頭一文字が[A-Za-z]かつ二文字目が_だったらドライブレターなので:に戻す,といった具合ですね.これは仕様かバグなのか,世界中のヒトビトはどうしているのでしょうか....


Tatsuya Shirai への返信

Re: PHPのpathinfo()について質問です

- Tatsuya Shirai の投稿
ちなみにこちらが,マルチバイトの文字列を含むパスに対して,Windows版phpのpathinfo()の結果と,自作のfs_pathinfo()の実行結果です.

(オリジナルのpathinfo())
Case1(C:/xampplite/1/index.class.php):dirname=[C:/xampplite/1],basename=[index.class.php],extension=[php]
Case2(C:/xampplite/1/):dirname=[C:/xampplite],basename=[??],extension=[]
Case3(C:/xampplite/1):dirname=[C:/xampplite],basename=[??],extension=[]
Case4(C:/):dirname=[C:\],basename=[C:],extension=[]

(自作のfs_pathinfo())
Case1(C:/xampplite/1/index.class.php):dirname=[C:/xampplite/1],basename=[index.class.php],extension=[php]
Case2(C:/xampplite/1/):dirname=[C:/xampplite/1],basename=[],extension=[]
Case3(C:/xampplite/1):dirname=[C:/xampplite],basename=[1],extension=[]
Case4(C:/):dirname=[C:],basename=[],extension=[]

フォルダ名に”1”を入れただけで化けます.内部処理のコード設定がおかしいならば自作のfs_pathinfo()で使用しているmb_split()もおかしな動作をするはずなので,mbstring系以外の関数の大半はマルチバイトに対応できていない(preg_replace()なんかはUTF-8に対応しているようですが),あるいは対応しない仕様ということでしょうか.

それにしてもドライブレター 'C:/'を与えた時のdirnameに\が付くのは変ですね.dirnameに分類された文字列にパス記号を付ける,というルールでコーディングした際に,ここだけC:\/となってしまいますね.(ダブった//を取り除く関数がmoodle内にも存在しますが...)

自作のfs_pathinfo()は狙った出力を得られていますので,moodleの改造にはこれを用いています.ちなみに同じ理由から,basename()やdirname()と云った関数も全てfs_basename(),fs_dirname()といった自作の関数に置き換える必要がありますので,結構,大変です.


フォルダ構造ごとコピーする関数が存在しない,フォルダー構造ごと削除する関数が存在しない(moodle内ではfulldelete()関数が用意されている)など,Unix環境ですと外部コマンドを呼び出す文化ですので問題は無いと思いますが,Windowsを前提とするとそれらの関数も作る必要があって,結構大変ですヘトヘト
Tatsuya Shirai への返信

Re: PHPのpathinfo()について質問です

- Tatsuya Shirai の投稿
あまり関係ないのですが,pathinfo(),マルチバイトのキーワードで検索をかけていたら,mbstringエミュレータというものを発見しました.きっとその道では有名なのでしょうね.

https://sourceforge.jp/projects/mbemulator/

mbstringがインストールされていないサーバでmbstring系の関数を動作可能とする(同じ名前の関数を定義しているのかな?)もののようです.


同じ問題に当たっていると思われる人を発見.
しかし原因と対策は「日本語ファイル名は使わない方が良い」ですね悲しい
http://www.gac.jp/article/index.php?stats=question&category=11&id=17576&command=msg


どたなか,Windows以外の環境の方,上に添付しましたtest.phpとtest2.phpの実行結果を教えて頂けないでしょうか.pathinfo()の謎の動作が,もしかしたらWindows版だけであって,Linux上では/Mac OS上では私が考えているように動作する,という可能性があり,とても気になります.もしWindows以外の環境でも同じ出力結果であるならば,私の理解/常識に誤りがあるということですね...

Tatsuya Shirai への返信

Re: PHPのpathinfo()について質問です

- Yuichi Saotome の投稿
五月女です.

>どたなか,Windows以外の環境の方,上に添付しましたtest.phpとtest2.phpの実行結果を教えて頂けないでしょうか.

参考になるかわかりませんが,
-FreeBSD 6.2-RELEASE-p4 i386 PHP5.2.3
-CentOS 4.4 2.6.9-42.ELsmp i386 PHP5.1.6
にて実行してみました.

以下実行結果です.
-test.php by FreeBSD 6.2-RELEASE-p4 i386 PHP5.2.3
Case1(C:/xampplite/htdocs/index.html):dirname=[C:/xampplite/htdocs],basename=[index.html],extension=[html]

Notice: Undefined index: extension in /home/meshi/public_html/test.php on line 15
Case2(C:/xampplite/htdocs/):dirname=[C:/xampplite],basename=[htdocs],extension=[]

Notice: Undefined index: extension in /home/meshi/public_html/test.php on line 18
Case3(C:/xampplite/htdocs):dirname=[C:/xampplite],basename=[htdocs],extension=[]

Notice: Undefined index: extension in /home/meshi/public_html/test.php on line 21
Case4(C:/):dirname=[.],basename=[C:],extension=[]

Notice: Undefined index: extension in /home/meshi/public_html/test.php on line 24
Case5(/):dirname=[/],basename=[],extension=[]

Notice: Undefined index: dirname in /home/meshi/public_html/test.php on line 27

Notice: Undefined index: extension in /home/meshi/public_html/test.php on line 27
Case6():dirname=[],basename=[],extension=[]

Notice: Undefined index: extension in /home/meshi/public_html/test.php on line 30
Case7(C:):dirname=[.],basename=[C:],extension=[]
-test.php by CentOS 4.4 2.6.9-42.ELsmp i386 PHP5.1.6
 Case1(C:/xampplite/htdocs/index.html):dirname=[C:/xampplite/htdocs],basename=[index.html],extension=[html]
Case2(C:/xampplite/htdocs/):dirname=[C:/xampplite],basename=[htdocs],extension=[]
Case3(C:/xampplite/htdocs):dirname=[C:/xampplite],basename=[htdocs],extension=[]
Case4(C:/):dirname=[.],basename=[C:],extension=[]
Case5(/):dirname=[/],basename=[],extension=[]
Case6():dirname=[],basename=[],extension=[]
Case7(C:):dirname=[.],basename=[C:],extension=[]
-test2.php by FreeBSD 6.2-RELEASE-p4 i386 PHP5.2.3
Case1(C:/xampplite/・・index.class.php):dirname=[C:/xampplite/・曽,basename=[index.class.php],extension=[php]

Notice: Undefined index: extension in /home/meshi/public_html/test2.php on line 30
Case2(C:/xampplite/・・):dirname=[C:/xampplite],basename=[・曽,extension=[]

Notice: Undefined index: extension in /home/meshi/public_html/test2.php on line 33
Case3(C:/xampplite/・・:dirname=[C:/xampplite],basename=[・曽,extension=[]

Notice: Undefined index: extension in /home/meshi/public_html/test2.php on line 36
Case4(C:/):dirname=[.],basename=[C:],extension=[]

Case1(C:/xampplite/・・index.class.php):dirname=[C:/xampplite/・曽,basename=[index.class.php],extension=[php]
Case2(C:/xampplite/・・):dirname=[C:/xampplite/・曽,basename=[],extension=[]
Case3(C:/xampplite/・・:dirname=[C:/xampplite],basename=[・曽,extension=[]
Case4(C:/):dirname=[C:],basename=[],extension=[]
-test2.php by CentOS 4.4 2.6.9-42.ELsmp i386 PHP5.1.6
Case1(C:/xampplite/・・index.class.php):dirname=[C:/xampplite/・曽,basename=[index.class.php],extension=[php]
Case2(C:/xampplite/・・):dirname=[C:/xampplite],basename=[xampplite],extension=[]
Case3(C:/xampplite/・・:dirname=[C:/xampplite],basename=[xampplite],extension=[]
Case4(C:/):dirname=[.],basename=[C:],extension=[]

Case1(C:/xampplite/・・index.class.php):dirname=[C:/xampplite/・曽,basename=[index.class.php],extension=[php]
Case2(C:/xampplite/・・):dirname=[C:/xampplite/・曽,basename=[],extension=[]
Case3(C:/xampplite/・・:dirname=[C:/xampplite],basename=[・曽,extension=[]
Case4(C:/):dirname=[C:],basename=[],extension=[]
Yuichi Saotome への返信

Re: PHPのpathinfo()について質問です

- Tatsuya Shirai の投稿
ありがとうございました.
・Linux版でも,パスの最後に/がついていても,最後のパスの要素をbasenameと認識する.
・basenameやextensionが存在しないようなパスを渡された場合に,各該当するarrayを空で返すのではなく,セットしない(したがってNotice発生)実装もある.
ということが分かりました.

pathinfo()がマルチバイトに対応していないかどうかの確証は難しいですね(ほぼ確実に非対応だと思うのですが,明言している文書を発見できていない).というのは,五月女さんの実行結果でも確かにbasenameやdirnameが化けていますが,Case()のカッコの中でも化けていますよね.このカッコ内はセットした変数をそのまま表示しているだけですので,五月女さんのお使いの環境でデフォルトの文字セット(出力か,内部エンコードか)がUTF-8ではないのかも知れません(tes2.phpはUTF-8でエンコードしてあります).お使いの環境に合わせて確認し直して頂けると助かります.ちなみに”1”,”2”(全角)は良く誤動作するのでサンプルには最適です.
------------
なんとか来週末までには日本語ファイル名/フォルダ名を使用できるパッチ(fs_converter)の情報をまとめようと思います.いま手元で動かしていますが,Windowsのmoodleサイトがグッと使い易くなります.確証はありませんが,LinuxやMac OSのサイトにとってもメリットがあるかも知れません.

Tatsuya Shirai への返信

Re: PHPのpathinfo()について質問です

- Yuichi Saotome の投稿
五月女です.

> pathinfo()がマルチバイトに対応していないかどうかの確証は難しいですね

コードも確認せずにただ実行しただけでした.
大変もうしわけありません.
.htaccessにでdefault_charset = "UTF-8"にして再度実行してみました.

以下結果です.
------------------------------------------------------------------------------------------------
test2.php by FreeBSD 6.2-RELEASE-p4 i386 PHP5.2.3
Case1(C:/xampplite/1/index.class.php):dirname=[C:/xampplite/1],basename=[index.class.php],extension=[php]

Notice: Undefined index: extension in /home/meshi/public_html/test2.php on line 30
Case2(C:/xampplite/1/):dirname=[C:/xampplite],basename=[1],extension=[]

Notice: Undefined index: extension in /home/meshi/public_html/test2.php on line 33
Case3(C:/xampplite/1):dirname=[C:/xampplite],basename=[1],extension=[]

Notice: Undefined index: extension in /home/meshi/public_html/test2.php on line 36
Case4(C:/):dirname=[.],basename=[C:],extension=[]

Case1(C:/xampplite/1/index.class.php):dirname=[C:/xampplite/1],basename=[index.class.php],extension=[php]
Case2(C:/xampplite/1/):dirname=[C:/xampplite/1],basename=[],extension=[]
Case3(C:/xampplite/1):dirname=[C:/xampplite],basename=[1],extension=[]
Case4(C:/):dirname=[C:],basename=[],extension=[]


-test2.php by CentOS 4.4 2.6.9-42.ELsmp i386 PHP5.1.6
Case1(C:/xampplite/1/index.class.php):dirname=[C:/xampplite/1],basename=[index.class.php],extension=[php]
Case2(C:/xampplite/1/):dirname=[C:/xampplite],basename=[xampplite],extension=[]
Case3(C:/xampplite/1):dirname=[C:/xampplite],basename=[xampplite],extension=[]
Case4(C:/):dirname=[.],basename=[C:],extension=[]

Case1(C:/xampplite/1/index.class.php):dirname=[C:/xampplite/1],basename=[index.class.php],extension=[php]
Case2(C:/xampplite/1/):dirname=[C:/xampplite/1],basename=[],extension=[]
Case3(C:/xampplite/1):dirname=[C:/xampplite],basename=[1],extension=[]
Case4(C:/):dirname=[C:],basename=[],extension=[]
------------------------------------------------------------------------------------------------

> なんとか来週末までには日本語ファイル名/フォルダ名を使用できるパッチ(fs_converter)の情報をまとめようと思います.
実は私も同じようなことをやっていたりします.
といっても私の場合はファイル名からパスから,すべてUTF-8に変更するようにして対応しています.
ファイルのアップロード・ダウンロードやzip展開・圧縮など一通り変更したんですが,Mac OS XのUTF-8-MACへの対応がうまくいっていません.
文字コードの問題って難しいです.
Tatsuya Shirai への返信

Re: PHPのpathinfo()について質問です

- Takayuki Fukuyama の投稿

福山です

phpのpathinfo()はファイル名の末尾に「/」があろうとなかろうと同じ挙動をするという仕様だと思います。従いまして

  • C:/xampplite/htdocs/
  • C:/xampplite/htdocs

は結果が等価となります。もっと言うとこれも等価になるはずです

  • C:/xampplite/htdocs//////

C:/ に関してはwindows版とlinux版では解釈が違うようですね。dirname=[C:\],basename=[C:]とありますが、linuxだとこんな値が返ります

Array
(
    [dirname] => .
    [basename] => C:
    [filename] => C:
)

ドライブレターをルートにもってきた場合PHPの中でいろいろ弄っているような感じがあります。linuxだとドライブレター関係なく単純に「C:」というファイル名と見做しますから

  • C:/
  • C:
  • C:///

これらはみな等しい値をreturnしてきます。C:ではなくCにしても同じ事(コロンがある/ないの違い)になります。

さて、この仕様ですが、基本的にこの仕様はlinuxなどのシステムに存在しているbasenameコマンドdirnameコマンドを参考にして組まれているのだと思っています(ただしwindowsの場合は変換したりと、これに完全に準拠しているというよりは、PHP側でいろいろと手をいれてるのは間違いないようです。)これらのコマンドが返す値が、最後のパス記号によって変わらないのもPHPと同じ仕様です。たとえばbasenameコマンドやdirコマンドの出力を以下に貼り付けてみます。

 

$ basename C:/xampplite/htdocs/ 
htdocs
$ dirname C:/xampplite/htdocs/
C:/xampplite


$ basename C:/xampplite/htdocs  
htdocs
$ dirname C:/xampplite/htdocs 
C:/xampplite

$ basename C:/xampplite/htdocs//////
htdocs
$ dirname C:/xampplite/htdocs//////
C:/xampplite

日本語が壊れる現象も確認しました。こちらはどうやらPHPのバグと見るべきですね。ただしPHP 4.4.4-8+etch6というバージョンでは成功、PHP 5.2.0-8+etch13ではbasenameが落ちる、PHP 5.2.6-1+lenny2というバージョンでは完全に失敗(文字化け)します。(バージョンの後についているコード名はdebianパッケージの改編番号なので多少異なっている(セキュリティをパッチしていたりする)のでしょうが根本的には同じものです。いろいろとバージョンが上がるにつれ、変わっていってしまっているようです。

Takayuki Fukuyama への返信

Re: PHPのpathinfo()について質問です

- Tatsuya Shirai の投稿

 PHPのバージョンの違いによる挙動の変化には戸惑うことが多いですね...

 とりあえず私は自作のpathinfo()を使うことで乗り切っていますが,このバージョンの違いの影響をMoodleが真面目に”PHPのバージョンに合わせて処理を変える”とか,”裏技で何とかする”を行うことに怖さを感じます.ファイルが読めない/書けないだけならば良いのですが,読めてはいけないところが読めてしまったでは困りますからね.Trackerに提案した方が良いのだろうか.

 とりあえずfs_moodleでは,添付したようなjp_lib.phpという自作のライブラリを使用しています.

 マルチバイト対応のstr_replace()なんて作っていたのですね,忘れていました...