Jam*_*ady 5 php recursion code-reuse iterator
我正在尝试遍历包含大量PHP文件的目录,并检测每个文件中定义的类.
考虑以下:
$php_files_and_content = new PhpFileAndContentIterator($dir);
foreach($php_files_and_content as $filepath => $sourceCode) {
// echo $filepath, $sourceCode
}
Run Code Online (Sandbox Code Playgroud)
上面的$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
}
Run Code Online (Sandbox Code Playgroud)
$index不是数字顺序的原因是因为'C类'在第二个源代码文件中定义,因此返回的数组再次从索引0开始.这在RecursiveIteratorIterator中保留,因为每组结果代表一个单独的迭代器(因此键/值对).
无论如何,我现在要做的是找到组合这些的最佳方法,这样当我迭代新的迭代器时,我可以得到键是类名(来自$defined_classes迭代器),值是原始文件路径,ala:
foreach($classes_and_paths as $filepath => $class) {
// print "$class => $filepath"; outputs
// Class A => file1.php
// Class B => file1.php
// Class C => file2.php
}
Run Code Online (Sandbox Code Playgroud)
这就是我到目前为止所处的位置.
目前,唯一想到的解决方案是创建一个新的RecursiveIterator,它会覆盖current()方法以返回外部迭代器键()(这将是原始文件路径),以及要返回的key()方法当前的iterator()值.但我不赞成这个解决方案,因为:
任何想法或建议感激不尽.
我也意识到有更快,更有效的方法可以做到这一点,但这也是我自己使用迭代器的一种练习,也是一种促进代码重用的练习,因此任何必须编写的新迭代器应该尽可能小并尝试利用现有功能.
谢谢
好吧,我想我终于明白了这一点。这大致是我在伪代码中所做的:
步骤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);
Run Code Online (Sandbox Code Playgroud)
步骤 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);
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
步骤 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;
}
}
Run Code Online (Sandbox Code Playgroud)
因此,这里我有我的基本迭代器,它将返回$callback我提供的结果,但我还对其进行了扩展以创建一个也将保留键的版本,而不是使用构造函数参数。
因此我们有这个:
// Returns a RecursiveIterator
// key: file path
// value: class name
$class_filter = new FixedKeyArrayCallbackFilterIterator($files_and_content, 'getDefinedClasses');
Run Code Online (Sandbox Code Playgroud)
第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);
Run Code Online (Sandbox Code Playgroud)
瞧,我现在可以迭代$classes_and_files并获取 $dir 下所有定义的类的列表,以及它们定义的文件。几乎所有用于执行此操作的代码也可以在其他上下文中重用。我没有在定义的迭代器中硬编码任何内容来完成此任务,也没有在迭代器之外进行任何额外的处理
| 归档时间: |
|
| 查看次数: |
2031 次 |
| 最近记录: |