RecursiveFilterIterator在RecursiveIteratorIterator中重新实例化?

时间:2013-05-23 11:03:53

标签: php spl

通过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上进行了测试,并没有区别。有什么想法吗?

1 个答案:

答案 0 :(得分:6)

  

当我检查FilterRuleset中的过滤器时,它们会在第一次调用时填充,然后在后续调用时填充。它好像在内部RecursiveIteratorIterator重新实例化我的FilterRuleset。

是的,情况确实如此。每次进入子目录时,数组都是空的,因为每个递归迭代器规则,递归过滤器迭代器需要提供子迭代器。

所以你在这里有两个选择:

  1. 在展平的迭代上应用过滤器,即在树遍历之后。只要你只需要过滤每个单独的文件而不是孩子,它在你的情况下看起来是可行的。
  2. 标准方法:注意getChildren()返回已设置过滤器的已配置FilterRuleset递归过滤器 - 迭代器对象。
  3. 我从第二个开始,因为它已经快速完成并且是正常的方法。

    通过将getChildren()父方法添加到您的班级来覆盖它。然后你获取父项的结果(这是子项的新FilterRuleset并设置私有成员。这在PHP中是可能的(如果你想知道他的作品是因为它是私人成员)因为它在类层次结构的同一级别上。然后你只需返回并完成:

    class FilterRuleset extends RecursiveFilterIterator
    {
        private $filters = array();
    
        ...
    
        public function getChildren() {
            $children = parent::getChildren();
            $children->filters = $this->filters;
            return $children;
        }
    }
    

    另一个(第一个)变体是你基本上"降级"它到了#34; flat"过滤器,即标准FilterIterator。因此,您首先使用RecursiveIteratorIterator进行递归迭代,然后将其包装到filter-iterator中。由于树已经被早期的迭代器遍历了,所以不再需要所有这些递归的东西了。

    首先将其转换为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);
        }
    }