Drupal Code Standards: Documentation

This is the fourth post in a series about coding standards. In this post we’ll talk about why good, standardized documentation is crucial to your project, and review Drupal coding standards regarding documentation and comments.

Other posts in this series:

  1. Drupal Code Standards: What Are They?
  2. Drupal Code Standards: How Do We Implement Them?
  3. Drupal Code Standards: Formatting
  4. Drupal Code Standards: Documentation
  5. Drupal Code Standards: The t() function
  6. Drupal Code Standards: Object Oriented Coding & Drupal 8
  7. Drupal Code Standards: Twig in Drupal 8

Why is documentation important?

Documentation tells us what our code does. How it’s set up, what variables it uses, what it returns. It tells us what to expect from our code.

But I know what my code does!

Sure you do, right now, you just wrote it. But how about 2 months or 10 projects from now? What about someone else? The key is to make your code maintainable by everyone. Of course, ideally, you’re writing code that’s easy to comprehend because you did everything in a perfectly logical and straightforward way. But comment it thoroughly, just in case. You might also do something clever to solve a tricky problem, but to the next person, it might look like a mistake. Documentation can clear this up.

But my code is so good, it’s self-documenting!

Of course it is. But even to the best programmer, documentation is still quicker and easier to read than code. Documentation is especially helpful for beginner programmers - say someone who’s just starting with Drupal and PHP is looking through your code to see if it does what they need - if you can spell it out for them, you’ll help them figure out what they need a lot faster. Thanks to Drupal’s documentation standards, we have a few ways to document your code that are designed to be quick and easy for the developer writing the code, and the developers reading and modifying it. That said, try to avoid repetitive inline comments - you don't need to comment on every single line of code, especially if it's very clear what it's doing. Comment on functions, large blocks of code, tricky things, things that may change - think about what might be unclear to the next person to look at it.

Doc Blocks

One of the most important parts of documentation in Drupal is doc blocks - these are specially formatted blocks of information that go both at the top of each PHP file and before each function.

File Doc Blocks

A file doc block goes at the top of a file to give you an overview of what the file contains. These go at the top of every PHP file, and while it is still in the process of being adopted as an official standard, there should be one blank line between the opening tag and the doc block. Here’s an example from the Backup and Migrate module:

<?php

/**
* @file
* Create (manually or scheduled) and restore backups of your Drupal MySQL
* database with an option to exclude table data (e.g. cache_*).
*/

It’s short and simple and explains what to expect in this module file. Each function will be documented separately, so this doesn’t need to be extensive. On the next line after the file tag, we have a description. The @file doc block may also commonly include @author and @version.

/**
* @file
* Provides hook implementations for the chromatic_blog module.
*
* @author Chris Free
*
* @version 1.0
*/

In something like a template file, you’ll see more detail in the @file doc block, because the rest of the file may not have as much documentation. The @file doc block may often spell out available variables.

Here’s an example from the Bartik theme:

/**
* @file
* Bartik's theme implementation to provide an HTML container for comments.
*
* Available variables:
* - $content: The array of content-related elements for the node. Use
* render($content) to print them all, or
* print a subset such as render($content['comment_form']).
* - $classes: String of classes that can be used to style contextually through
* CSS. It can be manipulated through the variable $classes_array from
* preprocess functions. The default value has the following:
* - comment-wrapper: The current template type, i.e., "theming hook".
* - $title_prefix (array): An array containing additional output populated by
* modules, intended to be displayed in front of the main title tag that
* appears in the template.
* - $title_suffix (array): An array containing additional output populated by
* modules, intended to be displayed after the main title tag that appears in
* the template.
*
* The following variables are provided for contextual information.
* - $node: Node object the comments are attached to.
* The constants below the variables show the possible values and should be
* used for comparison.
* - $display_mode
* - COMMENT_MODE_FLAT
* - COMMENT_MODE_THREADED
*
* Other variables:
* - $classes_array: Array of html class attribute values. It is flattened
* into a string within the variable $classes.
*
* @see template_preprocess_comment_wrapper()
*/

