Developer Forums | About Us | Site Map
Search  
HOME > TUTORIALS > SERVER SIDE CODING > PHP TUTORIALS > GETTING STARTED WITH OBJECTS WITH PHP V5


Sponsors





Useful Lists

Web Host
site hosted by netplex

Online Manuals

Getting started with objects with PHP V5
By Matt Zandstra - 2005-08-30 Page:  1 2 3 4 5 6

Inheritance

If you're already in any way familiar with object-oriented programming, you'll know that I have been saving the best for last. The relationship between classes and the dynamic objects they generate allows for much flexibility in a system. Individual Dictionary objects encapsulate distinct sets of translation data, for example, yet the model for these varying entities is defined in the single Dictionary class.

Sometimes, though, you need to inscribe variation down at the class level. Remember the DictionaryIO class? To recap, it takes data from a Dictionary object, writes it to the file system, takes data from a file, and merges it back into a Dictionary object. Listing 12 shows a quick implementation that uses serialization to save and load Dictionary data.


Listing 12. A quick implementation using serialization


class Dictionary {
    // ...

    function asArray() {
        return $this->translations;
    }

    function getType() {
        return $this->type;
    }

    function export() {
        $this->dictio->export( $this );
    }

    function import() {
        $this->dictio->import( $this );
    }
}

class DictionaryIO {

    function path( Dictionary $dictionary, $ext ) {
        $path  = Dictionary::getSaveDirectory();
        $path .= DIRECTORY_SEPARATOR;
        $path .= $dictionary->getType().".$ext";
        return $path;
    }

    function export( Dictionary $dictionary ) {
        $translations = $dictionary->asArray();
        file_put_contents( $this->path(
                           $dictionary, 'serial'), 
                           serialize( $translations ) );  
    }

    function import( Dictionary $dictionary ) {
        $path = $this->path( $dictionary, 'serial' );
        if ( ! is_file( $path ) ) return false; 
        $translations = unserialize( 
                        file_get_contents( $path ) );
        foreach ( $translations as $term => $trans ) {
            $dictionary->set( $term, $trans );
        }
    }
}

$dict = new Dictionary( "En", new DictionaryIO() );
$dict->set( "TREE", "tree" );
$dict->export();


This example introduces a couple of simple Dictionary methods -- in particular, asArray(), which returns a copy of the $translations array. The DictionaryIO implementation has the virtue of simplicity. As is usual in example code, error checking has been omitted, but even so, this is a quick and easy way of saving data to file.

Once you have deployed a library of this sort, you soon become committed to supporting its save format. Making a format obsolete risks the goodwill of your users who may store backups in this way. But requirements change, and you may also get complaints that the output format is not easily user-editable. Such users may wish to send export files to third parties in XML format.

You now face a problem. How do you support both formats behind the DictionaryIO interface?

One solution would be to use a conditional statement inside the export() and import() methods that tests a type flag, as shown in Listing 13.


Listing 13. Using a conditional statement inside the export() and import() methods


function export( Dictionary $dictionary ) {
    if ( $this->type == DictionaryIO::SERIAL ) {
        // write serialized data
    } else if ( $this->type == DictionaryIO::XML ) {
        // write xml data
    }
}

function import( Dictionary $dictionary ) {
    if ( $this->type == DictionaryIO::SERIAL ) {
        // read serialized data
    } else if ( $this->type == DictionaryIO::XML ) {
        // read xml data
    }
}

This kind of structure is an example of a bad "code smell" in that it relies upon duplication. When a change in one place (adding a new type test, for example) requires a set of parallel changes in other places (bringing other type tests into line), code can quickly become error-prone and hard to read.

Inheritance offers a much more elegant solution. You can create a new class XmlDictionaryIO that inherits the interface laid down by DictionaryIO, but overrides some of its functionality.

You create a child class using the extends keyword. Here is a minimal implementation of the XmlDictionaryIO class:


XmlDictionaryIO extends DictionaryIO {
}

