Abstract classes and interfaces

Abstract classes and interfaces

Introduction

The major part of PHP programmers know that object oriented programming in Zend Engine II is extended. Three of these features - argument hinting, abstract classes, and interfaces - revolve around checking and defining class types. In this article we will investigate how these features can help with code design and bug prevention.

Class types

PHP has 12 data types including scalar types (integer and float), complex types (arrays and objects) and special types (recourse and NULL). You can define your own types because PHP supports classes and objects. Actually, every time you define a class you also create a type.

Here we have a simple class:

<?php 
      
class Question  
       
    function 
answer$response_s ) { 
        
// ... 
    

       

?>

If you defined Question class you have automatically defined Question data type. An object instantiated from the Question class belongs to the Question type, as does an object instantiated from a child class of Question.

Here is the class that inherits Question:

<?php 

class ResourceQuestion extends Question 
    
// ... 

    
function addResourceURL$url_s ) { 
        
// ... 
    



                 
$question = new ResourceQuestion(); 
?>

$question object has a complex identity. It relates to the ResourceQuestion type, but it is also relates to the Question type, because ResourceQuestion extends the Question class.

Why types of the classes are important?

If you know the object type you know its characteristics and features. If you say that $question object belongs to the Question type, it means that it has answer() method that can be called. And if you say that $question is the object of the ResourceQuestion class as well, it means that that object has both methods answer(), and setResourceURL().

Testing and Setting Type

PHP 5 has instanceof operator that can be used for checking the type of the object. Instanceof operator has two parameters. First parameter is a tested object, second is the type you are checking the object. So if you want to confirm that $question is a Question object you would use this syntax:


<?php
if ( $question instanceof Question ) { 
    print 
"\$question is type Question\n"
?>

Let’s expand the Question class. It is a part of the simplified quiz library. Every Question object requires Marker object which is responsible for checking a user's response. In that example Question class constructor argument is Marker object:

<?php 

class Question  
    private 
$marker
    public 
$prompt ""

    function 
__construct$prompt_s$marker ) { 
        
$this->prompt $prompt_s
        
$this->marker $marker
    } 



?>

The code that calls __construct() may be beyond our control, so what happens if the wrong kind of object is passed into the $marker argument? Actually, nothing happens. And that is a problem. We just save Marker object to the attribute ready for the further using in the answer() method. If the wrong object is passed to the constructor you won't discover the error until answer() is called:

    <?php 

class Question  
    private 
$marker
    public 
$prompt ""

    function 
__construct$prompt_s$marker ) { 
        
$this->prompt $prompt_s
        
$this->marker $marker// bug occurs here 
    


    function 
answer$response_s ) { 
        
$this->marker->mark$response_s ); // effect of bug here 
    



?>

It is exactly that situation where type checking can prevent bugs. If we don’t want false data to penetrate in our class we need to check the $marker argument’s type manually:

<?php function __construct$prompt_s$marker ) { 
    if ( ! ( 
$marker instanceof Marker ) ) { 
        die( 
"Expected Marker object" );    
    } 

    
$this->prompt $prompt_s
    
$this->marker $marker// type guaranteed    
?>

However manual checking requires a lot of time and enlarges your code. PHP 5 solved that problem by class type hinting. By preceding an argument with a type name in a method declaration you can ask the PHP engine to enforce that type:


    <? 
function __construct$prompt_sMarker $marker ) { 
    
$this->prompt $prompt_s
    
$this->marker $marker
}

By writing ‘Marker’ before $marker variable we set its type. It means that passing the object that has another type will cause the following error:


Fatal error: Argument 2 must be an object of class Marker in...

So, when our answer() method is called we can be sure that Question::$marker attribute contains Marker object.

Working with class types: Abstract classes

You have seen that user-defined types are useful in that they guarantee an interface. In fact you can take this further and divorce interface from implementation.

Here's an implementation of the Question and Marker classes


<?php 

