Advertisement

Understanding and Applying Polymorphism in PHP

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

In object oriented programming, polymorphism is a powerful and fundamental tool. It can be used to create a more organic flow in your application. This tutorial will describe the general concept of polymorphism, and how it can easily be deployed in PHP.


What is Polymorphism?

Polymorphism is a long word for a very simple concept.

Polymorphism describes a pattern in object oriented programming in which classes have different functionality while sharing a common interface.

The beauty of polymorphism is that the code working with the different classes does not need to know which class it is using since they're all used the same way.

A real world analogy for polymorphism is a button. Everyone knows how to use a button: you simply apply pressure to it. What a button "does," however, depends on what it is connected to and the context in which it is used -- but the result does not affect how it is used. If your boss tells you to press a button, you already have all the information needed to perform the task.

In the programming world, polymorphism is used to make applications more modular and extensible. Instead of messy conditional statements describing different courses of action, you create interchangeable objects that you select based on your needs. That is the basic goal of polymorphism.


Interfaces

An integral part of polymorphism is the common interface. There are two ways to define an interface in PHP: interfaces and abstract classes. Both have their uses, and you can mix and match them as you see fit in your class hierarchy.

Interface

An interface is similar to a class except that it cannot contain code. An interface can define method names and arguments, but not the contents of the methods. Any classes implementing an interface must implement all methods defined by the interface. A class can implement multiple interfaces.

An interface is declared using the 'interface' keyword:

interface MyInterface {
    // methods
}

and is attached to a class using the 'implements' keyword (multiple interfaces can be implemented by listing them separated with commas):

class MyClass implements MyInterface {
    // methods
}

Methods can be defined in the interface just like in a class, except without the body (the part between the braces):

interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}

All methods defined here will need to be included in any implementing classes exactly as described. (read the code comments below)

// VALID
class MyClass implements MyInterface {
    protected $name;
    public function doThis() {
        // code that does this
    }
    public function doThat() {
        // code that does that
    }
    public function setName($name) {
        $this->name = $name;
    }
}

// INVALID
class MyClass implements MyInterface {
    // missing doThis()!

    private function doThat() {
        // this should be public!
    }
    public function setName() {
        // missing the name argument!
    }
}

Abstract Class

An abstract class is a mix between an interface and a class. It can define functionality as well as interface (in the form of abstract methods). Classes extending an abstract class must implement all of the abstract methods defined in the abstract class.

An abstract class is declared the same way as classes with the addition of the 'abstract' keyword:

abstract class MyAbstract {
    // methods
}

and is attached to a class using the 'extends' keyword:

class MyClass extends MyAbstract {
    // class methods
}

Regular methods can be defined in an abstract class just like in a regular class, as well as any abstract methods (using the 'abstract' keyword). Abstract methods behave just like methods defined in an interface, and must be implemented exactly as defined by extending classes.

abstract class MyAbstract {
    public $name;
    public function doThis() {
        // do this
    }
    abstract public function doThat();
    abstract public function setName($name);
}

Step 1: Identify The Problem

Let's imagine that you have an Article class that is responsible for managing articles on your website. It contains information about an article, including the title, author, date, and category. Here's what it looks like:

class poly_base_Article {
    public $title;
    public $author;
    public $date;
    public $category;

    public function  __construct($title, $author, $date, $category = 0) {
        $this->title = $title;
        $this->author = $author;
        $this->date = $date;
        $this->category = $category;
    }
}

Note: The example classes in this tutorial use the naming convention of "package_component_Class." This is a common way to separate classes into virtual namespaces to avoid name collisions.

Now you want to add a method to output the information into different formats, such as XML and JSON. You might be tempted to do something like this:

class poly_base_Article {
    //...
    public function write($type) {
        $ret = '';
        switch($type) {
            case 'XML':
                $ret = '<article>';
                $ret .= '<title>' . $obj->title . '</title>';
                $ret .= '<author>' . $obj->author . '</author>';
                $ret .= '<date>' . $obj->date . '</date>';
                $ret .= '<category>' . $obj->category . '</category>';
                $ret .= '</article>';
                break;
            case 'JSON':
                $array = array('article' => $obj);
                $ret = json_encode($array);
                break;
        }
        return $ret;
    }
}

This is kind of an ugly solution, but it works -- for now. Ask yourself what happens in the future, though, when we want to add more formats? You can keep editing the class, adding more and more cases, but now you're only diluting your class.

One important principle of OOP is that a class should do one thing, and it should do it well.