Function Doc Blocks

A function doc block goes just before every function in every PHP file. No exceptions. Even if it’s a one-line function, it gets a doc block. Now, coder will let you get away with only putting the one-line summary of your function, this is all that’s technically required. For some functions, this may be appropriate. But if your function has any parameters or return values, you should absolutely be documenting them. Yes, it’s more work upfront, but it’s vital work that saves time in the future.

Here we’ll look at a function from the Backup and Migrate module.

This passes coder, it’s got a one-line summary of the function:

/**
* Restore from a file in the given destination.
*/

But this is better:

/**
* Restore from a file in the given destination.
*
* @param string $destination_id
* The machine-readable path of the backup destination.
* @param object|string $file
* The file object, or its name.
* @param object|array $settings
* A settings object, or array to create a settings object.
*
* @return object|bool
* Returns the file, or FALSE if the restore had any errors.
*/

Does this take a little more time? Absolutely. But I guarantee you will have a better understanding of the code if you write out documentation that’s as detailed as possible. Now we know what is passed to the function, what each variable is, and what the function returns. Good documentation can also aid in debugging - if the documentation doesn’t match what’s happening in the code, you know something is wrong, and you’ve got a starting point for fixing it.

Tags

There are a variety of tags you can use in your doc blocks to indicate what this documentation is about, and they’re expected to go in a certain order, as follows:

Each type of tag should be separated by a blank line. The most-used tags in function doc blocks are probably @param and @return.

Here’s an example of how the Countries module uses some of the other tags:

/**
* Generate a country form.
*
* @ingroup forms
*
* @see countries_admin_form_validate()
* @see countries_admin_form_submit()
*/

Groups and topics are tags that come from Doxygen, on which these standards are based, and it helps to group generated documentation from the API module. From Drupal.org:

@defgroup, @addtogroup, @ingroup, @{, @}: Groups and topics

The @defgroup tag is used to define a "group" (in Doxygen terms), which the API module displays as a Topic page. A @defgroup tag needs to be in its own docblock (not inside a file description docblock, function docblock, class docblock, etc.). A group defined by @defgroup has an identifier (starting with a letter, and composed of numbers, letters, underscores, periods, and hyphens), a title, a summary, and documentation. In addition, individual "items" (files, functions, classes, and other things that are documented with docblocks in the API module) can be designated as being "in" the group. The API module makes a page for each group/topic, and on the page, it lists all of the items that are part of the group.

Here’s an example of @defgroup from Drupal 8 core - groups are used more often when you have a large amount of code, like core does. You’ll also see an example of @code and @endcode used here - this tells the API Module to format that text as code.

/**
* @defgroup php_wrappers PHP wrapper functions
* @{
* Functions that are wrappers or custom implementations of PHP functions.
*
* Certain PHP functions should not be used in Drupal. Instead, Drupal's
* replacement functions should be used.
*
* For example, for improved or more secure UTF8-handling, or RFC-compliant
* handling of URLs in Drupal.
*
* For ease of use and memorizing, all these wrapper functions use the same name
* as the original PHP function, but prefixed with "drupal_". Beware, however,
* that not all wrapper functions support the same arguments as the original
* functions.
*
* You should always use these wrapper functions in your code.
*
* Wrong:
* @code
* $my_substring = substr($original_string, 0, 5);
* @endcode
*
* Correct:
* @code
* $my_substring = Unicode::substr($original_string, 0, 5);
* @endcode
*
* @}
*/

And in another function block, we’ll see:

@ingroup php_wrappers

Which tells us that this function is part of the php_wrappers group. It’s a handy way to organize your code once you get the hang of it!

The @deprecated tag is useful if you don’t want to delete a function - someone could have custom code that relies on it.

