3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ZYYXAdvent Calendar 2022

Day 16

RecursiveRegexIteratorでファイル名を前方一致で絞り込む

Last updated at Posted at 2022-12-15

はじめまして、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
RecursiveDirectoryIteratorのみ使用
<?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',
    )
*/
?>
RecursiveDirectoryIterator + RecursiveRegexIteratorを使用
<?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
RecursiveRegexIteratorで前方一致検索
<?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でファイル名で検索できるようになります。

FileSystemIterator::CURRENT_AS_SELFフラグを使用する
<?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を用いた時に前方一致でファイル名検索を行うには、

  1. ファイルの絶対パス + 検索したい文字列で検索を行う
  2. RecursiveDirectoryIteratorでFilesystemIterator::CURRENT_AS_SELFフラグを使用する

の二つの方法があることがわかりました。

最後まで読んでいただきありがとうございました。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?