XmlDictionaryIO is now functionally identical to DictionaryIO. Because it inherits all public (and protected) attributes from DictionaryIO, you can do all the same things with an XmlDictionaryIO object that you can do with a DictionaryIO object. This relationship extends to object type. An XmlDictionaryIO object is obviously an instance of the XmlDictionaryIO class, but it is also an instance of DictionaryIO -- in the same way that a person is a human, a mammal, and an animal all at the same time and in that order of generalization. You can test this using the instanceof operator, which returns true if the object is a member of the indicated class, as shown in Listing 14.


Listing 14. Using the instanceof operator to test inheritance


$dictio = new XmlDictionaryIO();
if ( $dictio instanceof XmlDictionaryIO ) {
    print "object is an instance of XmlDictionaryIO\n";
}

if ( $dictio instanceof DictionaryIO ) {
    print "object is an instance of DictionaryIO\n";
}

This outputs:


object is an instance of XmlDictionaryIO
object is an instance of DictionaryIO


Just as instanceof accepts that $dictio is a DictionaryIO object, so, too, will methods accepting these objects as arguments. This means that an XmlDictionaryIO object can be passed to the Dictionary class' constructor, even though DictionaryIO is the type specified by the constructor's signature.

Listing 15 is a quick and dirty XmlDictionaryIO implementation that uses DOM for its XML functionality.


Listing 15. XmlDictionaryIO implementation


class XmlDictionaryIO extends DictionaryIO {

    function export( Dictionary $dictionary ) {
        $translations = $dictionary->asArray();
        $doc = new DOMDocument("1.0");
        $dic_el = $doc->createElement( "dictionary" ); 
        $doc->appendChild( $dic_el );
        foreach ( $translations as $key => $val ) {
            $term_el = $doc->createElement( "term" );
            $dic_el->appendChild( $term_el );
            $key_el = $doc->createElement("key", $key );
            $val_el = $doc->createElement(
                      "value", $val );
            $term_el->appendChild( $key_el );
            $term_el->appendChild( $val_el );
        }
        file_put_contents( $this->path( 
                           $dictionary, 'xml'), 
                           $doc->saveXML() );
    }

    function import( Dictionary $dictionary ) {
        $path = $this->path( $dictionary, 'xml');
        if ( ! is_file( $path ) ) return false;
        $doc = DOMDocument::loadXML( 
               file_get_contents( $path ) );
        $termlist = $doc
                    ->getElementsByTagName( "term" );
        foreach ( $termlist as $term ) {
            $key = $term->getElementsByTagName( "key" )
                   ->item( 0 )->nodeValue;
            $val = $term
                   ->getElementsByTagName( "value" )
                   ->item( 0 )->nodeValue;
            $dictionary->set( $key, $val ); 
        }
    }
}


The details of acquiring and generating XML can be taken for granted. There are plenty of ways of getting this done, including the excellent SimpleXML extension. In summary, the import() method takes an XML document and uses it to populate a Dictionary object. The export() method takes the data from a Dictionary object and writes it to an XML file. (In the real world, you would probably use an XML-based format called XLIFF, which is suitable for importing into third-party translation tools.)

Notice that both import() and export() call the utility method path(), which doesn't exist in the XmlDictionaryIO class. This doesn't matter because path() is implemented in DictionaryIO. When XmlDictionaryIO implements a method, it is this implementation that is invoked for an XmlDictionaryIO object when the method is called. When no implementation is present, the call falls through to the parent class.

Figure 2 shows the inheritance relationship between the DictionaryIO and XmlDictionaryIO classes. The closed arrow denotes inheritance and points from child to parent.


Figure 2. An inheritance relationship
inheritance relationship

View Getting started with objects with PHP V5 Discussion

Page:  1 2 3 4 5 6 Next Page: Summary

First published by IBM developerWorks


Copyright 2004-2024 GrindingGears.com. All rights reserved.
Article copyright and all rights retained by the author.