The Standard PHP Library

Along with the reflection API, the Standard PHP Library (SPL) is one of the most complex parts of the language. It is designed to solve standard problems easily, and, although it's quite hard to get your head around at first, learning SPL is key to true mastery of PHP.

To go into massive amounts of depth on SPL would probably be a book in itself, so what I'm going to do is pick out three example uses of SPL and run you through how they work and how they might be useful. Before I start, you need to know that most of the effort of understanding SPL comes down to understanding the concept of iterators . An iterator is a highly specialised object that processes elements in an array of similar items. By "array" I really mean "collection": don't think of iterators as needing to work on PHP arrays.

First I'm going to show you a very easy little iterator that automates the task of looping through files and directories. It's called DirectoryIterator, and you create it simply by passing in the directory you want to work with. Here's an example:

$files = new DirectoryIterator("c:");
foreach($files as $file) {
    print $file->getFilename() . "\n";
}

Those four lines of code are enough to print out all the files and directories in the C: drive of a Windows computer. If you're running Unix, change it to "/etc" or something else.

What actually happens here is that the foreach loop calls into the DirectoryIterator looking for the next file entry in the directory. This gets returned in the form of an object, upon which we call getFilename(), which is just one of several functions available to us. There's also isExecutable(), isReadable(), fileSize(), getPath(), and more - their actions are self-explanatory thanks to the naming convention!

So, what we have here is a task that would have otherwise required a bit of thinking and a bit of custom coding essentially being reduced to one line: $files = new DirectoryIterator("c:"). After that it's just a loop to pull out each individual filename - how much easier could it get?

Moving on, SPL allows us to make our objects act like arrays in certain situations. To do this, we need to implement the ArrayAccess interface and its four functions: offsetSet(), offsetGet(), offsetExists(), and offsetUnset(). Of those, the first one takes two parameters (the name of the key and its value), and the other three take just one (the name of the key).

Here's a simple example to let you see how this array/object overloading works:

<?php
    class objarray implements ArrayAccess {
        function offsetExists($offset) {
            echo "offsetExists called!\n";
            return true;      
        }

        function offsetGet($offset) {
            echo "offsetGet called!\n";
        }

        function offsetSet($offset, $value) {
            echo "offsetSet called!\n";
        }

        function offsetUnset($offset) {
            echo "offsetUnset called!\n";
        }
    }

    $myarr = new objarray();

    $myarr[5] = "foo";
    echo $myarr[5];

    if (isset($myarr[5])) {
        unset($myarr[5]);
    }
?>

Here's what that script outputs:

offsetSet called!
offsetGet called!
offsetExists called!
offsetUnset called!

Author's Note: The "implements ArrayAccess" is absolutely required in order to make your object work as an array. Without this - even if your class implements all the functions required - it would still not work. This is because PHP doesn't allow for accidents; it checks specifically for the ArrayAccess interface being implemented, and there's no way around it. This is a common theme in the SPL.

As you can see, as we set, get, check, and unset our $myarr objarray object, the relevant functions are called automatically. So, what can we do with this that's actually helpful? This is where things get complicated. Not from a coding perspective, mind you: you've just seen how easy it is. However, working out ways to use this technology is no easy task!

One possibility is to create an object that loads and saves files. For example:

$foo = file_get_contents("myfile.txt");
echo $foo;

Using a special object that implements ArrayAccess we could rewrite that like this:

echo $files["myfile.txt"];

Let's take a look at the code necessary to create this wonderful class...

class filearray implements ArrayAccess {
    function offsetExists($offset) {
        return file_exists($offset);
    }

    function offsetGet($offset) {
        return file_get_contents($offset);
    }

    function offsetSet($offset, $value) {
        return file_put_contents($offset, $value);
    }

    function offsetUnset($offset) {
        return unlink($offset);
    }
}

As you can see, that's the same as before except rather than just printing out messages we actually take some action. We can now use this class like this:

$myarr = new filearray();
 $myarr["somefile.txt"] = "This is a test.";
echo $myarr["somefile.txt"];
 $myarr["otherfile.txt"] = $myarr["somefile.txt"];

if (isset($myarr["somefile.txt"])) {
    unset($myarr["somefile.txt"]);
}

In that code we create the object, write some text to somefile.txt and print it out again, copy somefile.txt to otherfile.txt, and finally check whether somefile.txt exists and, if it does, delete it.

Of that script, only the checking whether it exists and deleting it are longer than the standard code: the rest of it is remarkably easy to read (especially copying the file) and thus an improvement.

We can make even those last two lines worthwhile by making our functions a little smarter. Right now, they just go ahead and do the requested action, but if you really want to make them worthwhile you might want to add appropriate calls to is_readable() and is_writeable() so that it doesn't generate a stream of errors.

Of course, treating files likes arrays is pretty weird, but there are many other things you could do - maybe the array needs to get serialised and sent to a server each time it's written? Maybe you want to store the array as an array, but also simultaneously write it to disk in case your application crashes?

Now, onto the last example I want to give: the generic iterator. Remember that iterators are used to work will groups of similar data, and in my limited experience (iterators were only introduced in PHP 5) they are most commonly used in foreach loops. A class that implements Iterator needs the following functions: rewind() (reset to the beginning of the collection), key() and current() (get the current key in the collection and its value), next() (move to the next item in the collection), and valid() (returns whether there are more values to be read in the collection).

With that in mind, what can we create to show off iterators? This is another one that seems simple on the surface but is less so when you think about it! So, I came up with the quirky idea of using an iterator object to count up factorials of numbers - something we've looked at twice already, so I hope you're familiar with the process! What I wanted to be able to write was this:

$myfactor = new factor(6);

foreach($myfactor as $key => $val) {
    echo "$key = $val\n";
}

That could would then count up from 0 to 6, printing out the factorials of each number along the way. As you can see, the userland code (that is, the bit outside of the Iterator we're about to create) is again very simple, which is the mark of code done well. Anyway, here's the code to make the iterator:

class factor implements Iterator {
    private $currpos = 0;
    private $max;

    private function dofactor($val) {
        if ($val <= 1) return 1;
        return $val * $this->dofactor($val - 1);
    }

    public function key() {
        return $this->currpos;
    }

    public function current() {
        if ($this->currpos == 0) return 0;
        return $this->dofactor($this->currpos);
    }

    public function next() {
        ++$this->currpos;
    }

    public function rewind() {
        $this->currpos = 0;
    }

    public function valid() {
        if ($this->currpos <= $this->max) {
            return true;
        } else {
            return false;
        }
    }

    public function __construct($maxval) {
        $this->max = $maxval;
    }
}

First, note that the class needs to implement Iterator in order to inherit the magic Iterator functionality. Without this, the class simply will not work as expected. I also put in two private variables: the current number we're working on, and also the highest number to count to (provided in the constructor).

The dofactor() recursive function has been done previously, and simply calls itself again and again to calculate the factorial of a number. Factorials were covered in the Functions chapter and in the Java chapter - see there for more information.

The key() function just returns the value of $currpos, whereas the current() function grabs the factorial for the current value. This allows us to print a neat table of factorials out.

As you probably expected, next() just increments $currpos and rewind() just sets $currpos to 0. The valid() function checks the current position against the max provided by the user when they created the object, and, if there are still factorials to be calculated, returns true. When valid() returns true, a foreach() loop will read from it again. Reading only stops when false is returned by valid().

Finally there's the __construct() constructor, which takes the maximum factorial to calculate as its only parameter, then stores that in $max for later use by valid().

We can now run the original script back, to get output like this:

0 = 0
1 = 1
2 = 2
3 = 6
4 = 24
5 = 120
6 = 720

Changing the 6 to 16 we get this instead:

0 = 0
1 = 1
2 = 2
3 = 6
4 = 24
5 = 120
6 = 720
7 = 5040
8 = 40320
9 = 362880
10 = 3628800
11 = 39916800
12 = 479001600
13 = 6227020800
14 = 87178291200
15 = 1307674368000
16 = 20922789888000

Apart from that one number, the code is identical: it's fast, it's very easy, and it's a great little hack to calculate ascending factorials.

Hopefully these three examples should have shown you that SPL opens up a whole new world of solution-oriented programming with PHP. While OOP enables you to re-use chunks of code, SPL enables you to re-use generic ideas and repurpose them as you see fit in individual projects.

But, as I said at the beginning of the chapter, we've only space to scratch the surface here: there's much more to SPL, and if you've made it this far without being utterly boggled, I recommend you check out the full PHP documentation to learn more.

 

Want to learn PHP 7?

Hacking with PHP has been fully updated for PHP 7, and is now available as a downloadable PDF. Get over 1200 pages of hands-on PHP learning today!

If this was helpful, please take a moment to tell others about Hacking with PHP by tweeting about it!

Next chapter: Reflection >>

Previous chapter: Error handling

Jump to:

 

Home: Table of Contents

Copyright ©2015 Paul Hudson. Follow me: @twostraws.