class Question  
    private 
$marker
    public 
$prompt ""
    public 
$score 0
    public 
$response ""

    function 
__construct$prompt_sMarker $marker ) { 
        
$this->prompt $prompt_s
        
$this->marker $marker
    } 

    function 
answer$response_s ) { 
        
$this->score 0
        
$this->response $response_s
        if ( 
$this->marker->mark$response_s ) ) { 
            
$this->score 1
        } 
    } 


class 
Marker 
    protected 
$condition
    function 
__construct$condition_s ) { 
        
$this->condition $condition_s
    } 
    
    function 
mark$response_s ) { 
        return ( 
$this->condition == $response_s ); 
    } 

?>

In that example we have expanded the Question type and now it supports the score of one or zero. When it is called in the Question class it compares string from the $condition and response passed for the method mark(). If strings match method returns true. Here is an example that checks these classes work:

$q = new Question( "how many beans make 5", 
                    new Marker( "five" ) ); 
$responses = array( "five", "six" ); 

foreach ( $responses as $response ) { 
    $q->answer( $response ); 
    print "response: $response scores {$q->score}\n"; 


// output: 
// response: five scores 1 
// response: six scores 0

For illustrating the class relationships you can use diagrams. Unified Modeling Language (UML) can be used for that purpose. The following diagram shows the relationships between Question and Marker classes:

You can also improve the Marker class for checking exact matches and list items matches. That’s why we need some method for distinguishing the types of the conditions. Here is an example that uses constant flags:


<?php class Marker 
    protected 
$condition
    protected 
$condWords

    const 
MATCH 1
    const 
CLIST 2
    private 
$type MATCH

    function 
__construct$condition_s$type=) { 
        
$this->type $type
        if ( 
$type == self::CLIST ) { 
            
$this->condWords 
                
preg_split"/\s*,\s*/"$condition_s ); 
        } else { 
            
$this->condition $condition_s
        } 
    } 
    
    function 
mark$response_s ) { 
        if ( 
$this->type == self::CLIST ) { 
            
// implement list marking
            
return true
        } else { 
            return ( 
$this->condition == $response_s ); 
        } 
    } 
?>

Maker class uses two constants: Marker::MATCH and Marker::CLIST to keep track of the kinds of marking. If Marker::$type property includes value defined in Marker::MATCH, Marker::mark() will compare strings. If Marker::$type includes value defined in Marker::CLIST, object breaks the response string into tokens and tests each one.

We acquire the value of the Marker::$type flag in the constructor. $type argument in the constructor method definition sets the default value that can be used by user.

The example omits the marking logic for now, in order to save space, and to keep the focus on the design of the code. Notice the similar patterns in the constructor and the mark() method? You might just get away with this code until the client realizes that she needs regular expression marking - oh, and arithmetic evaluation of answers. At this stage the Marker class becomes horribly bloated. Not only will it grow in size, but also you must maintain two parallel conditional statements. The first, in the constructor, manages the initial compilation of the condition string, if needed; and the second, in the mark() method, handles the evaluation of the user's response. The need to keep parallel conditional statements in line with one another can make code hard to maintain.

When you see two conditional statements that mirror one another in a class it is often a sign that you should consider separating these alternate implementations from your interface. You can do this using an abstract class.

Abstract class is created by adding abstract keyword to the class declaration:

<?php
abstract class MyClass 
    
// ... 

?>

After fulfilling that you won’t be able to create objects of that class. Abstract classes are meant for creating their subclasses. Abstract classes can have abstract methods as well as common attributes and methods. In the abstract method’s declaration word abstract should be in the first place. Abstract methods shouldn’t have method body; they just describe the interface only.


<?php abstract class MyClass 
    abstract function 
aMethod$an_arg ); ?>
}

That code changes Marker class and makes it abstract:

<?php 
abstract class Marker 
    protected 
$condition
    function 
