Wednesday, August 12, 2009

CREATING DOCUMENTATION USING THE REFLECTION CLASSES

In Chapter 4, I introduced a simple class called DirectoryItems. You may remember
what it does, but you probably can’t remem- ber the specific methods. With a user-defined class,
looking up forgotten methods usually means rooting through the class definition file. This can take a long time, especially for large classes. For an internal class you can go to http://php.net to look up the information you need. But there are more than 100 internal classes and interfaces, and their documentation is scattered throughout the PHP site. Wouldn’t it be useful to have a central repository of documentation for all classes?
Finding documentation is one problem, but the quality of documentation is another, equally important, problem. Most developers know the value of accurately commented code, but when you are in the middle of coding, the meaning of your code always seems crystal clear, so comments appear super- fluous. Besides, there’s always the ultimate excuse for the absence of internal documentation—you want to keep file size small to reduce download time.

This is often the situation with internal documentation, but external documentation fares no better. It doesn’t make sense to write it as you go because things always change, but, by the time you’ve finished coding, docu- mentation is the furthest thing from your mind. You’re ready to move on to something else.
This chapter offers a solution to the two problems of ready availability and quality of documentation. We’re going to create a documentation class derived from a new set of classes introduced in PHP 5, the reflection group. You’ll learn how to generate documentation dynamically that will fully
describe methods and data members and that will incorporate properly formatted internal comments for user-defined classes.

What Are the Reflection Classes?

In Chapter 10, before implementing the Iterator interface, you had to under- stand its methods. To create a class that will do your documenting for you, you need to become familiar with the reflection classes. This group of classes was created for the express purpose of introspecting other classes. These classes make it possible to examine the properties of other classes by retrieving meta- data about classes; you can even use them to examine the reflection classes themselves.
Reflection provides information about the modifiers of a class or interface—whether that class is final or static, for example. It can also reveal all the methods and data members of a class and all the modifiers applied to them. Parameters passed to methods can also be introspected and the names of variables exposed. Through reflection it is possible to automate the docu- mentation of built-in classes or user-defined classes. It turns out that the central repository of information about classes was right in front of us all
the time. PHP can tell us all about itself through the mirror of the reflection classes.

The Reflection Group of Classes

The reflection group of classes or Application Programming Interface (API)
is made up of a number of different classes and one interface, shown here:

class Reflection interface Reflector
class ReflectionException extends Exception class ReflectionFunction implements Reflector class ReflectionParameter implements Reflector class ReflectionMethod extends ReflectionFunction class ReflectionClass implements Reflector
class ReflectionObject extends ReflectionClass class ReflectionProperty implements Reflector class ReflectionExtension implements Reflector

We won’t be concerned with every class in the reflection API, but a gen- eral overview will help put things in perspective. Looking at this list, you may suppose that the Reflection class is the parent class of all the reflection classes, but there is actually no class ancestor common to all reflection classes. On the other hand, the Reflector interface is shared by all classes except Reflection and ReflectionException. As far as class hierarchies are concerned, ReflectionMethod extends ReflectionFunction, ReflectionObject extends ReflectionClass, and ReflectionException extends Exception.
Our concern is with objects, so we won’t spend any time on the method ReflectionFunction. ReflectionObject shares all the methods of ReflectionClass; the only difference between these classes is that ReflectionObject takes a class instance rather than a class name as a parameter—using an instance, you can introspect a class without knowing anything about it, even its name. The class ReflectionException is derived from Exception, a class we’ve already examined.
We’re principally interested in Reflection, ReflectionClass, ReflectionMethod,
ReflectionParameter, and ReflectionProperty.

The Reflection Class

The Reflection class has two static methods: export and getModifierNames. We’ll discuss getModifierNames later in this chapter, but let’s take a look at the export method—a quick way to introspect a class—to get a taste of what Reflection can tell us. Reflection requires a ReflectionClass object as the parameter to the export method. Let’s use the SOAPFault class as an example, since we recently encountered it in Chapter 12. The export method is static. As you’ll recall from Chapter 11, static methods are invoked by using the class name and the scope resolution operator. Here’s the code to export this class:

Reflection::export(new ReflectionClass('SOAPFault'));

In this example, a class name is passed to the ReflectionClass constructor, and the resultant object is the argument to the export method of the Reflection class. The output of the export method is shown in Listing 14-1.

Class [ <internal:soap> class SoapFault extends Exception ] {
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [4] {
Property [ <default> protected $message ] Property [ <default> protected $code ] Property [ <default> protected $file ] Property [ <default> protected $line ]
}

- Methods [9] {
Method [ <internal> <ctor> public method __construct ] {
}
Method [ <internal> public method __toString ] {
}
Method [ <internal> final private method __clone ] {
}
Method [ <internal> final public method getMessage ] {
}
Method [ <internal> final public method getCode ] {
}
Method [ <internal> final public method getFile ] {
}
Method [ <internal> final public method getLine ] {
}
Method [ <internal> final public method getTrace ] {
}
Method [ <internal> final public method getTraceAsString ] {
}
}
}

