5

我正在尝试遍历包含大量 PHP 文件的目录,并检测每个文件中定义了哪些类。

考虑以下:

$php_files_and_content = new PhpFileAndContentIterator($dir);
foreach($php_files_and_content as $filepath => $sourceCode) {
    // echo $filepath, $sourceCode
}

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

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

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是因为在第二个源代码文件中定义了“C 类”,因此返回的数组再次从索引 0 开始。这保留在 RecursiveIteratorIterator 中,因为每组结果代表一个单独的 Iterator(因此是键/值对)。

无论如何,我现在要做的是找到组合这些的最佳方法,这样当我迭代新的迭代器时,我可以获得键是类名(来自$defined_classes迭代器),值是原始文件路径,阿拉:

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() 方法当前的迭代器()值。但我不赞成这种解决方案,因为:

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

任何想法或建议都非常感激。

我也意识到有更快,更有效的方法来做到这一点,但这也是我自己使用迭代器的练习,也是促进代码重用的练习,因此必须编写的任何新迭代器都应该尽可能少并尝试利用现有功能。

谢谢

4

2 回答 2

2

好的,我想我终于明白了这一点。这大致是我在伪代码中所做的:

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

// 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 步 我们只需要考虑 PHP 文件

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 步 我们现在必须读入文件内容

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或类似的

/**
 * 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我提供的结果,但我还扩展了它以创建一个也将保留键的版本,而不是为其使用构造函数参数。

因此我们有这个:

// Returns a RecursiveIterator
// key: file path
// value: class name
$class_filter = new FixedKeyArrayCallbackFilterIterator($files_and_content, 'getDefinedClasses');

第5步 现在我们需要将其格式化为合适的方式。我希望文件路径是值,键是类名(即为一个类提供一个直接映射到自动加载器可以在其中找到它的文件)

// 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 下所有已定义类的列表,以及它们在其中定义的文件。几乎所有用于执行此操作的代码也可以在其他上下文中重用. 我没有在定义的迭代器中硬编码任何东西来完成这个任务,我也没有在迭代器之外做任何额外的处理

于 2009-03-08T09:26:10.170 回答
0

我认为您想要做的或多或少是反转从PhpFileAndContent. 所说的类返回一个列表filepath => source,你想首先反转映射source => filepath,然后source为每个定义的类展开source,所以它会是class1 => filepath, class2 => filepath

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

然后它会如你所愿地出现。

另一种选择是放弃使用,RecursiveArrayIterator而是编写自己的迭代器,该迭代器被初始化(in getChildren)为

return new FilePathMapperIterator($this->key,getDefinedClasses($this->current()));

然后FilePathMapperIterator将类数组从转换为我描述getDefinedClassesclass => filepath映射,方法是简单地遍历数组并返回当前类key()并始终返回指定的文件路径current()

getDefinedClasses()我认为后者更酷,但肯定有更多的代码,所以如果我能适应我的需求,我不太可能这样做。

于 2009-03-03T08:06:29.763 回答