Documentation with phpDocumentor: Part 2

0
160
9 min read

Documentation without DocBlocks

You have probably already noticed that short of some inline comments, the sample project has no DocBlocks, tags, or anything else added by the programmer for the purpose of documenting the code. Nevertheless, there is quite a bit that phpDocumentor can do with uncommented PHP code. If we are in the directory containing the project directory, we can run phpDocumentor and ask to generate documentation for the project like this:

The above command will recursively process all files in the project directory (–directory ./project/), create documentation with a custom title (–title ‘Generated Documentation – No DocBlocks’), include a source code listing of each file processed (–sourcecode on), save all documentation to the docs directory (–target ./project/docs), and group everything under a specified package name (–defaultpackagename ‘UserAuthentication’).

Listing all documentation pages that phpDocumentor generated is impractical, but let’s take a look at the outline and at least one of the classes. All we have to do to view the documentation is to open the index.html file in the docs directory where we told phpDocumentor to direct the output with a web browser.

Looking at the above screenshot, we see that phpDocumentor correctly found all the class files. Moreover, it identified Accountable as an interface and found index.php, even though it contains no class definitions. All classes and interfaces are grouped together under the AuthenticationUser package name that was specified from the command line. At the same time, we see some of the shortcomings. There is no further classification or grouping and all components are simply listed under the root level.

Before we move on, let’s also take a look at what information phpDocumentor was able to extract from the Users.php file:

It correctly identified the methods of the class, their visibility, and which parameters are required. I think that is a pretty useful start, albeit the description is a bit sparse and we have no idea what methods were actually implemented using the magic __call() method.

Another point to note here is that the class property $accounts does not appear in the documentation at all. That is intended behavior because the property has been declared private. If you want elements with private visibility to appear in your documentation, you will have to add the –pp / –parse private command line option or put this option in a config file.

Documentation with DocBlocks

Of course, this example wouldn’t be complete if we didn’t proceed to add proper DocBlocks to our code. The following is the exact same code as before, but this time it has been properly marked up with DocBlocks.

