2.4 Class Intermediates

The topics in the previous section covered the limit of PHP 4's object-oriented abilities. This section introduces a few concepts new to PHP 5: interfaces, type hinting, and static methods and properties.

2.4.1 Interfaces

In object-oriented programming, objects must work together. Therefore, you should be able to require a class (or more than one class) to implement methods that are necessary for the class to interact properly in your system.

For instance, an e-commerce application needs to know a certain set of information about every item up for sale. These items may be represented as different classes: Book, CD, DVD, etc. However, you need to know that your application can find the name, price, and inventory number of each object, regardless of its type.

The mechanism for forcing classes to support the same set of methods is called an interface. Defining an interface is similar to defining a class:

interface Sellable {

    public function getName( );

    public function getPrice( );

    public function getID( );

}

Instead of using the keyword class, an interface uses the keyword interface. Inside the interface, define your method prototypes, but don't provide an implementation.

This creates an interface named Sellable. Any class that's Sellable must implement the three methods listed in the interface: getName( ), getPrice( ), and getID( ).

When a class supports all the methods in the interface, it's called implementing the interface. You agree to implement an interface in your class definition:

class Book implements Sellable {



    public function getName( ) { ... }

    public function getPrice( ) { ... }

    public function getID( ) { ... }

}

Failing to implement all the methods listed in an interface, or implementing them with a different prototype, causes PHP to emit a fatal error.

A class can agree to implement as many interfaces as you want. For instance, you may want to have a Listenable interface that specifies how you can retrieve an audio clip for an item. In this case, the CD and DVD classes would also implement Listenable, whereas the Book class wouldn't.

When you use interfaces, it's important to declare your classes before you instantiate objects. In PHP 4, you can arrange your code in any order and PHP will still find a the class definition.

In PHP 5, that's still mostly true; however, when a class implements interfaces, PHP 5 can sometimes become confused. To avoid breaking existing applications, this requirement is not enforced, but it's best not to rely upon this behavior.

2.4.2 Type Hinting

Another way of enforcing controls on your objects is by using type hints. A type hint is a way to tell PHP that an object passed to a method must be of a certain class.

In PHP 4, you must check that an argument is of the correct type by yourself. This results in many calls to get_class( ) and is_array( ) inside your code.

To ease this burden, PHP 5 allows you to transfer the job of type checking to PHP. You can optionally specify a class name in function and method prototypes. This only works for classes, though, not for any other variable types. You cannot, for example, require that an argument be an array.

For example, to require the first argument to your AddressBook class's add( ) method to be of type Person:

class AddressBook {



    public function add(Person $person) {

        // add $person to address book  

    }

}

Then, if you call add( ) but pass a string, you get a fatal error:

$book = new AddressBook;



$person = 'Rasmus Lerdorf';



$book->add($person);

PHP Fatal error:  Argument 1 must be an object of class Person in...

Placing a type hint of Person in the first argument of your function declaration is equivalent to adding the following PHP code to the function:

public function add($person) {

        if (!($person instanceof Person)) {

                die("Argument 1 must be an instance of Person");

        }

}

The instanceof operator checks whether an object is an instance of a particular class. This code makes sure $person is a Person.

PHP 4 does not have an instanceof operator. You need to use the is_a( ) function, which is deprecated in PHP 5.

Type hinting has the side benefit of integrating API documentation directly into the class itself. If you see that a class constructor takes an Event type, you know exactly what to provide the method. Additionally, you know that the code and the "documentation" must always be in sync, because it's baked directly into the class definition.

However, type hinting does come at the cost of less flexibility. There's no way to allow a parameter to accept more than one type of object, so this places some restrictions upon how you design your object hierarchy.

Also, the penalty for violating a type hint is quite drastic?the script aborts with a fatal error. In a web context, you may want to have more control over how errors are handled and recover more gracefully from this kind of mistake. Implementing your own form of type checking inside of methods lets you print out an error page if you choose.

Last, unlike some languages, you cannot use type hinting for return values, so there's no way to mandate that a particular function always returns an object of a particular type.

2.4.3 Static Methods and Properties

Occasionally, you want to define a collection of methods in an object, but you want to be able to invoke those methods without the hassle of instantiating a object. In PHP 5, declaring a method static lets you call it directly:

class Format {

    public static function number($number, $decimals = 2, 

                                  $decimal = ',', $thousands = '.') {

        return number_format($number, $decimals, $decimal, $thousands);

    }

}



print Format::number(1234.567);

1,234.57

Since static methods don't require an object instance, use the class name instead of the object. Don't place a dollar sign ($) before the class name.

Static methods aren't referenced with an arrow (->), but with double colons (::)?this signals to PHP that the method is static. So, in the example, the number( ) method of the Format class is accessed using Format::number( ).

Number formatting doesn't depend upon any other object properties or methods. Therefore, it makes sense to declare this method static. This way, for example, inside your shopping cart application, you can format the price of items in a pretty manner with just one line of code and still use an object instead of a global function.

Static methods do not operate on a specific instance of the class where they're defined. PHP does not "construct" a temporary object for you to use while you're inside the method. Therefore, you cannot refer to $this inside a static method, because there's no $this on which to operate. Calling a static method is just like calling a regular function.

PHP 5 also has a feature known as static properties. Every instance of a class shares these properties in common. Thus, static properties act as class-namespaced global variables.

One reason for using a static property is to share a database connection among multiple Database objects. For efficiency, you shouldn't create a new connection to your database every time you instantiate Database. Instead, negotiate a connection the first time and reuse that connection in each additional instance:

class Database {

    private static $dbh = NULL;

    

    public function _ _construct($server, $username, $password) {

        if (self::$dbh =  = NULL) {

            self::$dbh = db_connect($server, $username, $password);

        } else {

            // reuse existing connection

        }

    }

}



$db  = new Database('db.example.com', 'web', 'jsd6w@2d');

// Do a bunch of queries



$db2 = new Database('db.example.com', 'web', 'jsd6w@2d');

// Do some additional queries

Static properties, like static methods, use the double colon notation. To refer to a static property inside of a class, use the special prefix of self. self is to static properties and methods as $this is to instantiated properties and methods.

The constructor uses self::$dbh to access the static connection property. When $db is instantiated, dbh is still set to NULL, so the constructor calls db_connect( ) to negotiate a new connection with the database.

This does not occur when you create $db2, since dbh has been set to the database handle.