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' が 2 番目のソース コード ファイルで定義されているためです。したがって、返される配列は再びインデックス 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() メソッドを返して返します。現在の iterator() 値。しかし、次の理由から、私はこのソリューションを支持していません。

  • 複雑に聞こえます (つまり、コードが見苦しく、直感的ではないということです)。
  • ビジネス ルールはクラス内にハードコーディングされていますが、私はいくつかのジェネリック イテレータを定義し、それらを組み合わせて必要な結果を生成できるようにしたいと考えています。

アイデアや提案はありがたく受け取りました。

これを行うためのはるかに高速で効率的な方法があることも認識していますが、これは自分自身のためにイテレーターを使用する練習でもあり、コードの再利用を促進する練習でもあります。したがって、作成する必要がある新しいイテレーターはできるだけ最小限に抑える必要があります。既存の機能を活用してみてください。

ありがとう

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

したがって、ここでは、提供された I の結果を返す基本的なイテレータがあり$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 の下に定義されているすべてのクラスのリストと、それらが定義されているファイルを取得できます。これを行うために使用されるほとんどすべてのコードは、他のコンテキストでも再利用可能です。 . このタスクを達成するために、定義された Iterator に何もハードコーディングしていません。また、Iterator の外部で追加の処理を行っていません。

于 2009-03-08T09:26:10.170 に答える
0

あなたがやりたいことは、多かれ少なかれから返されたキーと値を逆にすることだと思いますPhpFileAndContent。上記のクラスは のリストを返します。filepath => source最初にマッピングを逆にして、 で定義された各クラスsource => filepathを展開すると、 になります。sourcesourceclass1 => filepath, class2 => filepath

実行中のソースの現在のファイルパスを取得するためにgetChildren()アクセスするだけでよいので、簡単です。すべてのクラスのインデックス付き配列を返す代わりに、として記述して、現在のインデックス付き配列の各値が辞書のキーであり、値がそのクラスが定義されたファイルパスである辞書を返すことができます。$this->key()getDefinedClasses()getDefinedClassesgetDefinedClasses($path, $source)

すると思い通りに出てきます。

もう 1 つのオプションは、 の使用をやめて、代わりに ( で)RecursiveArrayIterator初期化された独自のイテレータを次のように記述することです。getChildren

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

次に、配列を反復処理して現在のクラスを返し、指定されたファイルパスを常に返すことによりFilePathMapperIterator、クラス配列をから説明getDefinedClassesしたマッピングに変換します。class => filepathkey()current()

getDefinedClasses()後者の方がクールだと思いますが、間違いなくコードが多いので、自分のニーズに適応できれば、そのようになる可能性は低いです。

于 2009-03-03T08:06:29.763 に答える