2009-03-01 37 views
5

Tôi đang cố gắng lặp qua thư mục chứa nhiều tệp PHP và phát hiện các lớp nào được xác định trong mỗi tệp.Kết hợp các kết quả lặp đệ quy: trẻ em với cha mẹ

xem xét như sau:

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

Các $php_files_and_content biến trên đại diện cho một iterator nơi quan trọng là filepath, và nội dung là mã nguồn của tập tin (như thể đó không phải là rõ ràng từ ví dụ).

này sau đó được cung cấp vào một iterator mà sẽ phù hợp với tất cả các lớp được định nghĩa trong mã nguồn, 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 
} 

Lý do các $index không được tuần tự số lượng là vì 'Class C' được định nghĩa trong tệp mã nguồn thứ hai, và do đó mảng được trả về bắt đầu từ chỉ mục 0 một lần nữa. Điều này được bảo toàn trong RecursiveIteratorIterator vì mỗi bộ kết quả đại diện cho một Iterator riêng biệt (và do đó các cặp khóa/giá trị). Dù sao, những gì tôi đang cố gắng làm bây giờ là tìm cách tốt nhất để kết hợp những thứ này, như vậy khi tôi lặp qua trình lặp mới, tôi có thể lấy khóa là tên lớp (từ trình biến đổi $defined_classes) và giá trị là đường dẫn tệp ban đầu, ala:

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

Và đó là nơi tôi bị kẹt cho đến nay. Tại thời điểm này, giải pháp duy nhất hiện ra trong đầu là tạo một RecursiveIterator mới, ghi đè phương thức current() để trả về khóa lặp() (đây sẽ là filepath gốc) và khóa () để trả về giá trị hiện tại của iterator(). Nhưng tôi không thiên về giải pháp này vì:

  • Nghe có vẻ phức tạp (có nghĩa là mã sẽ trông gớm ghiếc và nó sẽ không được trực quan
  • Các quy tắc kinh doanh được mã hóa cứng bên trong lớp, trong khi tôi muốn xác định một số vòng lặp chung chung và có thể kết hợp chúng theo cách như vậy để tạo ra kết quả cần thiết.

Bất kỳ ý tưởng hoặc gợi ý biết ơn nhận được.

tôi cũng nhận ra có rất xa nhanh hơn, hiệu quả hơn cách làm điều này, nhưng thứ cũng là một bài tập trong sử dụng Iterator cho bản thân và cũng là một bài tập trong việc thúc đẩy tái sử dụng mã, vì vậy bất kỳ Iterator mới nào phải được viết nên càng nhỏ càng tốt và cố gắng tận dụng chức năng hiện có.

Cảm ơn

+0

+1 để tăng thanh và sử dụng OOP trong PHP –

Trả lời

2

OK, tôi nghĩ cuối cùng tôi cũng đã xoay quanh vấn đề này. Dưới đây là khoảng những gì tôi đã làm trong pseudo-code:

Bước 1 Chúng ta cần phải liệt kê nội dung thư mục, do đó chúng ta có thể thực hiện như sau:

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

Bước 2 Chúng tôi cần phải xem xét chỉ các tệp 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 không phải là sử dụng tuyệt vời của mã có thể sử dụng lại. Một phương pháp tốt hơn có thể là cung cấp phần mở rộng tệp như là một phần của công trình xây dựng và nhận bộ lọc để phù hợp với điều đó. Mặc dù vậy, tôi đang cố gắng tránh xa các lập luận xây dựng, nơi họ không bắt buộc và dựa nhiều vào sáng tác, bởi vì điều đó giúp sử dụng tốt hơn mô hình Chiến lược. PhpFileIteratorFilter có thể chỉ đơn giản là đã sử dụng FileExtensionIteratorFilter chung và tự thiết lập.

Bước 3 Bây giờ chúng ta phải đọc trong nội dung tập tin

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

Bước 4 Bây giờ chúng ta muốn áp dụng callback của chúng tôi để từng hạng mục (nội dung tập tin) và bằng cách nào đó giữ lại kết quả . Một lần nữa, cố gắng sử dụng mô hình chiến lược, tôi đã thực hiện các đối số contructor không cần thiết, ví dụ: $preserveKeys hoặc tương tự

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

Vì vậy, ở đây tôi có iterator cơ bản của tôi mà sẽ trả về các kết quả của $callback tôi cung cấp thông qua, nhưng tôi cũng đã mở rộng nó để tạo ra một phiên bản đó sẽ gìn giữ các phím quá, chứ không phải là sử dụng một đối số hàm tạo cho nó.

Và do đó chúng tôi có điều này:

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

Bước 5 Bây giờ chúng ta cần phải định dạng nó thành một cách phù hợp. Tôi mong muốn các đường dẫn tập tin là giá trị, và các phím được tên lớp (tức là cung cấp ánh xạ trực tiếp cho một lớp học để các tập tin trong đó nó có thể được tìm thấy cho bộ nạp tự động)

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

Và Bây giờ, tôi có thể lặp qua $classes_and_files và nhận danh sách tất cả các lớp được xác định dưới $ dir, cùng với tệp mà chúng được định nghĩa. Và khá nhiều mã được sử dụng để thực hiện việc này cũng có thể sử dụng lại trong các ngữ cảnh khác . Tôi chưa mã hóa bất kỳ thứ gì trong Iterator được xác định để thực hiện tác vụ này, cũng như tôi không thực hiện bất kỳ xử lý bổ sung nào bên ngoài vòng lặp

0

Tôi nghĩ rằng những gì bạn muốn làm, là nhiều hơn hoặc ít hơn để đảo ngược các phím và các giá trị trở về từ PhpFileAndContent. Lớp được trả về trả về danh sách filepath => source và trước tiên bạn muốn đảo ngược ánh xạ để nó là source => filepath và sau đó mở rộng source cho mỗi lớp được xác định trong source, do đó, nó sẽ là class1 => filepath, class2 => filepath.

Nó phải dễ dàng như trong getChildren() bạn chỉ cần truy cập $this->key() để nhận đường dẫn tệp hiện tại cho nguồn bạn đang chạy getDefinedClasses() bật.Bạn có thể viết getDefinedClassesgetDefinedClasses($path, $source) và thay vì trả về một mảng được lập chỉ mục của tất cả các lớp, nó sẽ trả về một từ điển trong đó mỗi giá trị từ mảng được lập chỉ mục hiện tại là khóa trong từ điển và giá trị là filepath nơi lớp đó được định nghĩa.

Sau đó, nó sẽ xuất hiện đúng như bạn muốn.

Các tùy chọn khác là để thả bạn sử dụng RecursiveArrayIterator và thay vào đó viết iterator của riêng bạn mà được khởi tạo (trong getChildren) như

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

và sau đó FilePathMapperIterator sẽ chuyển đổi các mảng lớp từ getDefinedClasses đến class => filepath lập bản đồ tôi được mô tả đơn giản bằng cách lặp qua mảng và trả về lớp hiện tại trong key() và luôn trả về tệp được chỉ định trong current().

Tôi nghĩ rằng sau này là mát mẻ hơn, nhưng chắc chắn mã hơn vì vậy nó không chắc rằng tôi sẽ đi theo cách đó nếu tôi có thể thích ứng getDefinedClasses() cho nhu cầu của tôi.

Các vấn đề liên quan