Listing 14-1: Exporting SOAPFault

The export method gives a quick overview of a class. As you can see, SOAPFault extends the Exception class and possesses all the properties of Exception. Its methods are Exception class methods. This is exactly the sort of thing we want the reflection classes to do for us.

The ReflectionClass Class

The export method is quick and easy; but what if you want more information in a user-friendly format? The place to begin is with the ReflectionClass class, which you’ll extend to create a Documenter class.

NOTE There are nearly 40 methods of ReflectionClass. Often, the methods’ names clearly indi- cate their purpose. For instance, isInterface determines whether you are introspecting a class or an interface. We will only examine those methods that are of particular interest.

Methods of ReflectionClass
The getMethods and getProperties methods play an important role in class documentation. Invoking getMethods returns an array of ReflectionMethod objects. Invoking getProperties returns an array of ReflectionProperty objects. These methods and the objects returned make it possible to fully describe a class’s methods and data members.
You will recall that I promised we’d use internal comments when documenting a class. If internal comments are properly formatted, the getDocComment method of ReflectionClass can be used to incorporate them directly into your documentation.

Fortunately, ReflectionMethod and ReflectionProperty also have
getDocComment methods, so method-level and data member–level comments can also be included.

NOTE Those of you familiar with PEAR (PHP Extension and Application Repository) and phpDocumentor or the Java utility Javadoc will already know the proper format for internal comments.

ReflectionMethod and ReflectionParameter

ReflectionMethod objects contain all the information you need to fully describe a method. By using this object you can document the modifiers of a method; you can use its getParameters method to return an array of ReflectionParameter objects, which is essential for describing a method’s parameters.
A ReflectionParameter object will give you the number of parameters, their names, and any default values. You can even determine whether a parameter is a specific type of object if it is type hinted—yet another good reason to use type hinting.
There is one respect in which you might find the ReflectionMethod class wanting, however. Sometimes it’s important to know what a method returns; for example, when using the getMethods method, it is essential to know that an array of ReflectionMethod objects is returned. Since you can type hint parameters and retrieve this information it would be nice to do the same with returned values. However, because PHP is a weakly-typed language, it’s not surprising that this capability is not supported, so be sure to document return types in your comments where appropriate.

NOTE Type hinting return values is planned for PHP 6, so perhaps we can expect support for this capability in future versions of the reflection classes.

ReflectionProperty

The getProperties method of ReflectionClass is similar to the getMethods method. It returns an array of ReflectionProperty objects that can be queried in much the same way as ReflectionMethod objects. (Determining whether default values exist for data members poses some challenges; more about this shortly.)

Built-in Functions

We’ve looked at the principal classes and methods of the reflection classes, but there are some built-in PHP functions that can also be helpful when documenting classes. Most of the functions in the Class/Object group have been effectively, if not explicitly, deprecated in PHP 5 precisely because there are now reflection classes that do a superior job. However, a number of functions, such as get_declared_classes and is_object, continue to be
useful.

What Format Do You Want?

One of the major reasons for documenting classes is to make them easier for a client programmer to use. Because the client programmer is primarily interested in the public methods of a class (he wants to know how to use the class, not how it works), you should sort methods and data members by visibility, giving priority to those with public visibility.
If you have ever used a plain text editor to write code you know that syntax highlighting greatly improves readability. For this reason, the ability to change the appearance of keywords is also a desirable characteristic to incorporate into your class.
You’ve been acquainted with the capabilities of various reflection classes, and now have a fair idea of what kind of off-the-shelf functionality is available as well as what you will have to customize. You’re in a good position to begin extending ReflectionClass.

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 func- tion 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 C O MMO N A N C E ST O R

You might think that ReflectionMethod and ReflectionProperty objects each have a getModifiers method because they share a common interface, Reflector, and, con- sequently, 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 inter- face, 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.

Using the Documenter Class

That completes the description of the Documenter class. We will now use it in
a web page to display information about all internal and user-defined classes. We’ll create a sidebar of links to all existing classes and interfaces, and display detailed information in the main portion of the page. Again, we won’t discuss every line of code, only those lines that are of special interest.

Creating a Sidebar of Classes and Interfaces

Let’s create a sidebar that will display the names of all PHP classes as hyperlinks—fulfilling the promise of a central repository of information about all classes. Clicking a hyperlink will display documentation for this class in the main portion of your web page. The code to do this follows:

include 'MySQLResultSet.php'; include 'MySQLConnect.php'; include 'Documenter.php'; include 'PageNavigator.php';
$arr = get_declared_classes();
natcasesort($arr);
$classname = @$_GET["class"];
if(!isset($classname)){
$classname = current($arr);
}
echo "<h4 style=\"background-color:#fff;\">Classes</h4>";
foreach($arr as $key => $value){
echo "<a href=\"getclass.php ?class=$value\">". "$value</a><br />";
}

