Generate Documentation with ApiGen

Tweet

If you’ve ever worked with someone else’s code, you know the pain (or pleasure) of missing (or existing) documentation. Well documented code is one of the cornerstones of professional software development, and unless you learn how to document your code so that it becomes second nature, you’re doing yourself and others more harm than good.

If this sounds overly discouraging, it’s because it should be. If you’re writing undocumented code, you should stop this very moment. I’m serious. Drop everything, save and quit, and focus on improving this essential part of your workflow.

Over the years, I’ve had to suffer through horrible documentation (hello, Zend Framework!) and have glided through some pretty solid stuff as well (thank you, Swift Mailer!). I try to document every single file I produce, and I’ve been doing it long enough now that it has become second nature. I have a strict documentation standard I follow, one that is compatible with ApiGen, the documentation generator, and one that is highly human-readable but also fully compatible with modern IDEs. This means I can generate my library documentation for the public along with examples, tutorials, descriptions and possible errors with a single command.

ApiGen is a docblock parser like PhpDocumentor. PhpDocumentor has been around for much longer than ApiGen, but unfortunately its development is somewhat stunted and it lacks in terms of modern documentation and examples. It also has some unpleasant dependencies which can cause problems on fresh PHP installations, and often throws errors that aren’t documented anywhere. You can find out more about the PhpDocumentor in a previous SitePoint article. As for which is better… I believe ApiGen to be the logical choice due to the following downsides of PhpDocumentor:

  • requires page-level docblocks (above namespaces) that serve no purpose; they are never properly parsed and the entered descriptions are nowhere to be found. Update, August 24th 2012: a commenter has pointed out that page-level blocks are in fact used in templates other than the default. They are still, however, not essential, and generate errors when PhpDoc is parsing, staying in the documentation as logged errors indefinitely.
  • no search in the generated documentation.
  • layout errors – long package names break the “Api Documentation” drop-down menu, the generated GraphViz charts will have a Z-index higher than the menus and will thus appear above them even when the menus are expanded (see note below)
  • fails to recognize some type hinting for unknown reasons. (this is fixed in alpha8, see note below)
  • no html support in tag descriptions – HTML tags stay visible, so advanced formatting is not possible from what I’ve noticed. (see note below)

Note, Aug. 24th: Most of these issues have been fixed in the last version I tested – alpha 10. However, the lack of a search function is still a deal breaker for me.

Docblocks and Tags

We’ll be using a couple classes I wrote for the purpose of this tutorial spread out across a couple folders under two simple namespaces. These classes won’t be a whole lot of use in the real world, but they’ll serve us well for this article. Since the classes are rather large with all the comments and spacing dictated by the PSR-2 standard, I’ve uploaded the full code to GitHub. There are multiple branches available in the repo; the first branch ‘base-code’ contains nothing but the actual classes, The second branch ‘commented-code’ contains fully commented code, and the third branch ‘documentation-generated’ has a docs directory in which you can find auto-generated documentation.

Observing every class would take too much time and space, so instead let’s focus on just one right now. We’ll take a look part by part at the User class and I’ll explain the docblocks for each snippet:

<?php
/**
 * This block contains a short description of the classes
 * present in the rest of this file.
 *
 * This paragraph contains the long description of the same
 * things.
 *
 * This entire block will be completely ignored by ApiGen and
 * is here only to maintain compatibility with PhpDocumentor.
 * Requiring the presence of this near useless block in every
 * php file is one of PhpDocumentors downsides.
 *
 * @package Some package that only PhpDocumentor sees.
 */
namespace MyLibrary;

use MyLibraryAbstractsSuperType;
use MyLibraryInterfacesUserMapper;
use MyLibraryExceptionsUser as UserException;

/**
 * The User class is a sample class that holds a single
 * user instance.
 *
 * The constructor of the User class takes a Data Mapper which
 * handles the user persistence in the database. In this case,
 * we will provide a fake one.
 *
 * @category  MyLibrary
 * @package   Main
 * @license   http://www.opensource.org/licenses/BSD-3-Clause
 * @example   ../index.php
 * @example <br />
 * 	$oUser = new MyLibraryUser(new MappersUserMapper());<br />
 *  $oUser->setUsername('swader');<br />
 *  $aAllEmails = $oUser->getEmails();<br />
 *  $oUser->addEmail('test@test.com');<br />
 * @version   0.01
 * @since     2012-07-07
 * @author    Bruno Skvorc <bruno@skvorc.me>
 */
