Anne ile çocuk: özyinelemeli yineleyici sonuçları birleştirerek

2 Cevap php

PHP dosyaları yükler içeren bir dizin üzerinde yineleme ve sınıfları her dosyasında tanımlanır ne algılamaya çalışıyorum.

Aşağıdaki düşünün:

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

Yukarıdaki $php_files_and_content değişkeni anahtar filepath bir yineleyici temsil eder ve içeriği (bu örnekte açıkça değildi sanki) dosyasının kaynak kodu.

Bu daha sonra ala, kaynak kodu tüm tanımlı sınıfları maç olacak başka bir yineleyici içine verilir:

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
}

'C Sınıfı' ikinci kaynak kod dosyasında tanımlanan ve böylece dizi tekrar endeksi 0 dan başlar döndü çünkü $index sayısal sıralı değil nedenidir. Sonuçların her set ayrı bir yineleyici (ve dolayısıyla anahtar / değer çiftleri) temsil çünkü bu RecursiveIteratorIterator korunur.

Her neyse, ne şimdi yapmaya çalışıyorum bu birleştirmek için en iyi yolu bulmak olduğunu ben yeni yineleyici üzerinde yineleme zaman, ben anahtar sınıf adı (alanından $defined_classes yineleyici) ve olduğu alabilirsiniz böyle değeri, orijinal dosya yolu, ala:

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

Ben şimdiye kadar şaşırıp Ve işte burada.

Şu anda, akla geliyor tek çözüm (orijinal filepath olurdu) Geçerli geçersiz kılan yeni bir RecursiveIterator, (dış yineleyici anahtar dönmek) yöntemi () oluşturmak için, ve anahtar () yöntemi, geri dönmek için geçerli yineleyici () değer. : Ama bu çözüm, çünkü lehine değilim

  • Bu kod iğrenç bakacağız demektir (karmaşık geliyor ve bu sezgisel olmayacak
  • Bazı genel iterators tanımlamak ve istenilen sonucu üretmek için böyle bir şekilde bunları birleştirmek mümkün olmak istiyorum oysa iş kuralları, sınıf içinde kodlanmış olan.

Herhangi bir fikir veya öneri minnetle aldı.

Ben de bunu daha etkin yolu, çok hızlı vardır farkında, ama bu da myselfm için yineleyicileri kullanarak ve aynı zamanda kodu yeniden teşvik bir egzersiz bir egzersiz olduğunu, bu nedenle yazılı olması yeni Iterators mümkün olduğunca az olmalıdır ve mevcut işlevselliğini kaldıraç çalışın.

Teşekkürler

2 Cevap

Tamam, ben sonunda bu başımı var bence. Burada pseudo-code yaptım kabaca budur:

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

Yani, burada sonuçları dönecektir benim temel yineleyici var $callback Ben sağlanır, ama aynı zamanda oldukça yapıcı argümanı kullanarak daha çok anahtarları koruyacak bir sürümünü oluşturmak için bunu uzattık Bunun için.

Ve böylece bu var:

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

Ve işte, ben şimdi üzerinde yineleme $classes_and_files ve onlar içeri tanımlanan konum Ve hemen hemen tüm bunu yapmak için kullanılan kod yeniden bir dosya ile birlikte, $ dir altında tanımlanmış tüm sınıfların bir listesini almak başka bağlamlarda-kullanılabilir de. Ben bu görevi başarmak için tanımlanmış Iterasyon şey kodlanmış değil, ne de ben Yineleyicilerin dışında herhangi bir ekstra işlem yapmış

Ne yapmak istediğinizi, anahtarlarını ve değerlerini ters veya daha az PhpFileAndContent döndürülen olduğunu düşünüyorum. Dedi class filepath => source listesini verir ve ilk önce source => filepath yani haritalama ters ve sonra ({[tanımlanan her sınıf için source genişletmek istiyor o {olacaktır 3)]}, bu nedenle [(5)]}.

$this->key() üzerinde getDefinedClasses() çalıştıran kaynağı için geçerli dosya yolunu almak için sizin getChildren() yapabilirsiniz sadece erişimi kolay olmalıdır. Sen getDefinedClasses getDefinedClasses($path, $source) ve bunun yerine tüm sınıfları bir dizinlenmiş dizi dönen, o anki Dizinlenmiş diziden her değer sözlükte bir anahtar bir sözlük döner gibi ve yazabilirsiniz değeri, bu sınıf tanımlanmış filepath olduğunu.

Sonra istediğiniz gibi çıkacaktır.

Diğer seçenek RecursiveArrayIterator kullanımını bırakın ve bunun yerine ({[) (1]} olarak) başlatıldı kendi yineleyici yazmak için

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

ve FilePathMapperIterator Sadece dizi yineleme ve {[(3 mevcut sınıfı geri tarafından tarif edilen class => filepath eşleme getDefinedClasses ikinci sınıf dizi dönüştürür )]} ve her belirtilen dosya yolu döndürme current().

Ben ikincisi daha serin olduğunu düşünüyorum, ama benim ihtiyaçları için getDefinedClasses() adapte eğer bu şekilde giderdi kesinlikle daha kod yüzden onun olası.