Generate Documentation with ApiGen

Share this article

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

Frequently Asked Questions about ApiGen Documentation Generation

What is ApiGen and why is it useful?

ApiGen is a powerful tool that generates documentation from PHP source code. It’s useful because it allows developers to create comprehensive, easy-to-understand documentation automatically, saving time and effort. ApiGen uses annotations like @param, @var, @return, etc., in your PHP source code to generate the documentation. This makes it easier for other developers to understand the code, making collaboration and code maintenance more efficient.

How do I install ApiGen?

ApiGen can be installed using Composer, a tool for dependency management in PHP. You can install it by running the command composer require --dev apigen/apigen. After installation, you can run it using the command vendor/bin/apigen.

How do I generate documentation using ApiGen?

To generate documentation using ApiGen, you need to run the command apigen generate. You can specify the source code directory and the destination directory for the generated documentation. For example, apigen generate -s src -d docs.

Can I customize the look of the generated documentation?

Yes, ApiGen allows you to customize the look of the generated documentation using themes. You can specify a theme using the --theme option when running the apigen generate command.

What are the annotations supported by ApiGen?

ApiGen supports a wide range of annotations including @param, @var, @return, @throws, @deprecated, and more. These annotations allow you to provide additional information about your code, which is included in the generated documentation.

Can I exclude certain files or directories from the documentation?

Yes, you can exclude certain files or directories from the documentation using the --exclude option. For example, apigen generate -s src -d docs --exclude tests.

How do I update ApiGen to the latest version?

You can update ApiGen to the latest version by running the command composer update apigen/apigen.

Can I use ApiGen with continuous integration tools?

Yes, ApiGen can be used with continuous integration tools. You can set up your CI tool to run ApiGen and generate documentation as part of your build process.

What are the system requirements for ApiGen?

ApiGen requires PHP 7.1 or later. It also requires the mbstring and tokenizer PHP extensions.

Can I contribute to the development of ApiGen?

Yes, ApiGen is an open-source project and contributions are welcome. You can contribute by reporting issues, submitting pull requests, improving the documentation, and more.

Bruno SkvorcBruno Skvorc
View Author

Bruno is a blockchain developer and technical educator at the Web3 Foundation, the foundation that's building the next generation of the free people's internet. He runs two newsletters you should subscribe to if you're interested in Web3.0: Dot Leap covers ecosystem and tech development of Web3, and NFT Review covers the evolution of the non-fungible token (digital collectibles) ecosystem inside this emerging new web. His current passion project is RMRK.app, the most advanced NFT system in the world, which allows NFTs to own other NFTs, NFTs to react to emotion, NFTs to be governed democratically, and NFTs to be multiple things at once.

Intermediate
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week