class User extends SuperType
{

As what should be common practice for everyone by now, we first define a namespace at the beginning of the class file (ignore the entire docblock before the namespace which is there only for PhpDocumentor compatibility). We then follow it up with some use statements in order to establish aliases for the classes used by the class.

Next, we see a comment block immediately preceding the class declaration. This is a class description and it tells the user/developer what the class does in a short and concise TL;DR manner. The main class description usually consists of two parts – the short description and the long description. The short description is the first paragraph, and the long description is everything from the short description to the first docblock marker (even multiple paragraphs, in-line examples, anything the author thinks of).

What follows then are several markers, each with its own purpose.

@category specifies the category to which the class belongs. Categories are parents to packages (see below) and form large supersets of classes and utilities. Categories are usually as encompassing as namespaces, and as such our @category is actually the name of our namespace.

@package defines a smaller, more precise set of classes and utilities. It’s usually a subdirectory in the main namespace, or in the case of our User class, the “Main” package, which means it’s at the root of our library. There is also the @subpackage marker which can go even deeper. There is usually no need for it (due to namespaces handling all tree structures nicely), but an example would be “@category MyLibrary, @package User @subpackage Exceptions”, which means the class is in the MyLibrary category, and is a User-related Exception inside the User package.

@category and @package are arbitrary, and many developers use them as they see fit. There is no specific standard because properly namespaced code replaces the need for them – namespaces take precedence over these markers. Some developers even like to mark their classes as “@category controller”, “@category view”, and “@category model” in MVC libraries. When namespaces are not used, parsers like ApiGen will look at categories and packages to organize your documentation – but they prefer to ignore them in favor of namespaces.

@license specifies the license that applies to the code, and is usually a link to an online resource. Another useful marker can be the @copyright marker, which tells users if the code is copyrighted. The copyright marker usually contains the year range and the entity which copyrighted the code, be it an individual or a legal entity.

@example is a path to a file containing sample usage of the class this marker is in. In our case, we indicate that sample usage of the User class can be found one directory up, in the index.php file. The @example marker can also provide another in-line example. A class can have as many example tags as it needs.

@version is the version tag and should be changed with every update.

@since marks the date of creation for the current class.

@author specifies the developer who created the class. It can be just a name, or an e-mail address formatted as “Name ”. A docblock can contain multiple authors and all will be listed in the documentation.

Continuing onward, let’s look at the properties of the class right under the class declaration:

/**
 * The user's username. Defaults to contact email.
 * @var string
 */
protected $sUsername;

/**
 * An array of the user's emails. The first element
 * is the main contact email.
 * @var array|null
 */
protected $aEmails = null;

/**
 * A boolean flag on whether or not the user is logged in
 * @var bool
 */
protected $bLoggedIn = false;

/**
 * The mapper is responsible for all data persistence and for
 * fetching existing records from the database when a username
 * is provided.
 * @var UserMapper
 */
protected $oMapper = null;

Property docblocks only need to contain a short description and a @var marker which hints at the value type the property will contain. If there is a chance of multiple types being contained in said property, a pipe separator can be used, meaning “or” (e.g. array|null).

Finally, let’s have a look at the documentation of a method, in this case, the setUsername() method:

/**
 * Sets the username for the given user instance. If the username
 * is already set, it will be overwritten. Throws an invalid
 * argument exception if the provided username is of an invalid
 * format.
 *
 * @param string $sUsername The username string to set
 *
 * @return  User
 * @throws  InvalidArgumentException
 * @todo    Check to make sure the username isn't already taken
 *
 * @since   2012-07-07
 * @author  Bruno Skvorc <bruno@skvorc.me>
 *
 * @edit    2012-07-08<br />
 *          John Doe <john@doe.com><br />
 *          Changed some essential
 *          functionality for the better<br/>
 *          #edit3392
 */
public function setUsername($sUsername)
{
    if (!$this->checkString($sUsername, 5)) {
        throw new InvalidArgumentException(
            "The username needs to be a valid non-empty string of 5 characters or more."
        );
    }

    $this->populate($this->oMapper->findByUnique($sUsername));
    $this->sUsername = $sUsername;

    return $this;
}

Method docblocks always start with the description of the method which should be as detailed as possible in order to avoid any and all problems when other developers use it.

A list of accepted parameters follow the description using one or more @param markers. Each marker should have an expected type (string, bool, int, etc. or a mix of several types separated with the pipe character – array|int|string), the actual parameter name as it appears in the method declaration, and its description.

@return describes the return value of the method. If the method returns no value, the marker should say “@return void”, otherwise, a specific type should be declared (e.g. “@return int”, or in our case “@return User” since the method returns the instance of the very object it was executed on). A description of the data is optional but desired when return values can get complicated (e.g. associative arrays).

The @throws marker lets the developer know which Exceptions to expect in cases of errors. There can be multiple @throws markers, and they can (but usually don’t) have descriptions. Instead, the situation in which an exception is thrown is usually described in the method description.

If a method/class is unfinished and some functionality is still to be added, the @todo marker can be utilized. Not only is it recognized by ApiGen and turned into a task list when the documentation is being generated, it also shows up on the task list in many modern IDEs enabling you to easily track unfinished work. Docblocks can have as many todo markers as they want – they can even be added in-line (in the middle of a method) and will still be recognized by ApiGen and added to the task list.

Using @since and @author at the method-level is not very common since putting the author and date into the class-level docblock is usually sufficient, but knowing which developer added a mysterious method into a class that was originally yours is invaluable in large teams in which multiple developers sometimes work on the same class. This is entirely optional and takes a bit more time and keystrokes, but I’ve found that the benefits far outweigh the effort in large projects.

The @edit tag is not an officially supported tag. It has no meaning or value in the official docblock documentation and is not expected by any documentation generator I know of. I’ve enforced it’s usage simply because it’s indispensable in following code changes through means other than version control. In tandem with Git or SVN, the @edit tag can easily let you know who edited which file, when and why – and the hashtag at the end will be the same hashtag used in the commit message of the edit in the actual version control system, so it can be easily found when needed. Proper version control and teamwork is outside the scope of this article, but you would do well to adopt the @edit marker when working on other people’s code – the clarity it provides when you look back on your edits months down the road can save you countless hours of frustration.

By having this kind of documentation in place, we make sure that every developer who opens our files in the future knows exactly what each class does, and has good autocomplete regardless of which IDE they use. Now let’s turn our documentation into something even prettier to look at!

Installing and using ApiGen

Installing ApiGen is as simple as their website says. To install ApiGen, run the following commands (you might have to “sudo” them):

$ pear config-set auto_discover 1
$ pear install pear.apigen.org/apigen

That’s it, ApiGen is now installed. If this doesn’t work, try following some of the alternative instructions as detailed on their website.

Now it’s time to run ApiGen with some useful flags (everything except source and destination is optional):

$ apigen --source . --destination docs/apigen --todo yes 
 --title "My Library Documentation" --php no --download yes

The --destination flag instructs ApiGen to place the documentation in the docs/apigen directory. The --todo flag generates a task list from any @todo markers it finds. --title sets the title of the documentation project, the --php flag tells ApiGen to ignore core PHP files (like certain exceptions, if used) and the --download flag tells it to generate a downloadable ZIP archive of our source code as well.

Look at the generated documentation by opening index.html in the documentation’s folder, or by visiting the appropriate virtual host URL in your browser. What ApiGen generated there is an impressive set of HTML files which, when used together, form a beautiful static website with your project’s entire structure laid out. You can navigate through namespaces, subfolders, even use a search function with a fast auto-complete in the top right corner and download the entire zipped source. Every property, method and class we defined in the project is now visible in structured tabular and/or tree form on screen – even “todo” comments (click on “todo” at the top of the screen)!

All you have to do in order to expose this documentation to the public is upload it to your server and point a domain or subdomain at it – everything you see in the HTML files will also be visible to the visitors.

ApiGen has a plethora of other useful flags (implementation of Google Analytics, a custom root URL, a custom configuration directive, styling, etc.) but these are outside the scope of this article and might be covered in a later article into greater detail.

In Conclusion

Whether you’re a single developer working on smaller projects, or part of a larger team working on a joint effort, commenting is essential to your workflow. If done properly and with certain project-wide or team-wide standards in mind, it will help you and your colleagues avoid hours of toiling later on when revisiting older code and will allow users of your code to have a much smoother experience getting into it and sticking around. Bad documentation is one of the main reasons new users of complex libraries give up on them – so to avoid turning your users and colleagues away, adopt these best practices today.

Image via Fotolia

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • www.phpdoc.org Mike van Riel