File project/classes/Accountable.php:
<?php
/**
* @author Dirk Merkel <dirk@waferthin.com>
* @package WebServices
* @subpackage Authentication
* @copyright Waferthin Web Works LLC
* @license http://www.gnu.org/copyleft/gpl.html Freely available
under GPL
*/
/**
* <i>Accountable</i> interface for authentication
*
* Any class that handles user authentication <b>must</b>
* implement this interface. It makes it almost
* trivial to check whether a user is currently
* logged in or not.
*
* @package WebServices
* @subpackage Authentication
* @author Dirk Merkel <dirk@waferthin.com>
* @version 0.2
* @since r12
*/
interface Accountable
{
const AUTHENTICATION_ERR_MSG = 'There is no user account
associated with the current session. Try logging in fist.';
/**
* Did the current user log in?
* * This method simply answers the question
* "Did the current user log in?"
*
* @access public
* @return bool
*/
public function isLoggedIn();
/**
* Returns user account info
*
* This method is used to retrieve the account corresponding
* to a given login. <b>Note:</b> it is not required that
* the user be currently logged in.
*
* @access public
* @param string $user user name of the account
* @return Account
*/
public function getAccount($user = '');
}
?>
File project/classes/Authentication.php:
<?php
/**
* @author Dirk Merkel <dirk@waferthin.com>
* @package WebServices
* @subpackage Authentication
* @copyright Waferthin Web Works LLC
* @license http://www.gnu.org/copyleft/gpl.html Freely available
under GPL
*/
/**
* <i>Authentication</i> handles user account info and login actions
*
* This is an abstract class that serves as a blueprint
* for classes implementing authentication using
* different account validation schemes.
*
* @see Authentication_HardcodedAccounts
* @author Dirk Merkel <dirk@waferthin.com>
* @package WebServices
* @subpackage Authentication
* @version 0.5
* @since r5
*/
abstract class Authentication implements Accountable
{
/**
* Reference to Account object of currently
* logged in user.
*
* @access private
* @var Account
*/
private $account = null;
/**
* Returns account object if valid.
*
* @see Accountable::getAccount()
* @access public
* @param string $user user account login
* @return Account user account
*/
public function getAccount($user = '')
{
if ($this->account !== null) {
return $this->account;
} else {
return AUTHENTICATION_ERR_MSG;
}
}

/**
* isLoggedIn method
*
* Says whether the current user has provided
* valid login credentials.
*
* @see Accountable::isLoggedIn()
* @access public
* @return boolean
*/
public function isLoggedIn()
{
return ($this->account !== null);
}

/**
* login method
*
* Abstract method that must be implemented when
* sub-classing this class.
*
* @access public
* @return boolean
*/
abstract public function login($user, $password);
}
?>
File project/classes/Authentication/HardcodedAccounts.php:
<?php
/**
* @author Dirk Merkel <dirk@waferthin.com>
* @package WebServices
* @subpackage Authentication
* @copyright Waferthin Web Works LLC
* @license http://www.gnu.org/copyleft/gpl.html Freely available
under GPL
*/
/**
* <i>Authentication_HardcodedAccounts</i> class
* * This class implements the login method needed to handle
* actual user authentication. It extends <i>Authentication</i>
* and implements the <i>Accountable</i> interface.
*
* @package WebServices
* @subpackage Authentication
* @see Authentication
* @author Dirk Merkel <dirk@waferthin.com>
* @version 0.6
* @since r14
*/
class Authentication_HardcodedAccounts extends Authentication
{
/**
* Referece to <i>Users</i> object
* @access private
* @var Users
*/
private $users;
/**
* Authentication_HardcodedAccounts constructor
*
* Instantiates a new {@link Users} object and stores a reference
* in the {@link users} property.
*
* @see Users
* @access public
* @return void
*/
public function __construct()
{
$this->users = new Users();
}

/**
* login method
*
* Uses the reference {@link Users} class to handle
* user validation.
*
* @see Users
* @todo Decide which validate method to user instead of both
* @access public
* @param string $user account user name
* @param string $password account password
* @return boolean
*/
public function login($user, $password)
{
if (empty($user) || empty($password)) {
return false;
} else {
// both validation methods should work ...
// user static method to validate account
$firstValidation = Users::validate($user, $password);
// use magic method validate<username>($password)
$userLoginFunction = 'validate' . $user;
$secondValidation = $this->users-
>$userLoginFunction($password);
return ($firstValidation && $secondValidation);
}
}
}
?>
File project/classes/Users.php:
<?php
/**
* @author Dirk Merkel <dirk@waferthin.com>
* @package WebServices
* @subpackage Accounts
* @copyright Waferthin Web Works LLC
* @license http://www.gnu.org/copyleft/gpl.html Freely available
under GPL
*/
/**
* <i>Users</i> class
*
* This class contains a hard-coded list of user accounts
* and the corresponding passwords. This is merely a development
* stub and should be implemented with some sort of permanent
* storage and security.
*
* @package WebServices
* @subpackage Accounts
* @see Authentication
* @see Authentication_HardcodedAccounts
* @author Dirk Merkel <dirk@waferthin.com>
* @version 0.6
* @since r15
*/
class Users
{
/**
* hard-coded user accounts
*
* @access private
* @static
* @var array $accounts user name => password mapping
*/
private static $accounts = array('dirk' => 'myPass',
'albert' => 'einstein');

/**
* static validate method
*
* Given a user name and password, this method decides
* whether the user has a valid account and whether
* he/she supplied the correct password.
*
* @see Authentication_HardcodedAccounts::login()
* @access public
* @static
* @param string $user account user name
* @param string $password account password
* @return boolean
*/
public static function validate($user, $password)
{
return self::$accounts[$user] == $password;
}

/**
* magic __call method
*
* This method only implements a magic validate method
* where the second part of the method name is the user's
* account name.
*
* @see Authentication_HardcodedAccounts::login()
* @see validate()
* @access public
* @method boolean validate<user>() validate<user>(string
$password) validate a user
* @staticvar array $accounts used to validate users & passwords
*/
public function __call($name, $arguments)
{
if (preg_match("/^validate(.*)$/", $name, $matches) &&
count($arguments) > 0) {
return self::validate($matches[1], $arguments[0]);
}
}
}
?>
File project/index.php:
<?php
/**
* Bootstrap file
*
* This is the form handler for the login application.
* It expects a user name and password via _POST. If
*
* @author Dirk Merkel <dirk@waferthin.com>
* @package WebServices
* @copyright Waferthin Web Works LLC
* @license http://www.gnu.org/copyleft/gpl.html Freely available
under GPL
* @version 0.7
* @since r2
*/
/**
* required class files and interfaces
*/
require_once('classes/Accountable.php');
require_once('classes/Authentication.php');
require_once('classes/Users.php');
require_once('classes/Authentication/HardcodedAccounts.php');
$authenticator = new Authentication_HardcodedAccounts();
// uncomment for testing
$_POST['user'] = 'dirk';
$_POST['password'] = 'myPass';
if (isset($_POST['user']) && isset($_POST['password'])) {
$loginSucceeded = $authenticator->login($_POST['user'],
$_POST['password']);

if ($loginSucceeded === true) {
echo "Congrats - you're in!n";
} else {
echo "Uh-uh - try again!n";
}
}
?>

Since none of the functionality of the code has changed, we can skip that discussion here. What has changed, however, is that we have added DocBlocks for each file, class, interface, method, and property. Whereas the version of the project without documentation had a total of 113 lines of code, the new version including DocBlocks has 327 lines. The number of lines almost tripled! But don’t be intimidated. Creating DocBlocks doesn’t take nearly as much time as coding. Once you are used to the syntax, it becomes second nature. My estimate is that documenting takes about 10 to 20 percent of the time it takes to code. Moreover, there are tools to really speed things up and help you with the syntax, such as a properly configured code editor or IDE.

Now let’s see how phpDocumentor fared with the revised version of the project. Here is the index page:

This time, the heading shows that we are looking at the Web Services package. Furthermore, the classes and interfaces have been grouped by sub-packages in the left-hand index column. Next, here is the documentation page for the Users class:

As you can see, this documentation page is quite a bit more informative than the earlier version. For starters, it has a description of what the class does. Similarly, both methods have a description. All the tags and their content are listed and there are helpful links to other parts of the documentation. And, from the method tag we can actually tell that the magic method __call() was used to implement a method of the form validate<user>($password). That is quite an improvement, I would say!

To really appreciate how much more informative and practical the documentation has become by adding DocBlocks, you really need to run through this example yourself and browse through the resulting documentation.

LEAVE A REPLY

Please enter your comment!
Please enter your name here