__construct$condition_s ) { 
        
$this->condition $condition_s
        
$this->handleCondition$condition_s ); 
    } 

    public function 
getCondition() { 
        return 
$this->condition
    } 

    abstract protected function 
handleCondition$condition_s ); 
    abstract function 
mark$response_s ); 


?>

In that code there is used abstract keyword for modifying as Marker class as well as its methods: handleCondition() and mark(). Constructor saves unhandled string to the attribute and then calls the handleCondition() abstract method.

Any class that extends an abstract superclass must provide implementations for the parent's abstract methods or must itself be declared abstract. If you fail to do this, the PHP engine will show a fatal error when the constructor is called:

class WrongMarker extends Marker { 


//Fatal error: Cannot instantiate abstract class WrongMarker in..

In implementing abstract methods, you must define the same number of arguments, reproduce any type hinting in the argument list, and ensure that the implementing method's access level (public, protected or private) is no stricter than that defined in the abstract parent. In short you should match the abstract method declaration all but absolutely

Thus abstract class guarantees the implementation of all methods. Here are two classes that extend Marker class:

<?php 
class MatchMarker extends Marker 
    protected function 
handleCondition$condition_s ) { 
        
// implementation isn’t required 
    

    
    function 
mark$response_s ) { 
        return ( 
$this->condition == $response_s ); 
    } 


class 
ListMarker extends Marker 
    protected 
$condWords = array(); 

    protected function 
handleCondition$condition_s ) { 
        
$this->condWords preg_split"/\s*,\s*/"$condition_s ); 
    } 
    
    function 
mark$response_s ) { 
        
$respWords preg_split"/\s*,\s*/"$response_s ); 
        
$commonTerms array_intersect$this->condWords$respWords ); 
        if ( 
count$commonTerms ) == count$this->condWords ) ) { 
            return 
true
        } 
        return 
false
    } 

?>

MatchMarker class implements simple comparison of the strings. ListMarker also implements the variant of comparing. That class gets the condition string and user’s response as a word list and uses the regular expression for removing unnecessary spaces.

The user’s input should contain at least the same number of terms as the condition list; every term should match the response, otherwise question won’t be counted as correct. That code isn’t flexible enough for real using, but it is enough for us now.

Here is the code that checks our classes.


$questions[] = new Question("How many data types does PHP have?", new MatchMarker("12") ); 
$questions[] = new Question("Name the access control keywords", 
                    new ListMarker("private, public, protected") ); 
$answers = array( "12", "private, public, protected" ); 

foreach( $questions as $q ) { 
    $q->answer( array_shift( $answers ) ); 
    print "prompt:   {$q->prompt}\n"; 
    print "response: {$q->response}\n"; 
    print "score:    {$q->score}\n\n"; 


// output: 
// prompt:   How many data types does PHP have? 
// response: 12 
// score:    1 
// 
// prompt:   Name the access control keywords 
// response: private, public, protected 
// score:    1

The structure of our classes looks as follows:

Till that point we haven’t changed Question class. Question class works with Marker type object as well. Even though we have created two new classes, the fact that they extend Marker makes the changes entirely transparent to the Question class.

Using of specialized classes that provides only general interfaces is called as class switching or polymorphism. By hiding the details of the implementations from the client code we keep to minimum interdependence of the classes.

Class types and abstract classes in PHP 5 allow working with object with greater certainty. Abstract classes can be emulated using PHP 4. We can use different naming schemas and define the abstract methods which contains die() operators. For example:

<?php 

class Marker 
    
// ... 

    
function handleCondition$condition_s ) { 
        die( 
"handleCondition – abstract method" ); 
    } 

    function 
mark$response_s ) { 
        die( 
"handleCondition – abstract method" ); 
    } 


?>

By defining the abstract methods in this way you make child classes override them. The main problem is that if you haven’t overridden such “abstract” method in the child class you won’t see it without calling that method. In PHP 5 there will appear an error in such situation.

Working with class types: interfaces

