PHP 5.3 : RecursiveDirectoryIteartor の current が SplFileInfo を返すから isDot とか使えない
夜も遅くにこんばんは.
SPL関連でいうと,ちょっと調べてたら妙な挙動を発見した.
DirectoryIterator
DirectoryIterator は,ディレクトリをイテレートするためのイテレータで,例えば,以下のようなコードで,ディレクトリの中身を出力することができる.以下はマニュアルに載っているコード.*1
<?php $it = new DirectoryIterator(dirname(__FILE__)); foreach ($it as $f) { if (!$f->isDot()) { echo $f->getFilename() . "\n"; } }
ところで,このクラスは,SplFileInfo を継承しているので,SplFileInfoの持っているメソッドは全部使える,が,SplFileInfo には isDot というメソッドがないため,どうやら DirectoryIterator で実装されている.
RecursiveDirectoryIterator
で,DirecoryIterator を継承している(はず ((http://jp2.php.net/manual/ja/class.recursivedirectoryiterator.php))) RecursiveDirectoryIterator でも同じことができるよねってことで,やってみたところエラーがでた.
<?php $it = new RecursiveDirectoryIterator(dirname(__FILE__)); foreach ($it as $f) { if (!$f->isDot()) { echo $f->getFilename(), "\n"; } }
Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/test.php on line 12
おうふ?
どうやら RecursiveDirectoryIterator の current が,自分自身ではなく,SplFileInfo を返してきているから isDot なんてないよってエラーになる.
RecursiveDirectoryIterator が本当に継承しているもの
は,実は DirectoryIterator ではなく,FilesystemIterator でした.
2668 REGISTER_SPL_SUB_CLASS_EX(RecursiveDirectoryIterator, FilesystemIterator, spl_filesystem_object_new, spl_RecursiveDirectoryIterator_functions); 2669 REGISTER_SPL_IMPLEMENTS(RecursiveDirectoryIterator, RecursiveIterator);
で,この変更は,PHP 5.3 からのもの.FilesystemIterator は 5.3 で入りましたからね.FilesystemIterator の current は,フラグによってその返り値が変わるんだけど,このデフォルトが CURRENT_AS_FILEINFO になっているので,SplFileInfo が返ってきてしまう.
なので,望みどおりの動きをさせるには,
<?php $it = new RecursiveDirectoryIterator(dirname(__FILE__), RecursiveDirectoryIterator::CURRENT_AS_SELF); foreach ($it as $f) { if (!$f->isDot()) { echo $f->getFilename(), "\n"; } }
としてあげればいいんですけど,この情報はとりあえずマニュアルにも書いてなかったし,後方非互換の変更なんじゃないかなあ,などと思いつつソースの履歴を追ってみた.
最初は,当然 PHP 5.3 になって, FilesystemIterator が導入されたからそいつを継承させるように変更したら,FilesystemIterator の current がデフォルトで CURRENT_AS_FILEINFO なので,DirectoryIterator と挙動がかわってしまった ... !という類の,ボンミスかとおもった.
が,調べてみたらちょっと違う気がしてきた.
この変更がどこで入ったか
実は,phpallで検証すると,PHP 5.1.2 からこんな挙動になっている.
<?php $directory = '.'; $itt = new RecursiveDirectoryIterator($directory); foreach ($itt as $i) { $i->isDot(); break; }
% phpall rd_test.php 2>/dev/null php-5.0.0: php-5.0.1: php-5.0.2: php-5.0.3: php-5.0.4: php-5.0.5: php-5.1.0: php-5.1.1: php-5.1.2: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.1.3: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.1.5: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.1.6: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.0: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.1: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.2: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.3: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.4: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.5: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.6: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.8: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.9: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.10: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.2.11: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 php-5.3.0: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 Call Stack: 0.0005 333208 1. {main}() /home/sotarok/working/php/spl/rd_test.php:0 php-5.3.1: Fatal error: Call to undefined method SplFileInfo::isDot() in /home/sotarok/working/php/spl/rd_test.php on line 26 Call Stack: 0.0003 333208 1. {main}() /home/sotarok/working/php/spl/rd_test.php:0
なんでだろうと思って一応,継承元の変更点がいつなのかも調べてみた.
<?php $directory = '.'; $itt = new RecursiveDirectoryIterator($directory); var_dump(class_parents($itt));
% phpall rd_test.php 2>/dev/null [~/working/php/spl (master)] php-5.0.0: array(1) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" } php-5.0.1: array(1) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" } php-5.0.2: array(1) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" } php-5.0.3: array(1) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" } php-5.0.4: array(1) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" } php-5.0.5: array(1) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" } php-5.1.0: array(1) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" } php-5.1.1: array(1) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" } php-5.1.2: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.1.3: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.1.5: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.1.6: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.0: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.1: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.2: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.3: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.4: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.5: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.6: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.8: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.9: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.10: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.2.11: array(2) { ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.3.0: array(3) { ["FilesystemIterator"]=> string(18) "FilesystemIterator" ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" } php-5.3.1: array(3) { ["FilesystemIterator"]=> string(18) "FilesystemIterator" ["DirectoryIterator"]=> string(17) "DirectoryIterator" ["SplFileInfo"]=> string(11) "SplFileInfo" }
んー.一応継承元は 5.3 から FilesystemIterator に切り替わったみたいなんだけど,5.1.2 で SplFileInfo を基底にもつようになってから current の挙動が変わったんですかね.そのうえ,継承元が変わったのは 5.3 だからあたかもこの CURRENT_AS_FILEINFO や CURRENT_AS_SELF が FilesystemIteraotr の定数かのように見えるけど,実は,↑のCURRENT_AS_SELFフラグをつけたものは, PHP 5.1.2 から動く (つまり SplFileInfoに関する変更が入ったときから動いてる).
ただし,5.3.0 でこの定数定義が RecursiveDirectoryIterator のものではなく FilesystemIterator のものに変更されている.
どのへんが問題なのかよくわからないから一旦まとめ
- とりあえずドキュメントの情報は正しくない
- RecursiveIteratorIterator が DirectoryIterator を継承してるという情報は, 5.3.0 からは,偽.実際は FilesystemIterator
- RecursiveIteartorIterator には 5.1.2 からいくつかフラグが用意されていた!
cf.
% php-5.1.2 --re spl ... Class [ <internal:SPL> <iterateable> class RecursiveDirectoryIterator extends DirectoryIterator implements Traversable, Iterator, RecursiveIterator ] { - Constants [8] { Constant [ integer CURRENT_MODE_MASK ] { } Constant [ integer CURRENT_AS_PATHNAME ] { } Constant [ integer CURRENT_AS_FILEINFO ] { } Constant [ integer CURRENT_AS_SELF ] { } Constant [ integer KEY_MODE_MASK ] { } Constant [ integer KEY_AS_PATHNAME ] { } Constant [ integer KEY_AS_FILENAME ] { } Constant [ integer NEW_CURRENT_AND_KEY ] { } } ...
- RecursiveDirectoryIterator が SplFileInfo をデフォルトで返すという挙動は正しいのか (CURRENT_AS_SELF じゃないと isDot とかつかえねーじゃん.*2 )
- ちょっとわからん.5.1.2 の変更のときにそういうバグとか報告なかったのか,ちらっと探してみたんだけど,みあたらなかった*3.仕様だとしたらしかたないんだけど,バグだったら ... 5.1.2 って 2006 年だよ,でたの ...
というわけで,これが,ダメだと思う人〜?
バグ報告すべきかな?