Building Good DocumentationWriting documentation for your code is one of the most important things you can do before, during, and after actually writing the code itself. Even when I am building small sites for myself, I am bound to build a couple classes and functions that I go back and modify later or re-use elsewhere. Re-use is a good thing, but it is much easier when I can look at the documentation and use it to remember why I wrote something and what it did.
When you are working in a corporate setting, building documentation inside your code is even more important. It is one thing to be maintaining a small set of code that you wrote for yourself or for a friend, but it is quite another thing to have to fix code that someone else wrote. As a PHP developer who could be passing your code off to someone else to maintain, you should try to have the professional courtesy of making your code as easy to fix and understand as possible.
This is where a very useful tool such as phpDocumentor comes in. The phpDocumentor project can be installed with The PHP Extension and Application Repository (PEAR) and allows you to use either the command line or even a web front-end to turn tags inside your PHP code into meaningful documentation.
The tags that phpDocumentor uses are identifiers used inside your comments, which are parsed by phpDocumentor and formatted into various output formats, including HTML, PDF, Docbook XML, or even Windows help files (the
.chm extension, sometimes called “chum” files). The standard tags used by phpDocumentor for PHP 5 are listed in Table 1.
Depending on what you are using PHP 5 for, you might not use every single one of these standard tags. Even if many do not seem to apply to you, at least mark your files with the @author, @version, and @copyright tags. Ensure you add, at the very least, a short description and also identify which versions of PHP supports the code in your file.
Table 1: Standard phpDocumentor Tags
At the top of your PHP file, the comment block should look like the code in Listing 1.
At first glance, the tags appear to be inside a normal comment block. That is true, except the comment block starts out with /** instead of /*. If you forget the extra asterisk (*) and put the tags inside a normal comment block, phpDocumentor will skip right over your documentation. Have a look at Figure 1 to see what the documentation looks like after running phpDocumentor on Listing 1.
Now let us look at an example of documentation for a couple functions and variables. Listing 2 shows an example of documenting a public class-level variable:
The @var tag describes the type of the variable, and the @access tag describes the access of the variable, which in this case is public. Listing 3 demonstrates an example of documentation for a function.
Listing 1
<?php
/**
*This is a short description of the file.
*
*This is a much longer description of the
*file that is optional and provides a lot more
*detail about the class and methods.
*
*PHP Versions 4 and 5
*
*@author Author 1 <author1@example.com>
*@copyright 2006-2007 Author 1
*@version CVS: $Id$
*@link http://www.nathanagood.com/listing1.php
*/
class PolledHereford
{
/*TODO: Put class implentation here. */
}
?>
Listing 2
/* Snipped... Look at Listing 1 */
class PolledHereford
{
/**
*This is how a PolledHereford says, “hi!”
*@var string Hello message
*@access public
*/
var $helloMessage = “moo”;
}
Listing 3
/**
*This function returns the message the
*PolledHereford has for greeting new
*people. @return string Message that
*says hello.
*/
function getHelloMessage()
{
return $this->helloMessage;
}
After the
@return tag comes the data type (in this case string), then a short description about what is being returned from the function.
Running phpDocumentor on Your FilesAfter adding all the tags and other documentation into your PHP code, the next step is to generate the documentation by using phpDocumentor. If you do not already have phpDocumentor installed, you can do so either using PEAR or by going to
http://www.phpdoc.org site and following the links to download and install phpDocumentor. I will show how to install it using PEAR because it is very straightforward: (If you are using PHP 5, you already have PEAR installed on your machine.)
pear config-set data_dir /path/to/wwwroot/pear
pear install PhpDocumentorThat is it! The first line sets up an option in PEAR that will be used by phpDocumentor for the directory where the web front-end is installed. The second line tells PEAR to install the latest stable version of phpDocumentor. You can use either the command-line or the web interface to phpDocumentor. (I use the command line, but that is because I am a command line junkie and because the command line call can be put into scripts or Makefiles.)
After installing phpDocumentor, the command-line executable phpdoc is installed into PHP’s
bin directory, or in the same directory as the PHP and PEAR commands. To run it on one of your files, type:
phpdoc -f myfile.php -t /my/output/directorywhere
myfile.php is the name of the PHP file that contains the documentation you want to parse and
/my/output/directory is the name of the output directory. To view the documentation right away in a browser, place this directory under the web server’s root directory—just ensure you have access to write to that directory. (The web server does not need permission to write to the directory. Also, consult with your administrator if you are doing this on a server).
Although phpDocumentor generates usable documentation, it does not document your code for you. If you are working by yourself, begin to take the time to add some documentation to your code. If you are on a team of developers doing PHP development, suggest that you use code reviews to catch functions and classes that are undocumented and consider making the phpDocumentor process part of your build or promotion process.
Testing Your (Well Documented) CodeAside from documenting your code well, another best practice for increasing the quality of your code is to write automated unit tests that verify your code’s functionality. Automated unit tests can be used to test single methods in your classes to ensure your functions are returning what they are supposed to return. One of their biggest advantages is in regression testing. That is, once a class passes all the tests made for it, make sure to run the test each time you change anything in the class. Even changes that seem innocuous at first may break something that your unit tests will find, if the tests are written appropriately.
Allowing a PHP developer to write and run these unit tests is the purpose of the PhpUnit framework, which is similar to the JUnit framework available for Java. The PhpUnit framework has classes that can be extended for writing test classes, and the base classes have methods that provide easy ways to verify the output of functions you have written.
Listing 4
<?php
// Call PolledHerefordTest::main() if this source file is
executed directly.
if (!defined(“PHPUnit2_MAIN_METHOD”)) {
define(“PHPUnit2_MAIN_METHOD”,
“PolledHerefordTest::main”);
}
require_once “PHPUnit2/Framework/TestCase.php”;
require_once “PHPUnit2/Framework/TestSuite.php”;
// You may remove the following line when all tests have been
implemented.
require_once “PHPUnit2/Framework/IncompleteTestError.
php”;
require_once “listing3.php”;
/**
*Test class for PolledHereford.
*Generated by PHPUnit2_Util_Skeleton on 2006-04-09 at
18:03:20.
*/
class PolledHerefordTest extends PHPUnit2_Framework_
TestCase {
/**
*Runs the test methods of this class.
*
*@access public
*@static
*/
public static function main() {
require_once “PHPUnit2/TextUI/TestRunner.php”;
$suite = new PHPUnit2_Framework_TestSuite(“Polled
HerefordTest”);
$result = PHPUnit2_TextUI_TestRunner::run($suite);
}
/**
*Sets up the fixture, for example, open a network
connection.
*This method is called before a test is executed.
*
*@access protected
*/
protected function setUp() {
}
/**
*Tears down the fixture, for example, close a network
connection.
*This method is called after a test is executed.
*
*@access protected
*/
protected function tearDown() {
}
/**
*@todo Implement testGetHelloMessage().
*/
public function testGetHelloMessage() {
// Remove the following line when you implement this
test.
throw new PHPUnit2_Framework_
IncompleteTestError;
}
}
// Call PolledHerefordTest::main() if this source file is
executed directly.
if (PHPUnit2_MAIN_METHOD == “PolledHerefordTest::
main”) {
PolledHerefordTest::main();
}
?>
Installing PhpUnitLike phpDocumentor, PhpUnit can be installed as a PEAR package by using the pear command. The PhpUnit2 package targets PHP 5 and requires two other packages to be installed first—PEAR:: Log and PEAR::Benchmark. Use the --alldeps option with PEAR to automatically download and install these required packages while installing PhpUnit: pear install --alldeps PhpUnit2
Once PhpUnit is installed you can run tests from either the command line using the
phpunit command (like phpdoc it should be in PHP’s
bin directory) or by loading the test page in a browser.
One of the easiest ways to begin writing tests with PhpUnit is to use the --
skeleton option with PhpUnit, passing it the name of the class for which you want to generate tests. The
phpunit command will parse the file and generate test stubs for you. Generating the test stubs requires the command:
phpunit --skeleton <class> <filename>You can either substitute
<filename> with the name of the file that contains
<class>, or if you do not provide the filename
phpunit will append
.php onto the name of the class (for example,
MyClass is assumed to be in
MyClass.php). The skeleton that is generated for the class shown in Listings 1-3 is shown in Listing 4.
The test methods that are automatically created with the –
skeleton option include an exception that indicates the functionality inside the test has not been added,
PHPUnit2_Framework_IncompleteTestError. Since the test functions throw an exception, if you run the test using PhpUnit they will be marked as incomplete. The output of running PhpUnit (command:
phpunit PolledHerefordTest. php) on this generated class yields the following result:
PHPUnit 2.3.5 by Sebastian Bergmann.
I
Time: 0.002592
There was 1 incomplete test case:
1) testGetHelloMessage(PolledHerefordTest)
OK, but incomplete test cases!!!So far, this is a very good start. The unit testing class has been generated and it is successfully being run with
phpunit on the command line, and now all we need to do is add the actual code inside the
testGetHelloMessage() function to test the output of the
getHelloMessage() function in the PolledHereford class shown in Listings 1-3. The new
testGetHelloMessage() function is shown in Listing 5.
With the implementation code inside the
testGetHelloMessage() function, PhpUnit will output different results. If you run PhpUnit again, you will see the following output:
PHPUnit 2.3.5 by Sebastian Bergmann.
.
Time: 0.002240
OK (1 test)Listing 5
/**
*Tests the result of calling getHelloMessage().
*/
public function testGetHelloMessage() {
/*Instantiate a new copy of the class. This
*could be done in the setUp if there were many
*tests that could share the same object.
*/
$hereford = new PolledHereford();
/*Call assertEquals, which will compare the
*expected result (the first parameter) with
*the actual result (the second paramter).
*/
$this->assertEquals(‘moo’,
$hereford->getHelloMessage(),
‘Expected getHelloMessage() to equal \’moo\’’);
}
But how do we know the output is actually testing what we think it is testing? The easiest way (I learn best by trial and error, I guess) is to temporarily modify the assertEquals function to test a bad value and run PhpUnit again to watch what happens. In the assertEquals function change ‘moo’ to ‘oink’ and re-run the unit tests. The new output will look like this:
PHPUnit 2.3.5 by Sebastian Bergmann.
F
Time: 0.002831
There was 1 failure:
1) testGetHelloMessage(PolledHerefordTest)
Expected getHelloMessage() to equal ‘moo’ expected: but was:
/Users/ngood/Sites/phpnet_05-2006/PolledHerefordTest.php:68
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0, Incomplete Tests: 0.Not only did PhpUnit give the function an F grade, but there is some extra output that details how many tests where run, how many failed, and so on.
The file created by the --
skeleton option can be loaded directly in a browser, although it is slightly difficult to read in the browser than in the command line output.
Functions to Help You Write TestsTable 2 includes a list of the functions available when writing tests. They help you test a variety of conditions, whether it is to simply see if a value is true to checking to see if a regular expression finds a match in a string.
Table 2: Functions for Writing Tests
What Do You Test?ThIt may seem difficult or flat-out unrealistic in your time frame to test every single method—and many developers across many languages will let the simple set and get functions go untested as they sacrifice to buy time in “real” development. This is a mistake—it is sacrificing the integrity of the code for a short-term payoff of delivering “working” code more quickly. A bug can just as easily be introduced by a bad copy-and-paste error where a set function is not setting the variable it should be setting. This bug could be hard to track down, and could have been caught with proper testing. In the end it is up to you, but seldom are the phrases “a stitch in time saves nine” more true than in unit and regression testing.
Following Coding ConventionsEvery language seems to have its own twist on naming conventions, and PHP is no exception. That being said, PHP conventions do not differ substantially from those of other languages. Along with the proper case of your variables, ensure to format the placement of curly braces correctly and space conditional statements so they are
easier to read.
Using the Proper CaseUsing the proper case in the names of variables, functions, and classes helps make each part easily identifiable. The proper case makes the difference between being able to readily identify a constant versus having to dig for the answers.
There are three different casing schemes to be concerned about when doing PHP development: camel case, Pascal case, and all uppercase. Camel case refers to using capital letters at the beginning of words that are joined together, but the first character in the name is lowercase. Examples of camel case are
myVariable, fooBar, helloWorld, and
getTheeToANunnery. Pascal case names look just like camel case names, except the first letter is uppercase instead of lowercase. Examples of Pascal case are:
MyClassName, YourClassName, OurClassName. Table 3 provides a quick-reference for conventions referring to case of various identifiers
Class names that are in hierarchies, like
My/Package/Class should have underscores separating the levels of the hierarchies; for example,
My_Package_Class—the PhpUnit class name
PHPUnit2_TextUI_TestRunner—is a good example of this convention.
Placing Your BracesThere are many types of bracing and indenting styles, but two common ones are K&R (named after Kernigan and Ritchie of C programming language fame) and BSD/Allman (the latter named after Eric Allman of sendmail fame) styles. PHP is interesting because the PEAR site advocates using both styles, but for different reasons. Classes and functions use the BSD/ Allman style, which places the curly brace of the line after the
declaration, like this:
class AbcDef
{
function xyz($var)
{
/* Something here... */
}
}Control statements, like if conditions or while loops or switch statements on the other hand comply to the K&R method of formatting curly braces and indenting:
if ($something == $somethingElse) {
/* Some code here */
} else if ($something == $somethingDifferent) {
/* More code here */
}
while ($something > $somethingElse) {
/* Code here... */
}
switch ($something) {
case 1:
/* Code... */
break;
case 2:
/* More code... */
break;
default:
/* Default code... */
break;
}There are more conventions documented in the PEAR manual.
Applying What You Have LearnedBefore you go changing every line of code that you have written in the last year (perhaps I am being presumptuous—no doubt you already follow the practices in this article), consider starting with the application of documentation. After getting your code up to snuff with phpDocumentor, start writing unit tests and running them with PhpUnit. After you have a full array of unit tests, you can begin to refactor your code—if necessary—and use the unit tests to ensure any changes you make are not breaking changes.
The rewards of introducing documentation, testing, and adherence to standards in your code won’t be immediately apparent to end users. However, as quality in your code increases your users will no doubt be appreciative.