/**
* Wrapper for country_load().
*
* @deprecated
* Use country_load($iso2) instead.
*/

Lists

You can create lists in your doc blocks that will be interpreted by the API module as unordered lists. They can also make it easier to read. Start the line with a hyphen to indicate a list item. Here is an example from the CKEditor module:

/**
* Implementation of hook_requirements().
*
* This hook will issue warnings if:
* - The CKEditor source files are not found.
* - The CKEditor source files are out of date.
* - Quick upload and/or the built-in file browser are used and $cookie_domain is not set.
*/

Implements hook_xyz().

If you are implementing a hook - for example, hook_menu, all you have to put in the function doc block is:

/**
* Implements hook_menu().
*/

In fact, if you put more than this, coder will give you a warning such as:

 8 | WARNING | Format should be "* Implements hook_foo().", "* Implements
| | hook_foo_BAR_ID_bar() for xyz_bar()."
,, "* Implements
| | hook_foo_BAR_ID_bar() for xyz-bar.html.twig."
, or "* Implements
| | hook_foo_BAR_ID_bar() for xyz-bar.tpl.php."
.

You also do not need to duplicate the documentation for the function, such as the parameters. If you do, you’ll get a warning such as:

11 | WARNING | Hook implementations should not duplicate @param documentation

If it’s really important that you document something that has changed in your implementation, never skimp on documentation. However, you can always put the documentation inside the function if you want to keep coder happy about the docblock.

API Module

Why are these doc blocks so important, and why do they have to be formatted so exactly? The API Module parses the information in doc blocks into documentation, so it’s especially important to make sure the format is correct. Any documentation that strays from this format will be human-readable, but won’t be properly parsed. The documentation found at https://api.drupal.org/api/drupal is all generated this way.

Inline Comments

Inline comments are important, because this is what you’ll use to explain your code throughout your files. Drupal generally uses the C++-style // notation, though C-style comments (/* */) are allowed, but discouraged within functions. Inline comments shouldn’t follow a statement - this means they must get their own line. It’s usually understood that a comment precedes the line it’s commenting on, but you can always make this clearer by saying something like "The following line does XYZ."

Remember from our previous post on formatting that inline comments must always end in a full stop, and must never be longer than 80 characters. Here’s an example from a contrib module:

Wrong:

  'iso2' => array(
'12', // Numerical
'', // Empty
'$s', // Special char
'au', // Duplicate
'd', // Single property
'ddd', // Too long
),

Right:

  'iso3' => array(
// Numerical.
'123',
// Special char.
'$ss',
// Duplicate.
'aus',
// Single char.
'd',
// Double char.
'dc',
// Too long.
'xaaa',
),

Right, but discouraged:

'numcode' => array(
'1001', /* Too big. */
'-1', /* Too small. */
'-12', /* Too small. */
'-123', /* Too small. */
'$23', /* Special char. */
'23#', /* Special char. */
'048', /* Duplicate. */
'4', /* Duplicate. */
'04', /* Duplicate. */
),

Content Style Guide

Drupal.org has a style guide for content on the site, and it’s encouraged to use this guide for anything you write inside of Drupal code. Much of it is the style of various industry-related terms, along with Drupal specific terms. It’s worth giving it an overview. The clearer our content, the better we can communicate with each other and with new Drupal users!

We’ve covered why documentation is essential, how to write file and function doc blocks for the API module in your Drupal code, and inline comments. As detailed as this may seem, it’s still only an overview - the Drupal.org page has even more detail! But this guide will get your started and give you a reference point. Now go forth and document!

In our next post on coding standards, we’ll take a detailed dive into just one essential Drupal function - the t() function. Learn when and when not to use it in both Drupal 7 and Drupal 8!

Roadmap Your Drupal 7 Transition

We’re offering free 45 minute working sessions to help you assess your organizations level of risk, roadmap your transition plan, and identify viable options!

Drop us a note, and we’ll reach out to schedule a time.