With this in mind, conditional statements should be a red flag indicating that your class is trying to do too many different things. This is where polymorphism comes in.

In our example, it is clear that there are two tasks presented: managing articles and formatting their data. In this tutorial, we will refactor our formatting code into a new set of classes and discover how easy it is use polymorphism.


Step 2: Define Your Interface

The first thing we should do is define the interface. It is important to think hard about your interface, because any changes to it may require changes to calling code. In our example, we'll be using a simple interface to define our one method:

interface poly_writer_Writer {
    public function write(poly_base_Article $obj);
}

It's that simple; we have defined a public write() method that accepts an Article object as an argument. Any classes implementing the Writer interface will be sure to have this method.

Tip: If you want to restrict the type of arguments that can be passed to your functions and methods, you can use type hints, as we've done in the write() method; it only accepts objects of type poly_base_Article. Unfortunately, return type hinting is not supported in current versions of PHP, so it is up to you to take care of return values.


Step 3: Create Your Implementation

With your interface defined, it is time to create the classes that actually do stuff. In our example, we have two formats that we want to output. Thus we have two Writer classes: XMLWriter and JSONWriter. It's up to these to extract the data from the passed Article object and format the information.

Here's what XMLWriter looks like:

class poly_writer_XMLWriter implements poly_writer_Writer {
    public function write(poly_base_Article $obj) {
        $ret = '<article>';
        $ret .= '<title>' . $obj->title . '</title>';
        $ret .= '<author>' . $obj->author . '</author>';
        $ret .= '<date>' . $obj->date . '</date>';
        $ret .= '<category>' . $obj->category . '</category>';
        $ret .= '</article>';
        return $ret;
    }
}

As you can see from the class declaration, we use the implements keyword to implement our interface. The write() method contains functionality specific to formatting XML.

Now here's our JSONWriter class:

class poly_writer_JSONWriter implements poly_writer_Writer {
    public function write(poly_base_Article $obj) {
        $array = array('article' => $obj);
        return json_encode($array);
    }
}

All of our code specific to each format is now contained within individual classes. These classes each have the sole responsibility of handling a specific format, and nothing else. No other part of your application needs to care about how these work in order to use it, thanks to our interface.


Step 4: Use Your Implementation

With our new classes defined, it's time to revisit our Article class. All the code that lived in the original write() method has been factored out into our new set of classes. All our method has to do now is to use the new classes, like this:

class poly_base_Article {
    //...
    public function write(poly_writer_Writer $writer) {
        return $writer->write($this);
    }
}

All this method does now is accept an object of the Writer class (that is any class implementing the Writer interface), call its write() method, passing itself ($this) as the argument, then forward its return value straight to the client code. It no longer needs to worry about the details of formatting data, and it can focus on its main task.

Obtaining A Writer

You may be wondering where you get a Writer object to begin with, since you need to pass one to this method. That's up to you, and there are many strategies. For example, you might use a factory class to grab request data and create an object:

class poly_base_Factory {
    public static function getWriter() {
        // grab request variable
        $format = $_REQUEST['format'];
        // construct our class name and check its existence
        $class = 'poly_writer_' . $format . 'Writer';
        if(class_exists($class)) {
            // return a new Writer object
            return new $class();
        }
        // otherwise we fail
        throw new Exception('Unsupported format');
    }
}

Like I said, there are many other strategies to use depending on your requirements. In this example, a request variable chooses which format to use. It constructs a class name from the request variable, checks if it exists, then returns a new Writer object. If none exists under that name, an exception is thrown to let client code figure out what to do.


Step 5: Put It All Together

With everything in place, here is how our client code would put it all together:

$article = new poly_base_Article('Polymorphism', 'Steve', time(), 0);

try {
    $writer = poly_base_Factory::getWriter();
}
catch (Exception $e) {
    $writer = new poly_writer_XMLWriter();
}

echo $article->write($writer);

First we created an example Article object to work with. Then we try to get a Writer object from the Factory, falling back to a default (XMLWriter) if an exception is thrown. Finally, we pass the Writer object to our Article's write() method, printing the result.


Conclusion

In this tutorial, I've provided you with an introduction to polymorphism and an explanation of interfaces in PHP. I hope you realize that I've only shown you one potential use case for polymorphism. There are many, many more applications. Polymorphism is an elegant way to escape from ugly conditional statements in your OOP code. It follows the principle of keeping your components separate, and is an integral part of many design patterns. If you have any questions, don't hesitate to ask in the comments!

Advertisement