As you could see object can belong to more than one type. Object that belongs to any class at the same time belongs to all parent classes in the inheritance hierarchy.

A class can extend only one class, thus in PHP 4 object can belong to only one type family. PHP 5 however supports interfaces: class – like structures that allow placing objects in more than one type family.

Interfaces are declared with the help of interface keyword. They are similar to the abstract classes but they shouldn’t contain method implementation at all. Interface contains only attributes and abstract methods.

Here is the interface for XML-data outputting:

<?php 
interface XMLable 
    public abstract function 
toXML( ); 

?>

Class can implement the interface using implements keyword. That string is placed in the class declaration after extends string:

class MyClass extends ParentClass implements anInterface { 
        //... 
}

Any class that implements the interface should provide the implementation for al methods defined in the interface or must be itself declared as abstract. Class can implement a lot of interfaces; names of the implemented interfaces are added to the implements string:

class MyClass implements anInterface, anotherInterface { 
        //... 
}

Here is an edited version of the Question class that implements the XMLable interface:

class Question implements XMLable { 

    function toXML( ) { 
        return 
<<<XMLFRAGMENT 
    <question> 
        <prompt>{$this->prompt}</prompt> 
        <response>{$this->response}</response> 
        <condition>{$this->marker->getCondition()}</condition> 
    </question> 

XMLFRAGMENT; 
    } 
}

Now Question object belongs to the Question type as well as to the XMLable. In toXML() method implementation we create the XML string that contains Question object data.

Now imagine a class unrelated to the Question class. Perhaps a User class that stores a name and email address. We can round off this example by having User (which happens to extend a class called Individual) implement XMLable:

<?php 
class Individual 
    public 
$name ""
    public 
$email 0

    function 
__construct$name_s$email_s ) { 
        
$this->name $name_s
        
$this->email $email_s
    } 


class 
User extends Individual implements XMLable  
     
    function 
getLastLogin() { 
        
// ... 
    


    function 
toXML() { 
        return 
<<<
XMLFRAGMENT 
    
<userdata
        <
name>{$this->name}</name
        <
email>{$this->email}</email
    </
userdata
        
XMLFRAGMENT
    } 
}
?>

User class has nothing in common with Question class except that they both implement XMLable interface:

It means that any exterior class can work with Question and User objects as with XMLable type objects without looking at its main type. Here is such a class:

    class ObjectDump { 
    private $xmlStr; 
    private $objects = array(); 
                
    function addXMLable( XMLable $object ) { 
        $this->objects[] = $object; 
    } 
             
    function output() { 
        $xmlStr  = "<odump>\n"; 
         foreach($this->objects as $output) { 
            $xmlStr .= $output->toXML(); 
        } 
        $xmlStr .= "</odump>\n"; 
        return $xmlStr; 
    } 
       
    function __toString() { 
        return $this->output(); 
    } 
}

ObjectDump class accepts XMLable type objects using a class type hint in the addXMLable() method to enforce this. No matter how different the various objects it is passed may be, the ObjectDump class knows that each will implement an toXML() method, and that's all that matters to it. The ObjectDump class takes advantage of this knowledge in the output() method, which loops through all the XMLable objects it has aggregated, combining the output from each object's toXML() into a single XML string. Of course, all we know of each of these objects is that they have the method in question. Class type hinting can enforce object arguments, but return types are not enforced. Interfaces should be well documented to help programmers provide sane implementations in their classes.

Like the abstract classes interfaces describe the characteristics which clent can use when works with objects. PHP works with interfaces defining the integrated interfaces Iterator and IteratorAggregate.

When PHP encounters the IteratorAggregate type object inside the foreach operator it uses implemented methods for traversing the collection.

Summary

PHP provides simple working with integrated and user-defined types. You can assign any type to the variable and pass any type argument to the method or function. That flexibility should be used! Abstract classes and interfaces are used for separating interface and its implementation.


 

  • Top