3

通过 SPL 迭代器递归扫描目录的标准方法是:

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

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

我想要一组可组合的过滤器应用于我的递归文件搜索。我正在使用 aRecursiveDirectoryIterator来扫描目录结构。

我想对我的目录结构应用多个过滤器。

我的设置代码:

$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 上对其进行了测试,没有区别。有任何想法吗?

4

1 回答 1

6

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

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

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

  1. 在展平迭代上应用过滤器,即树遍历之后。只要您只需要过滤每个单独的文件 - 而不是子文件,它在您的情况下看起来是可行的。
  2. 标准方法:注意getChildren()返回一个配置FilterRuleset的带有过滤器集的递归过滤器迭代器对象。

我从第二个开始,因为它很快就完成了,而且是正常的方法。

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

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

    ...

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

另一个(第一个)变体是您基本上将其“降级”为“平面”过滤器,即标准FilterIterator. 因此,您首先使用 a 进行递归迭代,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);
    }
}
于 2013-05-23T22:17:45.960 回答