In addition to built-in classes, any user-defined classes that have been loaded using an include or require statement will be retrieved when you call
the get_declared_classes function. If no variable named class has been passed to this page, then $classname will default to the name of the first class in the array of declared classes. This $classname variable contains the class name that will be passed to the constructor of the Documenter class. Information about the specified class will be displayed in the center of the page. A foreach loop creates the list of hyperlinks to all available classes by creating a query string that includes the class name.
Your sidebar also displays links to all the declared interfaces. The code to do this is identical to the code to retrieve classes except that it calls the func- tion get_declared_interfaces instead of get_declared_classes. Therefore this code will not be reproduced here.

Formatting Detailed Documentation

The MySQLException class is a derived class that has a variety of methods (see Fig- ure 14-1), so use it as an example of how we would like the class documentation to look.

Let’s proceed by relating the code to this output. The web page that displays your documentation first creates an instance of the Documenter class:

try{
$class = new Documenter($classname);
echo "<h2>Name: ". $class-> getName() . "</h2>\n";
$today = date("M-d-Y");
echo "<p> Date: $today<br />";
echo "PHP version: ". phpversion() . "<br />";
echo "Type: ". $class-> getClassType() . "<br /><br />\n";
echo "<span class=\"fulldescription\">". $class-> getFullDescription(). "</span><br /><br />\n";
echo $class-> getDocComment() . "</p>\n";
...
}

Because creating an instance may throw a ReflectionException, you enclose your call to the constructor within a try block. You need to know which class we are documenting, so you display the class name by calling the inherited method getName. Knowing when documentation was created is

important, so you display the date using the date function. Likewise with the PHP version number. Since you are mixing built-in and user-defined classes, specifying the class type will reduce confusion.
As you saw earlier in this chapter, the full class description identifies whether you are dealing with a class or an interface, and also details the class parentage. Because internal comments within the class file have been properly formatted, you can extract them using the getDocComment method. When this method is called against an instance of a class, it retrieves the comment that immediately precedes the class definition. Let’s see how that’s done.

Formatting Comments for the Documenter

The getDocComment method is fussy about what it will retrieve, so let’s look at the format of the comments within an existing user-defined class. We’ll con- tinue using the MySQLException class as an example.

/** For use with MySQLConnection and MySQLResultSet classes */
class MySQLException extends Exception
{ ... }

A class-related, internal comment must meet the following conditions for the getDocComment method to work:

It must immediately precede the class definition statement.
It may run to any number of lines but must begin with a forward slash, followed by two asterisks, followed by white space, and be terminated by an asterisk and forward slash—in other words, exactly the format required by Javadoc.

The ReflectionFunction, ReflectionMethod, and ReflectionObject classes also support a getDocComment method. (As of PHP 5.1, the ReflectionProperty class also supports this method.) Exactly the same formatting rules apply. Again, internal comments must immediately precede what they document.
As you can see in Figure 14-1, the internal comments documenting the constructor are displayed immediately after the class description—as prom- ised, the Documenter class incorporates internal comments. Unfortunately, getDocComment only applies to user-defined classes and user-defined methods or data members; comments cannot be extracted for internal classes.

Documenting Methods

As shown in Figure 14-1, method documentation is displayed immediately after the class description and comments. With a view to the client programmer, public methods are displayed immediately after the class name and descrip- tion, followed by protected methods, and finally private methods. Because the MySQLException class has no protected methods, none are shown.

Methods of all levels of visibility are passed to the show_methods function to handle the details of displaying method descriptions. Here is the prototype for this function:

function show_methods(Documenter $d, $type, $arr)

One of the parameters of this function is an object. In PHP 4 you would want to ensure that this object was passed by reference by preceding the variable with & (an ampersand). As discussed in Chapter 13 in the section “__clone” on page 116, in PHP 5 all objects are automatically passed by reference, so there is no need to do this. This parameter is also type hinted, disallowing anything other than a Documenter object.
To summarize, this function displays the variable names of method parameters, type hints, and default values where applicable. Syntax high- lighting has been used for the keywords describing each method—you can quickly see in Figure 14-1 that the getMessage method of the MySQLException class is both final and public. User-defined methods are flagged as such, and any internal comments are displayed.

NOTE If you are running PHP 5.1 or higher, you can type hint the array passed to show_methods by changing the function prototype to read function show_methods(Documenter $d, $type, array $arr).

Data Members

Data members are handled in much the same way as methods. Those with the least restrictive visibility are presented first. Again, keywords are high- lighted. Even default values assigned to data members can be retrieved. Somewhat surprisingly, this is done using the getDefaultProperties method of ReflectionClass rather than by using a ReflectionProperty class method. As with methods, all modifiers are shown. The value of constants is retrieved using the ReflectionClass method getConstants.

Reflecting

The reflection classes make it easy to generate documentation for both internal and user-defined classes. Documentation can be created directly from the class files themselves, so any changes to the class are immediately reflected in the documentation—much easier than separately maintaining both code and documentation. Descriptions of methods and hints about class usage are invaluable not only for the client programmer but also for the class originator, especially when a few months have lapsed between creation of a class and its subsequent use. Class documentation can effortlessly incorporate internal comments as long as you simply pay a little attention to their format during coding.

0 comments: