2009-03-01 11 views
5

Estoy tratando de iterar sobre un directorio que contiene una gran cantidad de archivos PHP y detectar qué clases están definidas en cada archivo.Combinando resultados iterativos recursivos: niños con padres

considerar lo siguiente:

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

El $php_files_and_content variable de anterior representa un iterador donde la clave es la ruta de archivo, y el contenido es el código fuente del archivo (como si eso no era evidente a partir del ejemplo)

Ésta se incluye a continuación, en otro iterador que coincidirá con todas las clases definidas en el código fuente, 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 
} 

La razón por la $index no es secuencial numérico se debe a 'Clase C' se define en el segundo archivo de código fuente, y por lo tanto, la matriz devuelta comienza desde el índice 0 nuevamente. Esto se conserva en RecursiveIteratorIterator porque cada conjunto de resultados representa un iterador separado (y, por lo tanto, pares clave/valor).

De todos modos, lo que estoy tratando de hacer ahora es encontrar la mejor manera de combinar estos, de tal manera que cuando iterar sobre el nuevo iterador, puedo conseguir la clave es el nombre de la clase (de la $defined_classes iterador) y el valor es la ruta del archivo original, ala:

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

Y ahí es donde estoy estancado hasta ahora.

Por el momento, la única solución que se viene a la mente es crear un nuevo RecursiveIterator, que anula el método actual() para devolver la clave externa del iterador() (que sería el original del archivo) y la tecla () método para devolver el valor del iterador actual(). Pero no estoy a favor de esta solución porque:

  • Suena complejo (lo que significa que el código se verá horrible y no va a ser intuitivo
  • Las reglas de negocio son difíciles codificados dentro de la clase, mientras que yo le gustaría definir algunos iteradores genéricos y poder combinarlos de tal manera de producir el resultado deseado.

Cualquier idea o sugerencia agradecido recibimos.

también me di cuenta de que hay mucho más rápido, más eficiente formas de hacer esto, pero th También es un ejercicio de uso de Iterators para mí mismo y también un ejercicio para promover la reutilización de código, por lo que cualquier iterador nuevo que deba escribirse debe ser lo más mínimo posible e intentar aprovechar la funcionalidad existente.

Gracias

+0

+1 para subir la barra y usar OOP en PHP –

Respuesta

2

Bien, creo que finalmente entendí esto. Aquí es más o menos lo que hice en pseudo-código:

Paso 1 Necesitamos a la lista el contenido del directorio, por lo que puede realizar lo siguiente:

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

Paso 2 debemos tener en cuenta sólo los archivos del 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); 

el PhpFileIteratorFilter no es un gran uso de código reutilizable. Un método mejor habría sido proporcionar una extensión de archivo como parte de la construcción y obtener el filtro para que coincida con eso. Aunque dicho eso, trato de alejarme de los argumentos de construcción donde no son necesarios y confiar más en la composición, porque eso hace un mejor uso del patrón de Estrategia. El PhpFileIteratorFilter podría simplemente haber utilizado el genérico FileExtensionIteratorFilter y establecerse internamente.

Paso 3 Ahora debemos leer en el contenido del archivo

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

Paso 4 Ahora queremos aplicar nuestro devolución de llamada para cada elemento (el contenido del archivo) y de alguna manera mantener los resultados . Nuevamente, tratando de hacer uso del patrón de estrategia, eliminé los argumentos innecesarios del contructor, p. $preserveKeys o similares

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

lo tanto, aquí tengo a mi iterador básica que devolverá los resultados de la $callback I suministrada a través, pero también he ampliado para crear una versión que preserve las llaves también, en lugar de usando un argumento de constructor para eso.

y así tenemos esto:

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

Paso 5 Ahora tenemos que formatearlo en una forma adecuada. Deseo las rutas de archivo para ser el valor y las claves para ser el nombre de la clase (es decir, para proporcionar una asignación directa de una clase para el archivo en el que se puede encontrar para el cargador automático)

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

Y voila, ahora puedo iterar sobre $classes_and_files y obtener una lista de todas las clases definidas en $ dir, junto con el archivo en el que están definidas. Y casi todo el código utilizado para hacer esto es reutilizable en otros contextos, así . No he codificado nada en el iterador definido para lograr esta tarea, ni he hecho ningún procesamiento adicional fuera de los iteradores

0

Creo que lo que quiere hacer, es más o menos para revertir las claves y valores devueltos de PhpFileAndContent. Dicha clase devuelve una lista de filepath => source y primero desea invertir la asignación para que sea source => filepath y luego expanda source para cada clase definida en source, por lo que será class1 => filepath, class2 => filepath.

Debería ser fácil como en su getChildren() simplemente puede acceder $this->key() para obtener la ruta del archivo actual de la fuente que se está ejecutando en getDefinedClasses().Puede escribir getDefinedClasses como getDefinedClasses($path, $source) y en lugar de devolver una matriz indexada de todas las clases, devolverá un diccionario donde cada valor de la matriz indexada actual es una clave en el diccionario y el valor es la ruta de archivo donde se definió esa clase.

Luego saldrá como quieras.

La otra opción es dejar caer su uso del RecursiveArrayIterator y en lugar de escribir su propio iterador que se inicializa (en getChildren) como

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

y luego FilePathMapperIterator convertirá la matriz de clase de getDefinedClasses a la class => filepath asignación de E se describe simplemente iterando sobre la matriz y devolviendo la clase actual en key() y devolviendo siempre la ruta de archivo especificada en current().

Creo que este último es más genial, pero sin duda es más código, por lo que es poco probable que hubiera ido de esa manera si puedo adaptar getDefinedClasses() a mis necesidades.

Cuestiones relacionadas