首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >结合递归迭代器的结果:有父母的孩子

结合递归迭代器的结果:有父母的孩子
EN

Stack Overflow用户
提问于 2009-03-01 13:19:30
回答 2查看 2.4K关注 0票数 5

我试图迭代包含大量PHP文件的目录,并检测每个文件中定义了哪些类。

请考虑以下几点:

代码语言:javascript
复制
$php_files_and_content = new PhpFileAndContentIterator($dir);
foreach($php_files_and_content as $filepath => $sourceCode) {
    // echo $filepath, $sourceCode
}

上面的$php_files_and_content变量表示一个迭代器,其中键是文件路径,而内容是文件的源代码(好像从示例中看这一点并不明显)。

然后将它提供给另一个迭代器,它将匹配源代码中所有定义的类,ala:

代码语言:javascript
复制
class DefinedClassDetector extends FilterIterator implements RecursiveIterator {
    public function accept() {
        return $this->hasChildren();
    }

    public function hasChildren() {
        $classes = getDefinedClasses($this->current());
        return !empty($classes);
    }

    public function getChildren() {
        return new RecursiveArrayIterator(getDefinedClasses($this->current()));
    }
}

$defined_classes = new RecursiveIteratorIterator(new DefinedClassDetector($php_files_and_content));

foreach($defined_classes as $index => $class) {
    // print "$index => $class"; outputs:
    // 0 => Class A
    // 1 => Class B
    // 0 => Class C
}

$index没有顺序的原因是因为'Class C‘是在第二个源代码文件中定义的,因此返回的数组再次从索引0开始。这在RecursiveIteratorIterator中保留下来,因为每一组结果都代表一个独立的Iterator (因此是键/值对)。

无论如何,我现在要做的是找到最好的组合方法,这样当我在新的迭代器上迭代时,我可以得到类名(从$defined_classes迭代器),值是原始的文件路径ala:

代码语言:javascript
复制
foreach($classes_and_paths as $filepath => $class) {
    // print "$class => $filepath"; outputs
    // Class A => file1.php
    // Class B => file1.php
    // Class C => file2.php
}

到目前为止我被困在那里了。

