Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>OK, I think I finally got my head around this. Here's roughly what I did in pseudo-code:</p> <p><strong>Step 1</strong> We need to list the directory contents, thus we can perform the following:</p> <pre><code>// 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); </code></pre> <p><strong>Step 2</strong> We need to consider only the PHP files</p> <pre><code>class PhpFileIteratorFilter { public function accept() { $current = $this-&gt;current(); return $current instanceof SplFileInfo &amp;&amp; $current-&gt;isFile() &amp;&amp; end(explode('.', $current-&gt;getBasename())) == 'php'; } } // Extends FilterIterator, and accepts only .php files $php_files = new PhpFileIteratorFilter($dirContents); </code></pre> <p><em>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.</em></p> <p><strong>Step 3</strong> We must now read in the file contents</p> <pre><code>class SplFileInfoReader extends FilterIterator { public function accept() { // make sure we use parent, this one returns the contents $current = parent::current(); return $current instanceof SplFileInfo &amp;&amp; $current-&gt;isFile() &amp;&amp; $current-&gt;isReadable(); } public function key() { return parent::current()-&gt;getRealpath(); } public function current() { return file_get_contents($this-&gt;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); </code></pre> <p><strong>Step 4</strong> 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. <code>$preserveKeys</code> or similar</p> <pre><code>/** * 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-&gt;callback = $callback; parent::__construct($it); } public function accept() { return $this-&gt;hasChildren(); } public function hasChildren() { $this-&gt;results = call_user_func($this-&gt;callback, $this-&gt;current()); return is_array($this-&gt;results) &amp;&amp; !empty($this-&gt;results); } public function getChildren() { return new RecursiveArrayIterator($this-&gt;results); } } /** * Overrides ArrayCallbackFilterIterator to allow a fixed $key to be returned */ class FixedKeyArrayCallbackFilterIterator extends ArrayCallbackFilterIterator { public function getChildren() { return new RecursiveFixedKeyArrayIterator($this-&gt;key(), $this-&gt;results); } } /** * Extends RecursiveArrayIterator to allow a fixed $key to be set */ class RecursiveFixedKeyArrayIterator extends RecursiveArrayIterator { public function __construct($key, $array) { $this-&gt;key = $key; parent::__construct($array); } public function key() { return $this-&gt;key; } } </code></pre> <p>So, here I have my basic iterator which will return the results of the <code>$callback</code> I supplied through, but I've also extended it to create a version that will preserve the keys too, rather than using a constructor argument for it.</p> <p>And thus we have this:</p> <pre><code>// Returns a RecursiveIterator // key: file path // value: class name $class_filter = new FixedKeyArrayCallbackFilterIterator($files_and_content, 'getDefinedClasses'); </code></pre> <p><strong>Step 5</strong> 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)</p> <pre><code>// 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); </code></pre> <p>And voila, I can now iterate over <code>$classes_and_files</code> and get a list of all defined classes under $dir, along with the file they're defined in. And pretty much all of the code used to do this is re-usable in other contexts as well. I haven't hard-coded anything in the defined Iterator to achieve this task, nor have I done any extra processing outside the iterators</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload