Lesser-Known “Features” of PHP’s OO Model

    Lorna Jane Mitchell
    Share

    The vast majority of today’s applications written in PHP are object-oriented, and in general the core OOP concepts are pretty well understood by PHP developers. This article pushes the boundary of your understanding and shows you some tricks, or potential pitfalls depending on your perspective, of OOP in PHP.

    Inheritance for Interfaces and Traits

    Let’s begin in familiar territory: interfaces. An interface in PHP allows us to define a contract that any object implementing this interface must itself implement. But did you know that interfaces can also inherit other interfaces, and that either the parent or child interface can be implemented by a class?

    Consider this code, which defines an interface, another which extends it, and a class that implements the child interface:

    <?php
    interface Reversible
    {
        function reverse($target);
    }
    
    interface Recursible extends Reversible
    {
        function recurse($target);
    }
    
    class Tricks implements Recursible
    {
        public function recurse($target) {
            // something cool happens here
        }
    
        public function reverse($target) {
            // something backward happens here
        }
    }

    I’ve defined an interface named Reversible, and another one named Recursible that extends it. When I implement Recursible in the Tricks class, both the recurse() method from the Recursible interface and the reverse() method from the Reversible interface must be present.

    This is a useful technique to employ when an interface will contain methods that are used across one set of classes, but another set of classes need these and an additional set of methods. You can make a composite interface as shown here rather than implement two interfaces.

    Traits offer a similar pattern. If you haven’t had an opportunity to use traits yet, these look like classes and can contain complete method definitions that can be applied to any class(es) in your application, without the need to inherit from a common location. We’re often taught that it’s better to move code to a common parent class than to copy-and-paste between classes, but sometimes classes aren’t related and the inheritance is false. Traits are a great feature because they allow us to re-use code even where objects aren’t similar enough to justify inheritance.

    Let’s look at a simple trait example. (Warning: the zany naming scheme from the previous example is still in evidence.)

    <?php
    trait Reversible
    {
        public function reverse($target) {
            return array_reverse($target);
        }
    }

    The syntax for traits looks very much like a class, and indeed traits can contain both properties and methods – including abstract methods. These are then applied in a class that brings the trait into it by using the use keyword… or they can also be applied to a trait, as we see here:

    <?php
    trait RecursivelyReversible
    {
        use Reversible;
    
        public function reverseRecursively($target) {
            foreach($target as $key => $item) {
                if(is_array($item)) {
                    $target[$key] = $this->reverseRecursively($item);
                }
            }
            return $this->reverse($target);
        }
    }

    Now we’ve got a trait that uses another trait and adds a method of its own. This method calls a method in the first trait. At this point, we can apply the trait to the class, and since the traits contain the feature I want to illustrate here, the class doesn’t contain anything else.

    <?php
    class Mirror
    {
        use RecursivelyReversible;
    }
    
    $array = [1, "green", "blue", ["cat", "sat", "mat", [0,1,2]]];
    $reflect = new Mirror();
    print_r($reflect->reverseRecursively($array));

    If you run the code, you’ll see that not only does the top-level of the array get reversed, but that PHP also drills into and reverses all of the child elements as well.

    How Private is a Private Property?

    So you thought that a private property was only accessible from within the current object? Not quite true! Actually the restriction is only on class name, so objects of the same class can access one another’s private properties and methods. To illustrate this, I’ve created a class with a private property and a public method that accepts an instance of the same class of object as an argument:

    <?php
    class Storage
    {
        private $things = [];
    
        public function add($item) {
            $this->things[] = $item;
        }
    
        public function evaluate(Storage $container) {
            return $container->things;
        }
    }
    
    $bucket = new Storage();
    $bucket->add("phone");
    $bucket->add("biscuits");
    $bucket->add("handcream");
    
    $basket = new Storage();
    print_r($basket->evaluate($bucket));

    You might think that $basket would not have access to $bucket‘s private data, but actually the code above works just fine! Asking whether this behavior is a gotcha or a feature is like asking if a plant is a flower or a weed; it depends on your intention and perspective.

    What Does an Abstract Class Look Like?

    An abstract class is usually thought of as being an incomplete class; we only define partial functionality and use the abstract keyword to stop anything from attempting to instantiate it.

    <?php
    class Incomplete
    {
        abstract public function notFinished();
    }

    If you try to instantiate Incomplete, you’ll see the following error:

    PHP Fatal error: Class Incomplete contains 1 abstract method
    and must therefore be declared abstract or implement the
    remaining methods (Incomplete::notFinished) in /home/lorna/
    sitepoint/oop-features/incomplete.php on line 5

    The message is self-explanatory, but now consider another class:

    <?php
    abstract class PerfectlyGood
    {
        public function doCoolStuff() {
            // cool stuff
            return true;
        }
    }

    It’s a perfectly valid class aside from the abstract keyword. In fact, you can mark any class as abstract if you wish. Other classes can extend it, but it can’t itself be instantiated. This can be a useful device for library designers who want developers to extend their classes rather than making use of them directly. Zend Framework has a rich tradition of abstract classes for exactly this reason.

    Type Hints Don’t Autoload

    We use type hints to ensure that an incoming parameter to a method meets certain requirements by giving the name of a class or interface that it must be (or be related to). However, PHP does not call the autoloader if the class or interface given in a type hint hasn’t been declared yet; we’ll just see the missing class declaration error.

    <?php
    namespace MyNamespace;
    
    class MyException extends Exception
    {
    }
    
    class MyClass
    {
        public function doSomething() {
            throw new MyException("you fool!");
        }
    }
    
    try {
        $myclass = new MyClass();
        $myclass->doSomething();
        echo "that went well";
    }
    catch (Exception $e) {
        echo "uh oh... " . $e->getMessage();
    }

    The class name in the catch clause is actually a type hint, but since we didn’t indicate that the Exception class was in the top-level namespace, PHP thinks we mean MyNamespaceException which doesn’t exist. The missing class doesn’t raise an error, but our exception now misses the catch clause. You might expect that we’d see a missing MyNamespaceException message, but instead we get the unspeakably ugly “Uncaught Exception” error:

    Fatal error: Uncaught exception 'MyNameSpaceMyException'
    with message 'you fool!' in /home/lorna/sitepoint/
    oop-features/namespaced_typehints.php:11

    This behavior makes complete sense if you think about it – if something named in a type hint isn’t already loaded, then by definition the incoming parameter cannot match it. I had to make this mistake myself before I really thought about it, and it’s just a typo! If you catch Exception rather than just Exception, this works as I had originally intended.

    And Finally

    The finally clause is a feature worth knowing about that was recently introduced in PHP 5.5. If you’ve used other programming languages with exceptions then you may have seen this construct before. There’s only ever one try block, we can have as many catch blocks as we please, and from this version of PHP we can also add a finally.

    Here’s the previous code example again, with finally added:

    <?php
    namespace MyNameSpace;
    
    class MyException extends Exception
    {
    }
    
    class MyClass
    {
        public function doSomething() {
            throw new MyException("you fool!");
        }
    }
    
    try {
        $myclass = new MyClass();
        $myclass->doSomething();
        echo "that went well";
    }
    catch (Exception $e) {
        echo "uh oh ... " . $e->getMessage();
    }
    finally {
        echo "move along, nothing to see here";
    }

    The finally clause will always happen, regardless of whether we reached the end of the try block, entered any of the catch blocks, or if there are more uncaught exceptions on the way. In this example, the output from the finally block actually appears before the error about the uncaught exception because it’s not uncaught until we’ve ended the try/catch/finally section.

    Conclusion

    Certainly some of the examples I’ve shown here are quite far-fetched, and hopefully you wouldn’t run into them every day, but I do think it’s worth knowing what edge cases look like so that you can design and build systems correctly. Understanding the building blocks can certainly help us to get our jobs done and to debug problems quickly, and I love sharing ideas to make that easier!

    What gotchas have you found in PHP? Please share with the rest of us in the comments section.

    Image via Fotolia