Повторный экземпляр RecursiveFilterIterator в RecursiveIteratorIterator?

Стандартный способ рекурсивного сканирования каталогов с помощью итераторов SPL:

$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($path),
    RecursiveIteratorIterator::CHILD_FIRST
);

foreach ($files as $file) {
    print $file->getPathname() . PHP_EOL;
}

Я хочу, чтобы к моему рекурсивному поиску файлов применялся составной набор фильтров. Я использую RecursiveDirectoryIterator для сканирования структуры каталогов.

Я хочу применить более одного фильтра к своей структуре каталогов.

Мой код настройки:

$filters = new FilterRuleset(
    new RecursiveDirectoryIterator($path)
);
$filters->addFilter(new FilterLapsedDirs);
$filters->addFilter(new IncludeExtension('wav'));
$files = new RecursiveIteratorIterator(
    $filters, RecursiveIteratorIterator::CHILD_FIRST
);

Я думал, что смогу применить N фильтров, используя набор правил:

class FilterRuleset extends RecursiveFilterIterator {
    private $filters = array();

    public function addFilter($filter) {
        $this->filters[] = $filter;
    }

    public function accept() {
        $file = $this->current();

        foreach ($this->filters as $filter) {
            if (!$filter->accept($file)) {
                return false;
            }
        }

        return true;
    }
}

Фильтрация, которую я настроил, не работает должным образом. Когда я проверяю фильтры в FilterRuleset, они заполняются при первом вызове, а при последующих вызовах остаются пустыми. Как будто внутри RecursiveIteratorIterator воссоздается мой FilterRuleset.

    public function accept() {
        print_r($this->filters);
        $file = $this->current();

        foreach ($this->filters as $filter) {
            if (!$filter->accept($file)) {
                return false;
            }
        }

        return true;
    }

Выход:

Array
(
    [0] => FilterLapsedDirs Object
        (
        )

    [1] => IncludeExtension Object
        (
            [ext:private] => wav
        )
)
Array
(
)
Array
(
)
Array
(
)
Array
(
)
Array
(
)
Array
(
)

Я использую PHP 5.1.6, но тестировал его на 5.4.14, и разницы нет. Есть идеи?


person Greg K    schedule 23.05.2013    source источник


Ответы (1)


Когда я проверяю фильтры в FilterRuleset, они заполняются при первом вызове, а при последующих вызовах остаются пустыми. Как будто внутренне RecursiveIteratorIterator повторно создает экземпляр моего FilterRuleset.

Да, это именно так. Каждый раз, когда вы заходите в подкаталог, массив пуст, потому что в соответствии с правилами рекурсивного итератора итератор рекурсивного фильтра должен предоставлять дочерние итераторы.

Итак, у вас есть два варианта:

  1. Применяйте фильтры на сглаженной итерации, то есть после обхода дерева. В вашем случае это выглядит осуществимым, если вам нужно фильтровать только каждый отдельный файл, а не детей.
  2. Стандартный способ: позаботьтесь о том, чтобы getChildren() возвращал сконфигурированный объект FilterRuleset recursive-filter-iterator с установленными фильтрами.

Я начинаю со второго, потому что это делается быстро и это обычный способ сделать это.

Вы перезаписываете родительский метод getChildren(), добавляя его в свой класс. Затем вы берете результат родителя (который является новым FilterRuleset для дочерних элементов и устанавливаете закрытый член. Это возможно в PHP (если вам интересно, что он работает, потому что это частный член), потому что он находится на том же уровне иерархия классов Затем вы просто возвращаете его и готово:

class FilterRuleset extends RecursiveFilterIterator
{
    private $filters = array();

    ...

    public function getChildren() {
        $children = parent::getChildren();
        $children->filters = $this->filters;
        return $children;
    }
}

Другой (первый) вариант заключается в том, что вы в основном «деградируете» его до «плоского» фильтра, то есть стандартного FilterIterator. Поэтому вы сначала выполняете рекурсивную итерацию с RecursiveIteratorIterator, а затем переносите ее в свой фильтр-итератор. Поскольку дерево уже было пройдено предыдущим итератором, вся эта рекурсия больше не нужна.

Итак, прежде всего превратите его в FilterIterator:

class FilterRuleset extends FilterIterator
{
   ...
}

Единственное изменение заключается в том, что вы расширяете с помощью этого класса. И вы создаете экземпляр в немного другом порядке:

$path  = __DIR__;
$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
    RecursiveIteratorIterator::CHILD_FIRST
);

$filtered = new FilterRuleset($files);
$filtered->addFilter(Accept::byCallback(function () {
    return true;
}));

foreach ($filtered as $file) {
    echo $file->getPathname(), PHP_EOL;
}

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

Ах, и пока я не забыл: вот макет, который я использовал для создания фильтров в моем примере выше:

class Accept
{
    private $callback;

    public function __construct($callback) {
        $this->callback = $callback;
    }

    public function accept($subject) {
        return call_user_func($this->callback, $subject);
    }

    public static function byCallback($callback) {
        return new self($callback);
    }
}
person hakre    schedule 23.05.2013
comment
Спасибо, отличный ответ. Сначала я заставлю его работать, а затем приму ответ. - person Greg K; 29.05.2013
comment
Кроме того, если вы перейдете к: stackoverflow.com/questions/12077177/ @hakre снова объясняет концепцию IteratorIterator против RecursiveIteratorIterator - person flangofas; 07.08.2014