    The given list of downsides appears to be composed of a few bugs and a few misunderstandings maybe?

    >> requires page-level docblocks (above namespaces) that serve no purpose; they are never properly parsed and the entered descriptions are nowhere to be found.

    Page level DocBlocks are not a requirement; they are encouraged. The generated documentation will function just as well with or without them.

    >> layout errors – long package names break the “Api Documentation” drop-down menu,

    This has not been reported in our issue tracker and we didn’t come across. Thanks for mentioning it.
    I have added this to the issue tracker so we can fix it

    >> the generated GraphViz charts will have a Z-index higher than the menus and will thus appear above them even when the menus are expanded.

    Again thanks for mentioning this; I have fixed the issue and it will become available with the next release. Also: ApiGen doesn’t have an extensive Class Diagram.

    >> fails to recognize some type hinting for unknown reasons.

    Can you indicate which these are? According to the latest tests all type hinting should work without an issue.

    >> fails to recognize, parse, and format user-defined markers (such as @edit).

    phpDocumentor has no problem with interpreting a user-defined tag; it will
    be shown in the details section of the element together with the description
    text.

    • http://about.me/bruno.skvorc Bruno Skvorc

      Thanks for the feedback, I had no idea this would actually be read by someone who works on either of those parsers so I’m feeling cautiously optimistic about discussing this!

