Ладно, думаю, я наконец-то понял это. Вот примерно то, что я сделал в псевдокоде:
Шаг 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 вместе с файлом, в котором они определены. И почти весь код, используемый для этого, повторно можно использовать и в других контекстах. Я не жестко запрограммировал что-либо в определенном Итераторе, чтобы выполнить эту задачу, а также не выполнял никакой дополнительной обработки вне итераторов