Как использовать RegexIterator в PHP - PullRequest
22 голосов
/ 23 июля 2010

Мне еще предстоит найти хороший пример того, как использовать php RegexIterator для рекурсивного обхода каталога.

Конечным результатом будет то, что я хочу указать каталог и найти все файлы в нем с некоторыми заданными расширениями.Скажем, например, только 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)

Есть предложения?

Ответы [ 3 ]

48 голосов
/ 24 июля 2010

Есть несколько разных способов сделать что-то вроде этого, я дам вам два быстрых подхода на выбор: быстрый и грязный, более длинный и менее грязный (хотя сегодня вечером пятница, поэтому мы позволил немного сойти с ума).

1. Быстрый (и грязный)

Это включает в себя просто написание регулярного выражения (может быть разбито на несколько) для использования для фильтрации коллекции файлов одним быстрым движением.

(Только две закомментированные строки действительно важны для концепции.)

$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, так как он на самом деле не делает ничего полезного: фактическая фильтрация выполняется двумя классами, которые расширяют этот класс. Кроме того, его можно назвать FilesystemRegexFilter, но ничто не заставляет его (на этом уровне) фильтровать классы, связанные с файловой системой (я бы выбрал лучшее имя, если бы не был таким сонным).

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

Эти два класса являются очень простыми фильтрами, действующими на имя файла и имя каталога соответственно.

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 (следя за тем, чтобы имена папок сопоставьте со специально созданным регулярным выражением) и принимайте только файлы 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. Хм, этот ответ немного болтает; Я старался держать его как можно более кратким (даже удаляя огромные полосы супер-болтовни). Извиняюсь, если чистый результат оставляет ответ бессвязным.

8 голосов
/ 24 июля 2010

Документы действительно не очень полезны. Существует проблема с использованием регулярного выражения для «не совпадает», но сначала мы проиллюстрируем рабочий пример:

<?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} часть: Единственный способ, которым я знаю, как отрицательно соответствовать каталогу, это соответствие конец строки $, а затем утверждение с lookbehind (?<!/foo) ', если ему не предшествует' /foo'.

Однако, поскольку .Trash[0-9]{1,3} не является фиксированной длиной, мы не можем использовать ее как утверждение за кадром. К сожалению, для RegexIterator не существует «обратного соответствия». Но, возможно, есть более опытные люди, чем я, и я знаю, как сопоставить любую строку, не заканчивающуюся .Trash[0-9]+


edit : получил '%([^0-9]|^)(?<!/.Trash-)[0-9]*$%', поскольку регулярное выражение сработало бы.

1 голос
/ 08 января 2013

Улучшение в salathe, было бы забыть о пользовательском абстрактном классе. Просто используйте хороший ООП в 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();
    }
}
...