目前,想到的唯一解决方案是创建一个新的RecursiveIterator,该方法重写current()方法以返回外部迭代器key() (这将是原始文件),并使用key()方法返回当前的迭代器()值。但我不赞成这个解决方案,因为:

  • --听起来很复杂(这意味着代码看起来很难看,而且不会是直觉的
  • -业务规则在类中是硬编码的,而我想定义一些泛型迭代器,并能够以这样的方式组合它们以产生所需的结果。

任何感激不尽的想法或建议。

我还意识到,有更快、更有效的方法可以做到这一点,但这也是一个为我自己使用迭代器的练习,也是一个促进代码重用的练习,所以任何必须编写的新迭代器都应该尽可能地最小化,并设法利用现有的功能。

谢谢

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2009-03-08 09:26:10

好吧,我想我终于明白了。下面是我在伪代码中所做的大致工作:

步骤1我们需要列出目录内容,因此我们可以执行以下操作:

代码语言:javascript
复制
// Reads through the $dir directory
// traversing children, and returns all contents
$dirIterator = new RecursiveDirectoryIterator($dir);

// Flattens the recursive iterator into a single
// dimension, so it doesn't need recursive loops
$dirContents = new RecursiveIteratorIterator($dirIterator);

步骤2我们只需要考虑文件

代码语言:javascript
复制
class PhpFileIteratorFilter {
    public function accept() {
        $current = $this->current();
        return    $current instanceof SplFileInfo
               && $current->isFile()
               && end(explode('.', $current->getBasename())) == 'php';
    }
}


// Extends FilterIterator, and accepts only .php files
$php_files = new PhpFileIteratorFilter($dirContents);

PhpFileIteratorFilter并不是对可重用代码的很好的使用。一个更好的方法是能够提供一个文件扩展名作为构造的一部分,并使过滤器与之匹配。尽管如此,我还是试图摆脱那些不需要的建筑论点,而更多地依赖于构图,因为这更好地利用了“战略”模式。PhpFileIteratorFilter可以简单地使用泛型FileExtensionIteratorFilter并在内部设置自己。

步骤3我们现在必须读取文件内容

代码语言:javascript
复制
class SplFileInfoReader extends FilterIterator {

    public function accept() {
        // make sure we use parent, this one returns the contents
        $current = parent::current();
        return    $current instanceof SplFileInfo
               && $current->isFile()
               && $current->isReadable();
    }

    public function key() {
        return parent::current()->getRealpath();
    }

    public function current() {
        return file_get_contents($this->key());
    }    
}

// Reads the file contents of the .php files
// the key is the file path, the value is the file contents
$files_and_content = new SplFileInfoReader($php_files);

步骤4,,现在我们希望将回调应用于每个项(文件内容),并以某种方式保留结果。同样,为了使用策略模式,我消除了不必要的构造参数,例如$preserveKeys或类似的

代码语言:javascript
复制
/**
 * Applies $callback to each element, and only accepts values that have children
 */
class ArrayCallbackFilterIterator extends FilterIterator implements RecursiveIterator {

    public function __construct(Iterator $it, $callback) {
        if (!is_callable($callback)) {
            throw new InvalidArgumentException('$callback is not callable');
        }

        $this->callback = $callback;
        parent::__construct($it);
    }

    public function accept() {
        return $this->hasChildren();
    }

    public function hasChildren() {
        $this->results = call_user_func($this->callback, $this->current());
        return is_array($this->results) && !empty($this->results);
    }

    public function getChildren() {
        return new RecursiveArrayIterator($this->results);
    }
}


/**
 * Overrides ArrayCallbackFilterIterator to allow a fixed $key to be returned
 */
class FixedKeyArrayCallbackFilterIterator extends ArrayCallbackFilterIterator {
    public function getChildren() {
        return new RecursiveFixedKeyArrayIterator($this->key(), $this->results);
    }
}


/**
 * Extends RecursiveArrayIterator to allow a fixed $key to be set
 */
class RecursiveFixedKeyArrayIterator extends RecursiveArrayIterator {

    public function __construct($key, $array) {
        $this->key = $key;
        parent::__construct($array);
    }

    public function key() {
        return $this->key;
    }
}

因此,这里有我的基本迭代器,它将返回我提供的$callback的结果,但我也扩展了它,以创建一个也将保留键的版本,而不是为它使用构造函数参数。

因此,我们有:

代码语言:javascript
复制
// Returns a RecursiveIterator
// key: file path
// value: class name
$class_filter = new FixedKeyArrayCallbackFilterIterator($files_and_content, 'getDefinedClasses');

步骤5现在我们需要将它格式化为一种适当的方式。我希望文件路径是值,键是类名(即为自动加载程序提供类到文件的直接映射)。

代码语言:javascript
复制
// Reduce the multi-dimensional iterator into a single dimension
$files_and_classes = new RecursiveIteratorIterator($class_filter);

// Flip it around, so the class names are keys
$classes_and_files = new FlipIterator($files_and_classes);

瞧,我现在可以在$classes_and_files上迭代,并获得$dir下所有定义的类的列表,以及它们在其中定义的文件。而且,几乎所有用于此操作的代码在其他上下文中也是可重用的。我没有在定义的Iterator中硬编码任何东西来完成这个任务,也没有在迭代器之外进行任何额外的处理。

票数 2
EN

Stack Overflow用户

发布于 2009-03-03 08:06:29

我认为您想要做的是或多或少地反转从PhpFileAndContent返回的键和值。上述类返回一个filepath => source列表,您希望首先将映射反转为source => filepath,然后为source中定义的每个类展开source,因此它将是class1 => filepath, class2 => filepath

这应该很容易,因为在您的getChildren()中,您可以简单地访问$this->key()来获取运行getDefinedClasses()的源的当前文件路径。您可以将getDefinedClasses写成getDefinedClasses($path, $source),而不是返回所有类的索引数组,它将返回一个字典,其中当前索引数组中的每个值都是字典中的键,并且该值是定义该类的文件路径。

然后它就会出现在你想要的地方。

另一个选择是放弃使用RecursiveArrayIterator,而是编写自己的迭代器(在getChildren中)作为

代码语言:javascript
复制
return new FilePathMapperIterator($this->key,getDefinedClasses($this->current()));

然后,FilePathMapperIterator将类数组从getDefinedClasses转换为我描述的class => filepath映射,只需迭代数组并返回key()中的当前类,并始终在current()中返回指定的文件路径。

我认为后者更酷,但绝对是更多的代码,所以如果我能够根据我的需要调整getDefinedClasses(),那么我不太可能这样做。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/599809

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档