はじめまして、ZYYX新卒入社1年目のエンジニアです。
今回はPHPのRecursiveRegexIterator
でファイル名を前方一致検索した時に、躓いた点を共有したいと思います。
検証環境
- macOS Ventura 13.0.1
- PHP 8.1.6
- Xdebug 3.1.6
- Visual Studio Code 1.74.0
$ sw_vers
ProductName: macOS
ProductVersion: 13.0.1
BuildVersion: 22A400
$ php -v
PHP 8.1.6 (cli) (built: May 16 2022 02:55:18) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.6, Copyright (c) Zend Technologies
with Xdebug v3.1.6, Copyright (c) 2002-2022, by Derick Rethans
$ osascript -e 'version of app "Visual Studio Code"'
1.74.0
そもそもRecursiveRegexIteratorって何?
phpマニュアルには以下のように書かれています。
この再帰イテレータは、別の再帰イテレータを正規表現でフィルタリングすることができます。
PHP RecursiveRegexIterator - Manual
つまり、以下のようにRecursiveDirectoryIterator
などで取得した要素から、正規表現にマッチする要素を取り出すことができます。
参考リンク:PHP RecursiveDirectoryIterator - Manual
\- __DIR__
\- search
|- fileA
|- fileB
|- fileC
|- fileD
\- fileE
<?php
const BASE_DIR = __DIR__ . "/search";
$result = [];
$directoryIterator = new RecursiveDirectoryIterator(BASE_DIR, FilesystemIterator::SKIP_DOTS);
// 取得した要素名を出力
foreach ($directoryIterator as $file) {
$result[] = $file->getFileName();
}
var_export($result)
/* 実行結果
array (
0 => 'fileD',
1 => 'fileC',
2 => 'fileB',
3 => 'fileE',
4 => 'fileA',
)
*/
?>
<?php
const BASE_DIR = __DIR__ . "/search";
$result = [];
$directoryIterator = new RecursiveDirectoryIterator(BASE_DIR, FilesystemIterator::SKIP_DOTS);
// 正規表現で絞り込みを行う
$regex = "/fileA/";
$regexIterator = new RecursiveRegexIterator($directoryIterator, $regex);
// 取得した要素名を出力
foreach ($regexIterator as $file) {
$result[] = $file->getFileName();
}
var_export($result)
/* 実行結果
array (
0 => 'fileA',
)
*/
?>
RecursiveRegexIterator
を使用することにより、search
ディレクトリに存在する5つのファイルのうち、正規表現(fileA
)にマッチした1つのファイルを取得することができました。
本題
RecursiveDirectoryIterator + RecursiveRegexIteratorでファイル名の前方一致検索ができない
それでは、RecursiveRegexIterator
を使用してファイル名を前方一致で絞り込みをしてみましょう。
今回はファイル名がsearch_
で始まるファイルを取得します。
search_fileA
ファイルのみが取得できるはずです。
\- __DIR__
\- search
|- search_fileA
|- not_match_search_fileB
|- not_match_search_fileC
|- not_match_search_fileD
\- not_match_search_fileE
<?php
const BASE_DIR = __DIR__ . "/search";
$result = [];
$directoryIterator = new RecursiveDirectoryIterator(BASE_DIR, FilesystemIterator::SKIP_DOTS);
// 正規表現で絞り込みを行う
$regex = "/^search_/";
$regexIterator = new RecursiveRegexIterator($directoryIterator, $regex);
// 取得した要素名を出力
foreach ($regexIterator as $file) {
$result[] = $file->getPathName();
}
var_export($result);
/* 実行結果
array ()
*/
?>
取得できませんでした...orz
原因
上記のような絞り方をRecursiveRegexIterator
で行うと期待した結果を得ることができません。
これはRecursiveRegexIterator
でチェックする文字列がデフォルトではファイル名ではなくファイルの絶対パスであることが原因です。
解決策1(ファイルの絶対パスで検索を行う)
解決策の1つ目は、ファイルの絶対パスを検索する文字列として指定する方法です。
以下のように正規表現にファイルの絶対パス + 抽出したい文字列
を指定すると期待した結果を得ることができました。
\- __DIR__
\- search
|- search_fileA
|- not_match_search_fileB
|- not_match_search_fileC
|- not_match_search_fileD
\- not_match_search_fileE
<?php
const BASE_DIR = __DIR__ . "/search";
$result = [];
$directoryIterator = new RecursiveDirectoryIterator(BASE_DIR, FilesystemIterator::SKIP_DOTS);
// 正規表現で絞り込みを行う
$regex = "{^" . BASE_DIR . "/search_}";
$regexIterator = new RecursiveRegexIterator($directoryIterator, $regex);
// 取得した要素名を出力
foreach ($regexIterator as $file) {
$result[] = $file->getFileName();
}
var_export($result);
/* 実行結果
array (
0 => 'search_fileA',
)
*/
?>
使用できるデリミタ(区切り文字)
PHPでデリミタには英数字、バックスラッシュ・空白文字以外の任意の文字
が使用できます。
つまり、{}, <>, ##, []
などがデリミタとして使用することができます。
参考URL: PHP デリミタ - Manual
解決策2(検索をファイル名で行うようにする)
解決策2つ目はFilesystemIterator::CURRENT_AS_SELFフラグを使用する方法です。
デフォルトでは、FilesystemIterator::current()
が SplFileInfo オブジェクトを返すようになっていますが(FilesystemIterator::CURRENT_AS_FILEINFO)、RecursiveDirectoryIterator
を返すようにすることにより、RecursiveRegexIterator
でファイル名で検索できるようになります。
<?php
const BASE_DIR = __DIR__ . "/search";
$result = [];
$directoryIterator = new RecursiveDirectoryIterator(
BASE_DIR,
FilesystemIterator::CURRENT_AS_SELF
);
// 正規表現で絞り込みを行う
$regex = "/^search_/";
$regexIterator = new RecursiveRegexIterator($directoryIterator, $regex);
// 取得した要素名を出力
foreach ($regexIterator as $file) {
$result[] = $file->getFileName();
}
var_export($result);
/* 実行結果
array (
0 => 'search_fileA',
)
*/
?>
このやり方だとRecursiveRegexIteratorから得られるオブジェクトはSplFileInfo
からRecursiveDirectoryIterator
に変化します。なので解決策1と同じオブジェクトを得ようとすると$file->getFileInfo();の処理を入れる必要があります。
まとめ
RecursiveDirectoryIterator
+ RecursiveRegexIterator
を用いた時に前方一致でファイル名検索を行うには、
-
ファイルの絶対パス + 検索したい文字列
で検索を行う - RecursiveDirectoryIteratorで
FilesystemIterator::CURRENT_AS_SELF
フラグを使用する
の二つの方法があることがわかりました。
最後まで読んでいただきありがとうございました。