views:

278

answers:

2

Hi,

I'm trying to get my head around using Iterators effectively in PHP 5, and without a lot of decent examples on the net, it's proving to be a little difficult.

I'm trying to loop over a directory, and read all the (php) files within to search for defined classes. What I then want to do is have an associative array returned with the class names as keys, the the file paths as the values.

By using a RecursiveDirectoryIterator(), I can recurse through directories. By passing this into a RecursiveIteratorIterator, I can retrieve the contents of the directory as a single dimensional iterator. By then using a filter on this, I can filter out all the directories, and non-php files which will just leave me the files I want to consider.

What I now want to do is be able to pass this iterator into another iterator (not sure which would be suitable), such that when it loops over each entry, it could retrieve an array which it needs to combine into a master array.

It's a little complicated to explain, so here's a code example:

// $php_files now represents an array of SplFileInfo objects representing files under $dir that match our criteria
$php_files = new PhpFileFilter(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)));

class ClassDetector extends FilterIterator {
    public function accept() {
        $file = $this->current(); // get the current item, which will be an SplFileInfo object

        // Match all the classes contained within this file
        if (preg_match($regex, $file->getContents(), $match)) {
            // Return an assoc array of all the classes matched, the class name as key and the filepath as value
            return array(
                'class1' => $file->getFilename(),
                'class2' => $file->getFilename(),
                'class3' => $file->getFilename(),
            );
        }
    }
}

foreach (new ClassDetector($php_files) as $class => $file) {
    print "{$class} => {$file}\n";
}

// Expected output:
// class1 => /foo.php
// class2 => /foo.php
// class3 => /foo.php
// class4 => /bar.php
// class5 => /bar.php
// ... etc ...

As you can see from this example, I'm kind of hijacking the accept() method for FilterIterator, which is completely incorrect usage I know - but I use it only as an example to demonstrate how I just want the one function to be called, and for it to return an array which is merged into a master array.

At the moment I'm thinking I'm going to have to use one of the RecursionIterators, since this appears to be what they do, but I'm not fond of the idea of using two different methods (hasChildren() and getChildren()) to achieve the goal.

In short, I'm trying to identify which Iterator I can use (or extend) to get it to pass over a single-dimensional array(?) of objects, and get it to combine the resulting array into a master one and return that.

I realise that there are several other ways around this, ala something like:

$master = array();
foreach($php_files as $file) {
    if (preg_match($regex, $file->getContents(), $match)) {
        // create $match_results
        $master = array_merge($master, $match_results);
    }
}

but this defeats the purpose of using Iterators, and it's not very elegant either as a solution.

Anyway, I hope I've explained that well enough. Thanks for reading this far, and for your answers in advance :)

A: 

I once did something similar. The source is right here and I believe is easily understandable. If you have any problem with it please let me know.

The main idea is to extend SplFileInfo and then use RecursiveIteratorIterator::setInfoClass($className); in order to obtain information about the source code. A Filter for parsing only PHP files could be nice though I decided back then to filter them by extension in the main loop.

Ionuț G. Stan
A: 

Right, I managed to get my head around it eventually. I had to use a Recursive iterator because the input iterator is essentially generating child results, and I extended IteratorIterator which already had the functionality to loop over an Iterator.

Anyways, here's a code example, just in case this helps anyone else. This assumes you've passed in an array of SplFileInfo objects (which are the result of a DirectoryIterator anyway).

class
    ClassMatcher
extends
    IteratorIterator
implements
    RecursiveIterator
{

    protected $matches;

    public function hasChildren() {
        return preg_match_all(
            '#class (\w+)\b#ism',
            file_get_contents($this->current()->getPathname()),
            $this->matches
        );
    }

    public function getChildren() {
        $classes = $this->matches[1];

        return new RecursiveArrayIterator(
            array_combine(
                $classes,                                                       // class name as key
                array_fill(0, count($classes), $this->current()->getPathname()) // file path as value
            )
        );
    }
}
JamShady