Introduction to module development
Whole books are written about Drupal module development. This chapter is just a brief introduction to a very complex subject, to get you started.
Table of contents
- Introduction
- Guidelines for module documentation
- Runtime error reporting
- QA tools
- Naming a module
- A very simple custom module
- Interacting with the database
- Variables
- Debugging
- Administrator's interface
- Final word
- Style, structure, and guidelines
- Online documentation structure
- Module documentation guidelines.
- README Template.
- Content style guide
- Coding standards
- Drupal.org style and content guidelines
- Writing module .info files
Introduction
One of the most flexible aspects of Drupal is that the framework allows for custom module development.
Guidelines for module documentation
Below are pointers to the major sources for writing modules and documentation:
Runtime error reporting
While developing, some run-time errors may produce a blank page. This is known as the White Screen Of Death or WSOD. You may also experience that some task, such as installation of a custom module, has no effect.
When this happens, you want to see all error messages and warnings
to see what led up to the failure to produce output. You'll get more
runtime error reporting by adding this to the start of
settings.php
on your staging site (just below
<?php
):
error_reporting(E_ALL); ini_set('display_errors', TRUE); ini_set('display_startup_errors', TRUE);
In addition, navigate to:
$conf['error_level'] = 2;
.
For a production site, you want to set this to “None”.
The Database logging module was previously called Watchdog in Drupal 4 and Drupal 5.
There is more about debugging run-time errors on the Drupal.org site. See, for instance, these notes posted on the developer's wiki: Show all errors while developing, Fixing white screens step by step and Blank pages or WSOD. In addition to the WSOD, internal server errors (status code 500) may pose a challenge when debugging.
This pair of CLI commands may also be handy when debugging. The first shows the latest entries from the database log. The second truncates the log.
$ drush ws $ drush wd-del all -y
QA tools
A Drupal project is expected to meet certain standards. There are a couple of automatic tools that can help with this:
- PAReview.sh (install locally)
- PAReview (online version)
- Coder
PAReview.sh makes use of Composer to install all its dependencies. Make sure you have git, composer, npm and pip installed. Download the module and extract the package to a location outside of any siteroot. Install the dependencies using composer:
$ composer install
To update an existing installation:
$ composer update
To see the usage information for pareview.sh do the following:
$ ./pareview.sh --help Usage: ./pareview.sh GIT-URL [BRANCH] ./pareview.sh DIR-PATH Examples: ./pareview.sh http://git.drupal.org/project/rules.git ./pareview.sh http://git.drupal.org/project/rules.git 8.x-1.x ./pareview.sh sites/all/modules/rules
To see the version installed:
$ fgrep version pareviewsh.info version = "7.x-1.10"
The online version of PAReview will review the default branch in the git repository you supply the URL to (and gripe about it), unless you specify a branch. Here is an example of how to specify the url with a specific branch name:
http://git.drupal.org/sandbox/gisleh/1834468.git 1834498-bugfix
The syntax of the strings passed to the translation function
t()
is checked by the Drupal
translation server,
and reported on the translation releases page for the
project. To get to this page, click on the link
in the right
sidebar on the project's home page, and then on the
button. Clicking on
the number in the column show
the warnings.
Naming a module
Names are important in Drupal. A lot of what goes on under the hood of Drupal depends on naming conventions. One of these naming conventions is that in addition to a human readable name every module must have an unique machine name. The human readable name is a proper noun, so it should be capitalized. The machine name can be anything (as long as it unique), but the standard procedure is it to derive from the module's human readable name by removing the spaces and any special characters, if necessary abbreviating some parts of the human readable name, and converting the resulting string to lower case.
The machine name will be used for machine identification. It will be used to name the folder that contains the module, the files in the module, and as part of the names of the functions and variables that make up the module.
You should exercise some care when picking the module's machine name. In particular, make sure the machine name is not part of any other module's machine name, or vice versa. If there already exists a module named “foobar”, you should not use “foo”, nor “foobarbaz”, as the machine name of your module.
A very simple custom module
To get started, let us first construct a very simple module. This is a module that is equivalent to the “Hello, World!” example that you'll find in the introductory chapter of almost any programming language text-book. But it is still a complete custom module that you may install and enable on any Drupal 7 website,
The module I am about to create solves a rather special problem. In Drupal, if you use the standard role based node access system to block access for the anonymous user to forums, the forum landing page is still visible for the anonymous user role (when landing there, he or she will see a message about access being blocked, and that it will be necessary to log in to access forums). For a cleaner user interface, I shall create a custom module that blocks the anonymous user from accessing the forum landing page.
This custom module is going to be given the human readable name “No Forum for Anonymous”. For its machine name we shall use: “noforumforanon”.
To create this module, start by creating a folder for the module. It should be named the same as the machine name, i.e. noforumforanon.
The minimum Drupal module consists of just two files: one information file to identify the module and another containing the PHP code that scripts the instructions for what the module should do.
The information file is
should be named with the module machine name followed by
.info
, while the code files is named with the module
machine name followed by .module
.
The next step is to create the information file for the module.
This file is only three lines of text with keys that sets the human
readable name, a brief description, and what version of the Drupal
core the module is compatible with. It should be
named noforumforanon.info
, and may look like this:
name = No Forum for Anonymous description = Blocks anonymous from accessing the forum landing page. core = 7.x
There
may be additional keys in the information file.
The project
key is used when checking the module's update
status in the Drupal.org repository. This will produce an error if
the module is not in the repository. It is recommended to
omit this key from custom projects.
All that remains is to create the PHP code that implements the
module. This example is rather typical of a Drupal module. It
implements a callback function for one of Dupal's hooks
(hook_menu_alter), and an internal function that
contains the business logic of the module. The file with
the module's code
should be named noforumforanon.module
.
It may contain the following:
<?php /** * @file * Blocks anonymous user from accessing the forum landing page. */ /** * Implements hook_menu_alter. * Check access for forum menu item. */ function noforumforanon_menu_alter(&$items) { $items['forum']['access callback'] = '_noforumforanon_accesscheck'; } /** * Callback to disallow access for anonymous users. */ function _noforumforanon_accesscheck(){ global $user; return $user->uid; }
The function noforumforanon_menu_alter is an implementation of hook_menu_alter. The naming convention in Drupal requires the implementation of the hook within a module to replace the string “hook” with the machine name of the module – here “noforumforanon”.
What this hook does is to alter the access callback in the forum menu object. Instead of the default (do nothing), there is a callback to the function named _noforumforanon_accesscheck, which is also supplied by the module. This callback function exploits the fact that the anonymous user always will have user ID zero. So if the user has a non-zero user ID, this function returns true (i.e. access to the forum menu object is granted), otherwise, it returns false (i.e. no access).
The name of the callback function _noforumforanon_accesscheck starts with an underscore. This is a Drupal coding standard convention used to indicate that the intent is for this function to be private. This means it should only be called by the module that declared it. Note that there is nothing that enforces this. The functions whose names start with an underscore will have global visibility.
You
should notice that the file starts with the <?php
processing instruction, but it does not end with the corresponding
?>
. This is not a typographic error. Drupal
coding standards require files that are completely PHP to start with
<?php
and omit the closing ?>
. This is
done for several reasons, most notably to prevent the inclusion of
whitespace from breaking HTTP headers.
After a module is created, installing its directory below the site's module directory will immedately expose it in Drupal's administrator GUI. Navigate to
in the administratice toolbar to enable it.For a more extensive example of how to create a custom module, see the following series of blog posts from Andy Pangus: Simple Content Type. Permission and Access. Adding Roles, and Adding Roles to Users Dynamically.
While the .info
and .module
files are the
only files required, to adhere to best practices, you should also
create a README
-file for the module.
Interacting with the database
[TBA]
Variables
HTTP is stateless, which means that there is no default mechanism to transfer information from one web page to another. To allow pages to share information, there are two mechanisms available: Drupal variables, and session variables.
Drupal variables
Drupal variables (aka. database variables, persistent variables, or just variables), are variables that are persistent and global. They are shared by all pages and sessions.
These variables are set when Drupal is boostrapping a page. They
may be read from the database ({variable}
table), or from
the site's settings.php
file. The values in
settings.php
take precedence.
The following three functions below are used to interact with Drupal variables:
variable_set($name, $value) variable_get($name, $default = NULL) variable_del($name)
Function variable_set set the value of the variable to the
value passed in the second argument. A variables name must start with
the machine readable name of the module that owns it, followed by an
underscore. If a module is named mymodule
, a variable
that holds and integer setting some sort of timeout value could be
named mymodule_timeout
. The follwing function call sets
the value of this variable to 3600.
variable_set('mymodule_timeout', 3600);
Function variable_get returns the current value of a variable. The second parameter let you pass a default value that will be returned if the variable has never been set.
$ret0 = variable_get('mymodule_timeout', 900); variable_set('mymodule_timeout', 3600); $ret1 = variable_get('mymodule_timeout', 1800); variable_del('mymodule_timeout'); $ret2 = variable_get('mymodule_timeout', 1800); echo "$ret0 $ret1 $ret2";
The output of the above code fragment would be: “900 3600 1800
”.
If your module creates variables, it needs to remove those from the database when the module is uninstalled. Below is an example showing boilerplate code inside an implementation of hook_uninstall by a module named “mymodule” to remove all the variables created by that module. Note that the method used for deletion involves the use of the module machine name in combination with a wildcard and is considered to be quick and dirty.
/** * Implements hook_uninstall(). */ function mymodule_uninstall() { // Delete my variables. db_delete('variable') ->condition('name', 'mymodule_%', 'LIKE') ->execute(); cache_clear_all('variables', 'cache'); }
Some Drupal programmers argue that the boilerplate code presented above should be deprecated as unsafe because it may interfere with the Drupal variable namespace of another module. However, if one exercises some care when picking the machine name for the module, this should not happen.
If you use the machine name mymodule and there exists another module named mymodule_somethingelse, and both are installed at the same Drupal website, the wildcard construct used in the boilerplate will wipe out the Drupal variables owned by both modules when you uninstall the module with the shortest name. To prevent this from happening the wildcard construct should not be used. Instead make sure your module keeps track of all the variables it creates, and deletes each one explicitly with repeated calls to del_variable.
Session variables
Session variables are not global, and only persistent for the duration of a session, where a session lasts from the point a user logs on a site, and until that user logs out again or closes his or her browser.
This means that a session variable is suitable for tracking state for a single user during a single visit, while that user moves from page to page on the site.
In addition to tracking the session of logged in users, the session variables tracked by Drupal will also track the default anonymous user (user 0). While all anonymous visitors share this user id, they all will have different session variables unless they also share the same browser instance.
You may store any variable you want to keep track of in this manner in a named location in the session array, and retrieve it later in the session. Example:
Session variables only work with arrays. Setting a session variable to a scalar value has no effect.
$_SESSION['foo'] = 'This will not work; $_SESSION['bar'] = array('OK'); $_SESSION['mymodule']['a'] = 'OK'; … echo $_SESSION['foo']; echo $_SESSION['bar'][0]; echo $_SESSION['mymodule']['a'];
The first echo
-statement will not output anything, while the
last two echo
-statements will output “OK”.
Debugging
debug() watchdog() drupal_set_message()
In addition to the functions listed above, there is the Devel project that provides functions that can be used for debugging. Unlike debug output generated by the devel-functions output generated by the debug fuction does not have any access controls, so it will always display, even for the anonymous user role.
Administrator's interface
[TBA]
Final word
While you're developing the site, you will probably have a lot of monitoring turned on, cache turned off, etc. When you go from development to production, you may want to tune your site for speed, rather than debugging. Dominique De Cooman has written a go live checklist that may come handy when turing a development site into a production site.
Last update: 2021-01-30 [gh].