      Just a heads up first: It seems OSX 10.8. introduced new problems in using GraphViz, and since PhpDoc depends on it, I had to do dance around some problems. The situation was finally resolved by doing “brew install libtool”, which graphviz depends on (I guess?) in 10.8. This might be worth including in the PhpDoc documentation, since it might end up being a roadblock to many users.

      1) Page level docblocks are optional, yes, but seeing a lot of red on screen and a several lines of warnings like “No page-level DocBlock was found in file Mappers/UserMapper.php” for such a small sample project is quite discouraging – it hints at fatal failure even if none occurred. Perhaps include a –no-pageblock option that defaults to yes?

      3) ApiGen indeed does not have a Class Diagram, and this is one of PhpDoc’s major advantages. However, due to the Z index error I found them less than usable. Thanks for fixing this.

      4) “The type hint of the argument is incorrect for the type definition of the @param tag with argument $oMapper in __construct()” is the warning I get. I assume this refers to the User class (it would be great if warnings echoed out the files in which they found the errors, too), but as you can see in the source the type hint for it is valid, at least from my perspective, I’m fully aware that I might be missing something. (Fyi the documentation was generated fine – the warning seems unwarranted)

      5) I don’t know what I did wrong to make @edit not work, I just tried again and it worked fine. I will edit said part of the article. What I meant to say aside from it was that there’s no html support in the tag description text – html tags will remain visible. I’m not sure if there’s a command to force html parsing of description text, I haven’t found it while looking, so if there is I apologize in advance.

      Thank you again for taking the time to read this and provide feedback. If you’d like to get in touch and approach some of the problems in more detail please do so. I’d be glad to give the next PhpDoc release a new try.

      • http://www.phpdoc.org Mike van Riel

        I try to read every blog post and tweet related to phpDocumentor. I value community input quite a lot and base decisions on what people want. It also helps me to discover bugs that might not be reported and do not come up in my own tests; which is cool.

        Concerning GraphViz; the requirement is annoying. I can’t make it any prettier than it is. We still want to move that to a(n optional) plugin so that people do not hit the dependency.

        Concerning the points that you address:

        1. One of the improvements that we want to make is to allow errors to be toggleable for one or more file. That way you could switch off the page-level docblock error for your project and/or switch off all errors for your ‘vendors’ directory so that you could parse your dependencies if you’d want to.

        4) Since version alpha 7 or alpha 8 we now output the name of the file that is processed and all errors are grouped. Appending the filename after the message cause an unreadable lot of lines in the CLI.
        Regarding the error; I will run some tests this evening and contact you by mail. It should not output an error.

        5) regarding the HTML tags; I will take a look at that as well. Escaping is probably still enabled for that specific part of the template.

        • http://about.me/bruno.skvorc Bruno Skvorc

          That’s fantastic, thanks. It’s very refreshing to know the software’s author pays attention to the community this much.

  • boen_robot

    About the part
    “requires page-level docblocks (above namespaces) that serve no purpose; they are never properly parsed and the entered descriptions are nowhere to be found.”

    File descriptions actually do serve a purpose – they are shown under a “Files” menu that’s present in pretty much all templates… admittedly other than the default one.

    • http://about.me/bruno.skvorc Bruno Skvorc

      Didn’t know this, never changed PhpDoc templates, thanks for the heads up!

  • Kris

    There’s an error in your install directions. You have:
    pear install pear.apigen.org/api
    it should be:
    pear install pear.apigen.org/apigen

    • http://zaemis.blogspot.com Timothy Boronczyk

      Thanks for the catch! I’ve fixed it in the article.

    • http://about.me/bruno.skvorc Bruno Skvorc

      Thank you, not sure how I missed that!

  • Attila Fulop

    At first run after install I had `ERROR: the server encountered an internal error and was unable to complete your request.` error message. Neither –debug did help. I had to set date.timezone in php.ini as described here: https://github.com/apigen/apigen/issues/75
    I hope this might help others as well.

    • http://about.me/bruno.skvorc Bruno Skvorc

      Thanks for the heads up!