The Documenter Class

We won't be looking at each and every line of code in this class, but to help put the following comments in context you might want to download the code now. The export method of Reflection gave us a rough idea of the kind of information we would like to see (refer to Listing 14-1). Now let's discuss the Documenter class in terms of how class information will be displayed.

Describing the Documenter Class

At the very minimum you need basic information about a class. The getFullDescription method combines existing ReflectionClass methods to create a string that matches the actual class declaration.

public function getFullDescription(){
    $description = "";
    if($this->isFinal()){
        $description = "final ";
    }
    if($this->isAbstract()){
        $description = "abstract ";
    }
    if($this->isInterface()){
        $description .= "interface ";
    }else{
        $description .= "class ";
    }
    $description .= $this->name . " ";
    if($this->getParentClass()){
        $name =  $this->getParentClass()->getName();
        $description .= "extends $name ";
    }
    $interfaces = $this->❶getInterfaces();
    $number = count($interfaces);
    if($number > 0){
        $counter = 0;
        $description .= "implements ";
        foreach($interfaces as $i){
            $description .= $i->getName();
            $counter ++;
            if($counter != $number){
                $description .= ", ";
            }
        }
    }
    return $description;
}

This code calls a number of self-explanatory, inherited methods to build a class description. The only slight complication is that, because a class can implement more than one interface, ❶ the getInterfaces method returns an array, and so requires a foreach loop. When applied to the SoapFault class, the following string is returned by the getFullDescription method:

class SoapFault extends Exception

SoapFault is correctly identified as a class rather than an interface, it is neither final nor abstract, and its derivation from Exception is documented. This is exactly the same description that you saw in Listing 14-1 when you exported this class.

Describing Methods and Data Members

Since methods are more important than data members, let's next deal with how to adapt the reflection classes to document methods. Calling the getMethods method of the ReflectionClass class creates an array of ReflectionMethod objects. The visibility of each method can then be determined by the isPublic, isProtected, or isPrivate methods of the ReflectionMethod class.

However, you want to display methods sorted by visibility—basically, you want a getPublicMethods method and an identical method for displaying private and protected methods. In order to be able to retrieve an array of ReflectionMethod objects sorted by visibility, you are going to loop through all the methods in a class and create separate arrays of each type. Let's see how this is done.

private function createMethodArrays(){
    $methods = $this->getMethods();
    //ReflectionMethod array returned
    foreach($methods as $m){
        $name = $m->getName();
        if($m->isPublic()){
            $this->publicmethods[$name] = $m;
        }
        if($m->isProtected()){
            $this->protectedmethods[$name] = $m;
        }
        if($m->isPrivate()){
            $this->privatemethods[$name] = $m;
        }
    }
}

Again, the code is quite simple. An array of all methods of a class is retrieved using the inherited ReflectionClass method getMethods, and each ReflectionMethod object is stored in the appropriate associative array, using the method name as the array key.

Each array is a private variable with a public accessor method—the prescribed way for retrieving data members. For example, to examine the public methods of a class, you simply call getPublicMethods, which will return the array populated by createMethodArrays.

Data member arrays are created in exactly the same fashion. Your class has a createDataMemberArrays that uses the getProperties method inherited from the ReflectionClass to create an array of ReflectionProperty objects. You then query each ReflectionProperty object to create arrays of public, private, and protected data members. These arrays can, in turn, be retrieved using accessor methods.

The Constructor

The createDataMemberArrays method and the companion method for creating an array of methods are both private and called from within the constructor of the Documenter class.

public function __construct($name){
    parent::__construct($name);
    $this->createDataMemberArrays();
    $this->createMethodArrays();
}

Placement of the call to the parent constructor is noteworthy. Because createDataMemberArrays and createMethodArrays both invoke methods of the parent class, it is essential that the call to the parent constructor occur first. Doing otherwise results in calling methods on a not-yet-existent object.

Method and Data Member Modifiers

It is essential to know the access modifiers for methods and data members of a class. Both the ReflectionMethod and the ReflectionParameter classes have a getModifiers method that returns an integer with bit fields set to flag the different access modifiers. Your Documenter class has its own getModifiers method that converts these flags to their string equivalents using the static getModifierNames method of the Reflection class.

public function getModifiers($r){
    if($r ❶instanceof ReflectionMethod ||
          $r ❶instanceof ReflectionProperty){
        $arr = ❷Reflection::getModifierNames($r->getModifiers());
        $description = implode(" ", $arr );
    }else{
        $msg = "Must be ReflectionMethod or ReflectionProperty";
        throw new ReflectionException( $msg );
    }
    return $description;
}

You want to ensure that only ReflectionMethod objects or ReflectionProperty objects are passed into this method so you use the operator, ❶ instanceof. This operator was introduced with PHP 5 and replaces the now-deprecated function is_a. This operator allows you to restrict use of your method to classes that support the getModifiers method and to throw a ReflectionException if the wrong type of object is passed in.

When you pass the return value of getModifiers to ❷ the static method of the Reflection class, getModifierNames, a string array of all the modifiers is returned. A series of calls to isPublic, isStatic, and like methods would achieve the same result, but using getModifierNames is by far the most succinct way of getting the string values of method and data member modifiers.

NO COMMON ANCESTOR

You might think that ReflectionMethod and ReflectionProperty objects each have a getModifiers method because they share a common interface, Reflector, and, consequently, you could type hint the parameter to this method to check for an instance of this particular interface only. However, you would be mistaken. There are only two methods of the Reflector interface: export and __toString. As far as a common class heritage is concerned, ReflectionMethod derives from ReflectionFunction and ReflectionProperty has no parent class. So there is no common parentage. That said, the fact remains that checking for an instance of the Reflector class would achieve essentially the same result as checking for ReflectionFunction and ReflectionProperty—but for the wrong reasons. It is only fortuitous that both classes have a getModifiers method. Another way to screen for the correct class would be to introspect the variable $r to determine whether it has a getModifiers method.


As an interesting aside, when introspecting the methods of a built-in interface, the modifiers are always public and abstract. In Chapter 11 you saw that PHP prohibits the use of the modifier abstract when defining the methods of a user-defined interface, despite the fact that the methods of an interface must in fact be abstract.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.234.62