组合递归迭代器结果:孩子与父母

发布于 2024-07-14 12:22:29 字数 1968 浏览 13 评论 0原文

我正在尝试迭代包含 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 不是按数字顺序排列的原因是因为“Class C”是在第二个源中定义的代码文件,因此返回的数组又从索引0开始。 这被保留在 RecursiveIteratorIterator 中,因为每组结果代表一个单独的迭代器(以及键/值对)。

不管怎样,我现在想做的是找到组合这些的最佳方法,这样当我迭代新的迭代器时,我可以得到关键是类名(来自 $define_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
}

这就是我到目前为止所陷入的困境。

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

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

我很高兴收到任何想法或建议,

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

谢谢。

I'm trying to iterate over a directory which contains loads of PHP files, and detect what classes are defined in each file.

Consider the following:

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

The above $php_files_and_content variable represents an iterator where the key is the filepath, and the content is the source code of the file (as if that wasn't obvious from the example).

This is then supplied into another iterator which will match all the defined classes in the source code, 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
}

The reason the $index isn't sequential numerically is because 'Class C' was defined in the second source code file, and thus the array returned starts from index 0 again. This is preserved in the RecursiveIteratorIterator because each set of results represents a separate Iterator (and thus key/value pairs).

Anyway, what I am trying to do now is find the best way to combine these, such that when I iterate over the new iterator, I can get the key is the class name (from the $defined_classes iterator) and the value is the original file path, ala:

foreach($classes_and_paths as $filepath => $class) {
    // print "$class => $filepath"; outputs
    // Class A => file1.php
    // Class B => file1.php
    // Class C => file2.php
}

And that's where I'm stuck thus far.

At the moment, the only solution that is coming to mind is to create a new RecursiveIterator, that overrides the current() method to return the outer iterator key() (which would be the original filepath), and key() method to return the current iterator() value. But I'm not favouring this solution because:

  • It sounds complex (which means the code will look hideous and it won't be intuitive
  • The business rules are hard-coded inside the class, whereas I would like to define some generic Iterators and be able to combine them in such a way to produce the required result.

Any ideas or suggestions gratefully recieved.

I also realise there are far faster, more efficient ways of doing this, but this is also an exercise in using Iterators for myselfm and also an exercise in promoting code reuse, so any new Iterators that have to be written should be as minimal as possible and try to leverage existing functionality.

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

只怪假的太真实 2024-07-21 12:22:29

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

第 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 下所有定义的类的列表,以及它们定义的文件。几乎所有用于执行此操作的代码都可以在其他上下文中重用,例如出色地。 我没有在定义的迭代器中硬编码任何内容来完成此任务,也没有在迭代器之外进行任何额外的处理

OK, I think I finally got my head around this. Here's roughly what I did in pseudo-code:

Step 1
We need to list the directory contents, thus we can perform the following:

// 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);

Step 2
We need to consider only the PHP files

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);

The PhpFileIteratorFilter isn't a great use of re-usable code. A better method would have been to be able to supply a file extension as part of the construction and get the filter to match on that. Although that said, I am trying to move away from construction arguments where they are not required and rely more on composition, because that makes better use of the Strategy pattern. The PhpFileIteratorFilter could simply have used the generic FileExtensionIteratorFilter and set itself up interally.

Step 3
We must now read in the file contents

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);

Step 4
Now we want to apply our callback to each item (the file contents) and somehow retain the results. Again, trying to make use of the strategy pattern, I've done away unneccessary contructor arguments, e.g. $preserveKeys or similar

/**
 * 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;
    }
}

So, here I have my basic iterator which will return the results of the $callback I supplied through, but I've also extended it to create a version that will preserve the keys too, rather than using a constructor argument for it.

And thus we have this:

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

Step 5
Now we need to format it into a suitable manner. I desire the file paths to be the value, and the keys to be the class name (i.e. to provide a direct mapping for a class to the file in which it can be found for the auto loader)

// 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);

And voila, I can now iterate over $classes_and_files and get a list of all defined classes under $dir, along with the file they're defined in. And pretty much all of the code used to do this is re-usable in other contexts as well. I haven't hard-coded anything in the defined Iterator to achieve this task, nor have I done any extra processing outside the iterators

谜兔 2024-07-21 12:22:29

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

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

然后它就会像你想要的那样出来。

另一种选择是放弃使用 RecursiveArrayIterator ,而是编写自己的迭代器,该迭代器已初始化(在 getChildren 中),然后

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

FilePathMapperIterator 将转换从 getDefinedClassesclass => 的类数组 我通过简单地迭代数组并在 key() 中返回当前类并始终在 current() 中返回指定的文件路径来描述文件路径映射。

我认为后者更酷,但代码肯定更多,所以如果我可以根据我的需要调整 getDefinedClasses() ,我不太可能会这样做。

I think what you want to do, is more or less to reverse the keys and values returned from PhpFileAndContent. Said class returns a list of filepath => source, and you want to first reverse the mapping so it is source => filepath and then expand source for each class defined in source, so it will be class1 => filepath, class2 => filepath.

It should be easy as in your getChildren() you can simply access $this->key() to get the current file path for the source you are running getDefinedClasses() on. You can write getDefinedClasses as getDefinedClasses($path, $source) and instead of returning an indexed array of all the classes, it will return a dictionary where each value from the current indexed array is a key in the dictionary and the value is the filepath where that class was defined.

Then it will come out just as you want.

The other option is to drop your use of RecursiveArrayIterator and instead write your own iterator that is initialized (in getChildren) as

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

and then FilePathMapperIterator will convert the class array from getDefinedClasses to the class => filepath mapping I described by simply iterating over the array and returning the current class in key() and always returning the specified filepath in current().

I think the latter is more cool, but definitely more code so its unlikely that I would have gone that way if I can adapt getDefinedClasses() for my needs.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文