web-dev-qa-db-ja.com

PHP)でRegexIteratorを使用する方法

PhpRegexIteratorを使用してディレクトリを再帰的にトラバースする方法の良い例をまだ見つけていません。

最終的には、ディレクトリを指定して、その中のすべてのファイルを特定の拡張子で検索したいと思うでしょう。たとえば、html/php拡張子のみを言います。さらに、.Trash-0、.Trash-500などのタイプのフォルダーを除外したいと思います。

<?php 
$Directory = new RecursiveDirectoryIterator("/var/www/dev/");
$It = new RecursiveIteratorIterator($Directory);
$Regex = new RegexIterator($It,'/^.+\.php$/i',RecursiveRegexIterator::GET_MATCH);

foreach($Regex as $v){
    echo $value."<br/>";
}
?>

これまでのところ、次の結果になります。致命的なエラー:キャッチされない例外 'UnexpectedValueException'とメッセージ 'RecursiveDirectoryIterator :: __construct(/media/hdmovies1/.Trash-0)

助言がありますか?

22
Chris

このようなことを行うには、いくつかの異なる方法があります。2つの簡単なアプローチから選択できます。すばやく汚れたものと、長くて汚れが少ないものです(ただし、金曜日の夜なので、行くことができます)。少しクレイジー)。

1。速い(そして汚い)

これには、ファイルのコレクションを一挙にフィルタリングするために使用する正規表現(複数に分割される可能性があります)を記述するだけです。

(コメントされた2行だけがこの概念にとって本当に重要です。)

$directory = new RecursiveDirectoryIterator(__DIR__);
$flattened = new RecursiveIteratorIterator($directory);

// Make sure the path does not contain "/.Trash*" folders and ends eith a .php or .html file
$files = new RegexIterator($flattened, '#^(?:[A-Z]:)?(?:/(?!\.Trash)[^/]+)+/[^/]+\.(?:php|html)$#Di');

foreach($files as $file) {
    echo $file . PHP_EOL;
}

このアプローチには多くの問題がありますが、ワンライナーとして実装するのは簡単です(ただし、正規表現は解読するのが面倒かもしれません)。

2。速度が遅い(そして汚れが少ない)

より再利用可能なアプローチは、(正規表現などを使用して)いくつかの特注フィルターを作成し、最初のRecursiveDirectoryIteratorで使用可能なアイテムのリストを必要なものだけに絞り込むことです。以下は、RecursiveRegexIteratorを拡張するためのほんの一例です。

フィルタリングしたい正規表現を保持することが主な仕事である基本クラスから始めます。それ以外はすべてRecursiveRegexIteratorに延期されます。クラスはabstractであることに注意してください。これは、実際にはdo何も有用ではないためです。実際のフィルタリングは、2つのクラスによって実行されます。これを拡張します。また、FilesystemRegexFilterと呼ばれることもありますが、ファイルシステム関連のクラスをフィルタリングするように(このレベルで)強制するものはありません(それほど眠くない場合は、より適切な名前を選択しました)。

abstract class FilesystemRegexFilter extends RecursiveRegexIterator {
    protected $regex;
    public function __construct(RecursiveIterator $it, $regex) {
        $this->regex = $regex;
        parent::__construct($it, $regex);
    }
}

これらの2つのクラスは非常に基本的なフィルターであり、それぞれファイル名とディレクトリ名に作用します。

class FilenameFilter extends FilesystemRegexFilter {
    // Filter files against the regex
    public function accept() {
        return ( ! $this->isFile() || preg_match($this->regex, $this->getFilename()));
    }
}

class DirnameFilter extends FilesystemRegexFilter {
    // Filter directories against the regex
    public function accept() {
        return ( ! $this->isDir() || preg_match($this->regex, $this->getFilename()));
    }
}

これらを実践するために、以下はスクリプトが存在するディレクトリの内容を再帰的に繰り返し(これを自由に編集してください!)、.Trashフォルダを除外します(フォルダ名do match特別に細工された正規表現)、およびPHPおよびHTMLファイルのみを受け入れます。

$directory = new RecursiveDirectoryIterator(__DIR__);
// Filter out ".Trash*" folders
$filter = new DirnameFilter($directory, '/^(?!\.Trash)/');
// Filter PHP/HTML files 
$filter = new FilenameFilter($filter, '/\.(?:php|html)$/');

foreach(new RecursiveIteratorIterator($filter) as $file) {
    echo $file . PHP_EOL;
}

特に注目すべきは、フィルターは再帰的であるため、フィルターを反復処理する方法を試してみることができるということです。たとえば、次のようにすることで、最大2レベルの深さ(開始フォルダーを含む)のみをスキャンするように簡単に制限できます。

$files = new RecursiveIteratorIterator($filter);
$files->setMaxDepth(1); // Two levels, the parameter is zero-based.
foreach($files as $file) {
    echo $file . PHP_EOL;
}

さらに特殊なフィルタリングのニーズ(ファイルサイズ、フルパスの長さなど)に合わせて、さらに多くのフィルターを追加することも非常に簡単です(さまざまな正規表現でより多くのフィルタークラスをインスタンス化するか、新しいフィルタークラスを作成します)。

P.S.うーん、この答えは少しせせらぎます。私はそれをできるだけ簡潔に保つように努めました(スーパーバブルの広大な帯を取り除くことさえ)。最終的な結果が答えに一貫性を残さない場合はお詫びします。

51
salathe

ドキュメントは確かにあまり役に立ちません。ここで「一致しない」の正規表現を使用すると問題が発生しますが、最初に実際の例を示します。

_<?php 
//we want to iterate a directory
$Directory = new RecursiveDirectoryIterator("/var/dir");

//we need to iterate recursively
$It        = new RecursiveIteratorIterator($Directory);

//We want to stop decending in directories named '.Trash[0-9]+'
$Regex1    = new RecursiveRegexIterator($It,'%([^0-9]|^)(?<!/.Trash-)[0-9]*$%');

//But, still continue on doing it **recursively**
$It2       = new RecursiveIteratorIterator($Regex1); 

//Now, match files
$Regex2    = new RegexIterator($It2,'/\.php$/i');
foreach($Regex2 as $v){
  echo $v."\n";
}
?>
_

問題は、_.Trash[0-9]{3}_の部分が一致しないことです。ディレクトリを負に一致させる方法を知る唯一の方法は、match文字列の終わり_$_、そして次に'/ foo'が前に付いていない場合は、後読み_(?<!/foo)_ 'でアサートします。

ただし、_.Trash[0-9]{1,3}_は固定長ではないため、後読みアサーションとして使用することはできません。残念ながら、RegexIteratorには「反転一致」はありません。しかし、おそらく正規表現に精通した人の方が多いので、_.Trash[0-9]+_で終わらない文字列を一致させる方法を知っています。


編集:正規表現でうまくいくので'%([^0-9]|^)(?<!/.Trash-)[0-9]*$%'を取得しました。

8
Wrikken

Salatheの改善点は、カスタム抽象クラスを忘れることです。良いOOP in PHPを使用し、代わりにRecursiveRegexIteratorを直接拡張します。

これがファイルフィルターです

class FilenameFilter 
extends RecursiveRegexIterator 
{
    // Filter files against the regex
    public function accept() 
    {
        return ! $this->isFile() || parent::accept();
    }
}

そしてディレクトリフィルター

class DirnameFilter 
extends RecursiveRegexIterator 
{
    // Filter directories against the regex
    public function accept() {
        return ! $this->isDir() || parent::accept();
    }
}
1
Guillermo