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

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

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • mrPascal

    Thanks to Author !
    Interesting article. But I’d like to practice a little.
    unfortunately It doesnt work at my server
    I have PHP ver. 5.3.10 being installed at my server (
    I can suppose after reading previous article about “What’s New in PHP 5.5″
    that following syntax has appeared in PHP ver. 5.5
    <?php
    02 trait RecursivelyReversible
    03 {
    04 use Reversible;
    ….
    Let me know please Is It true about version of PHP
    Thanks a lot

    • boen_robot

      You mean traits? Those have appeared since PHP 5.4 even.

  • Timb

    Hi Lorna,
    very nice article indeed.
    Although I would not advice to use the hidden-private-properties. Chances are great that these kind of ‘features’ will be removed in future versions of PHP. And then, you have to rewrite a bunch of code. Which I don’t like as a lazy programmer :p
    Loved to read it anyway.
    Tim

    • Abel de Beer

      @Timb:

      Regarding the private properties: this behaviour won’t change, since it’s rather standard, also across different OOP languages. Although surprising at first, I really like the reasoning behind it:

      “Objects of the same type will have access to each others private and protected members even though they are not the same instances. This is because the implementation specific details are already known when inside those objects.”

      Think about it: of course method X knows it can access private property Y of an object of the same class – it is defined just a few lines above it! ;)

      The official page with this info: http://php.net/manual/en/language.oop5.visibility.php#language.oop5.visibility-other-objects

      Kind Regards, Abel

  • http://www.zachis.it/blog Zach Smith

    this was actually informative – not like most of what i see online. sharing :)

  • http://cvuorinen.net Cvuorinen

    Hi,
    Very nice article overall. Informative and I learned something new which is always great!

    The only thing I had trouble understanding is the point about type hints not autoloading. As far as I can see, in the example at least, the issue is with namespaces and has nothing to do with autoloading? You declare a namespace and then any class declarations inside it without fully qualified name will be treated as local to that namespace. Or am I missing something here?

    • http://www.piformation.com PFY

      I agree that the type-hinting section doesn’t quite make sense. Perhaps a little clarification on it?

  • Joel Larson

    In your “Type Hints Don’t Autoload” segment, there is incorrect information. Specifically with:

    “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:”

    The reason why it is not caught is because “Exception” defined in the contained namespace resolves to “MyNamespaceException”. When attempting to catch “MyException”, resolving as “MyNamespaceMyException”, the catch block sees that the given Exception (or parent of the Exception) does not match the cached Exception type, resolved as “MyNamespaceException”.

    Described above is the reason why “Exception”, “MyNamespaceMyException”, and “MyException” all work correctly when attempting to catch a thrown exception. A better example to give about how typecasting does not automatically invoke the autoloader is giving an example like: https://gist.github.com/JoelLarson/6059225

    It is also not mentioned that it is possible to make the PHP interpreter autoload undefined arguments that are typehinted. This is done by using register_shutdown_function alongside an autoloader. Laravel 4’s IoC container / Exception component is a good example of this.

  • http://nashwan-d.ocm Nashwan Doaqan

    Thanks for the article, I really like the private-properties hint, something new to me :)

  • Really ?

    Dear Lorna,
    in “How Private is a Private Property?”,
    you open the door for the thief!!!!
    “Dependency Injection”, think in

    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();
    $basket->add( $bucket->evaluate(bucket) );

    That´s cool!!!! Denpendency Injection!!!!
    But “$things” are private ,,, yes ,,
    Who open the door for the Thief ????