Thursday 23 May 2013

PHP: glob() and how PHP's design-by-random-contribution sux

G'day:
A coupla weeks back I undertook to write some code to emulate a web server directory listing in PHP. And having written the code I offered it up for code review on Stack Exchange's Code Review subsite.

I didn't get much feedback, and on the whole it was (at least partially misguided ~) advice on the logic, rather than the PHP (so missing the point of what I was asking), but there were a coupla interesting things to take away from it.

The most interesting thing is that the person on Code Review had to say was that instead of using the opendir() / readdir() approach, one can simply use glob() instead. Sample code:

<?php
$dir = realpath("./sample");
echo "<h2>Directory:</h2>";
echo "$dir<br><hr>";

echo "<h2>readdir():</h2>";
opendir($dir);
while ($entry = readdir()) {
    echo "$entry<br>";
}
// dir handle being implicitly closed @ end of request is OK
echo "<hr>";

echo "<h2>glob():</h2>";
foreach (glob("$dir\\*") as $entry) {
    echo "$entry<br>";
}
echo "<hr>";
?>

The output for this code is:

Directory:

D:\websites\php.local\filesystem\sample


readdir():

.
..
fromCodeReview.php
gdayworld.php
isDir.php
listing.php
phpinfo.php
subdir1
subdir2


glob():

D:\websites\php.local\filesystem\sample\fromCodeReview.php
D:\websites\php.local\filesystem\sample\gdayworld.php
D:\websites\php.local\filesystem\sample\isDir.php
D:\websites\php.local\filesystem\sample\listing.php
D:\websites\php.local\filesystem\sample\phpinfo.php
D:\websites\php.local\filesystem\sample\subdir1
D:\websites\php.local\filesystem\sample\subdir2


So that's a reasonable analogy of what I was initially trying. Slightly less convenient return values for what I wanted, if I'm honest, but so be it.

But I have one comment to make about this.

glob()

glob()?

frickin' glob()?!?

PHP, are you having a laugh? What the hell were you thinking when creating a function to list files, and you decided to call it "glob()". With some digging, I found this. But do you know what? That's still stupid, and it's bloody stupid to create a filesystem-processing function called glob(). There's no way - if PHP had a coherent approach to managing its language - that this function would have been called "glob()". Man, I love CFML sometimes.

Anyway, given there's two ways to do much the same thing, I decided to see how many other bloody ways there are, and what daft names they have. I googled for php list directory contents, and had a look at the links that appeared "above the fold" for me, and dug up a total of five different ways to do much the same thing. And only one of them had a stupid name (again, to do with "glob"). I'm not saying there's only five... just those are the ones I found.

Here's a demonstration of a basic usage of each of them:

<?php
    $dir = realpath("./sample");
    echo "<h2>Directory:</h2>";
    echo "$dir<br><hr>";

    // readdir() and glob() methods omitted

    echo "<h2>RecursiveDirectoryIterator:</h2>";
    $dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
    $dirIterator->next();
    while($dirIterator->valid()) {
        echo $dirIterator->key() . "<br>";
        $dirIterator->next();
    }
    echo "<hr>";

    echo "<h2>scandir():</h2>";
    $files = scandir($dir);
    foreach ($files as $entry){
        echo "$entry<br>";
    }
    echo "<hr>";

    echo "<h2>GlobIterator():</h2>";
    $globIterator = new GlobIterator("$dir/*");
    foreach ($globIterator as $entry){
        echo $entry->getFilename() . "<br>";
    }
    echo "<hr>";
?>

And the output:

Directory:

D:\websites\php.local\filesystem\sample

RecursiveDirectoryIterator:

D:\websites\php.local\filesystem\sample\.
D:\websites\php.local\filesystem\sample\..
D:\websites\php.local\filesystem\sample\fromCodeReview.php
D:\websites\php.local\filesystem\sample\gdayworld.php
D:\websites\php.local\filesystem\sample\isDir.php
D:\websites\php.local\filesystem\sample\listing.php
D:\websites\php.local\filesystem\sample\phpinfo.php
D:\websites\php.local\filesystem\sample\subdir1\.
D:\websites\php.local\filesystem\sample\subdir1\..
D:\websites\php.local\filesystem\sample\subdir2\.
D:\websites\php.local\filesystem\sample\subdir2\..


scandir():

.
..
fromCodeReview.php
gdayworld.php
isDir.php
listing.php
phpinfo.php
subdir1
subdir2


GlobIterator():

fromCodeReview.php
gdayworld.php
isDir.php
listing.php
phpinfo.php
subdir1
subdir2



So there's three new options there:

RecursiveDirectoryIterator


This is rather more object-oriented-seeming than the previous two examples, and seems to introduce the concept of Iterators in PHP, which will be a good topic for further investigation and another article. And this one is cool because it does the recursion. I think going forward I'd be using something like this for these operations. Also there's a bunch of keys one gets back for each entry, beyond just the file name like with the earlier options (and scandir(), below).

scandir()

As far as I can tell, this is pretty much the same as opendir() / readdir(), so I question why PHP has both approaches.

GlobIterator()

This one is a variation on its chum, implemented as a class instead of a function. I guess I can see the merit in enhancing the language like this (from procedural to OO I mean), but a pity they stuck with "glob".

As I was finishing this lot up, I found another blog article which discusses all these options, but someone far more savvy than I am when it comes to PHP (which is damning them with faint praise!).

There's nothing profound in any of this, but it's one (or five...) more things I've learned about PHP, and I'm intrigued by these iterators, so will give them a look.

I prefer having CFML's approach of the sensibly-named <cfdirectory> / directoryList(), although the filtering options on the glob options in PHP are better.

--
Adam