Our goal: a module with a block
In this article we are going to build a simple module. The module will use the Block Subsystem to add a new custom block. The block that we add will simply display a list of all of the currently enabled modules on our Drupal installation.We are going to divide this task of building a new module into the three parts:
- Create a new module folder and module files
- Work with the Block Subsystem
- Write automated tests using the SimpleTest framework included in Drupal
Agile software
development is a particular methodology designed to help teams of
developers effectively and efficiently build software. While Drupal
itself has not been developed using an agile process, it does facilitate
many of the agile practices. To learn more about agile, visit http://agilemanifesto.org/
However, our goal here is not to exemplify a particular methodology,
but to discover how to write modules. It is easier to learn module
development by first writing the module, and then learn how to write
unit tests. It is easier for two reasons:- SimpleTest (in spite of its name) is the least simple part of this article. It will have double the code-weight of our actual module.
- We will need to become acquainted with the APIs we are going to use in development before we attempt to write tests that assume knowledge of those APIs.
Let's now move on to the first step of creating a new module.
Creating a new module
Creating Drupal modules is easy. How easy? Easy enough that over 5,000 modules have been developed, and many Drupal developers are even PHP novices! In fact, the code in this article is an illustration of how easy module coding can be. We are going to create our first module with only one directory and two small files.Module names
It goes without saying that building a new module requires naming the module. However, there is one minor ambiguity that ought to be cleared up at the outset, a Drupal module has two names:- A human-readable name: This name is designed to be read by humans, and should be one or a couple of words long. The words should be capitalized and separated by spaces. For example, one of the most popular Drupal modules has the human-readable name Views. A less-popular (but perhaps more creatively named) Drupal 6 module has the human-readable name Eldorado Superfly.
- A machine-readable name: This name is used internally by Drupal. It can be composed of lower-case and upper-case letters, digits, and the underscore character (using upper-case letters in machine names is frowned upon, though). No other characters are allowed. The machine names of the above two modules are views and eldorado_superfly, respectively.
Because of the convention of similar naming, the two names can usually be used interchangeably, and most of the time it is not necessary to specifically declare which of the two names we are referring to. In cases where the difference needs to be made (as in the next section), the authors will be careful to make it.
Where does our module go?
One of the less intuitive aspects of Drupal development is the filesystem layout. Where do we put a new module? The obvious answer would be to put it in the /modules directory alongside all of the core modules.
The second, far less obvious place to put modules is in /sites/all/modules. This is the location where all unmodified add-on modules ought to go, and tools like Drush ( a Drupal command line tool) will download modules to this directory.
In some sense, it is okay to put modules here. They will not be automatically overwritten during core upgrades.
However, as of this writing, /sites/all/modules is not the recommended place to put custom modules unless you are running a multi-site configuration and the custom module needs to be accessible on all sites.
The current recommendation is to put custom modules in the /sites/default/modules directory, which does not exist by default. This has a few advantages. One is that standard add-on modules are stored elsewhere, and this separation makes it easier for us to find our own code without sorting through clutter. There are other benefits (such as the loading order of module directories), but none will have a direct impact on us.
We will always be
putting our custom modules in /sites/default/modules. This follows
Drupal best practices, and also makes it easy to find our modules as
opposed to all of the other add-on modules.
The one disadvantage of storing all custom modules in /sites/default/modules
appears only under a specific set of circumstances. If you have Drupal
configured to serve multiple sites off of one single instance, then the /sites/default
folder is only used for the default site. What this means, in practice,
is that modules stored there will not be loaded at all for other sites.In such cases, it is generally advised to move your custom modules into /sites/all/modules/custom.
Other module directories
Drupal does look in a few other places for modules. However, those places are reserved for special purposes.
Drupal does look in a few other places for modules. However, those places are reserved for special purposes.
Creating the module directory
Now that we know that our modules should go in /sites/default/modules, we can create a new module there.Modules can be organized in a variety of ways, but the best practice is to create a module directory in /sites/default/modules, and then place at least two files inside the directory: a .info (pronounced "dot-info") file and a .module ("dot-module") file.
The directory should be named with the machine-readable name of the module. Similarly, both the .info and .module files should use the machine-readable name.
We are going to name our first module with the machine-readable name first, since it is our first module. Thus, we will create a new directory, /sites/default/modules/first, and then create a first.info file and a first.module file:

For permissions, make sure that your webserver can read both the .info and .module files. It should not be able to write to either file, though.
In some sense, the only
file absolutely necessary for a module is the .info file located at a
proper place in the system. However, since the .info file simply
provides information about the module, no interesting module can be
built with just this file.
Next, we will write the contents of the .info file.Writing the .info file
The purpose of the .info file is to provide Drupal with information about a module—information such as the human-readable name, what other modules this module requires, and what code files this module provides.A .info file is a plain text file in a format similar to the standard INI configuration file. A directive in the .info file is composed of a name, and equal sign, and a value:
name = valueBy Drupal's coding conventions, there should always be one space on each side of the equals sign.
Some directives use an array-like syntax to declare that one name has multiple values. The array-like format looks like this:
name[] = value1 name[] = value2Note that there is no blank space between the opening square bracket and the closing square bracket.
If a value spans more than one line, it should be enclosed in quotation marks.
Any line that begins with a ; (semi-colon) is treated as a comment, and is ignored by the Drupal INI parser.
Drupal does not support INI-style section headers such as those found in the php.ini file.
To begin, let's take a look at a complete first.info file for our first module:;$Id$ name = First description = A first module. package = Drupal 7 Development core = 7.x files[] = first.module ;dependencies[] = autoload ;php = 5.2This ten-line file is about as complex as a module's .info file ever gets.
The first line is a standard. Every .info file should begin with ;$Id$. What is this? It is the placeholder for the version control system to store information about the file. When the file is checked into Drupal's CVS repository, the line will be automatically expanded to something like this:
;$Id: first.info,v 1.1 2009/03/18 20:27:12 mbutcher Exp $This information indicates when the file was last checked into CVS, and who checked it in.
CVS is going away, and
so is $Id$. While Drupal has been developed in CVS from the early days
through Drupal 7, it is now being migrated to a Git repository. Git does
not use $Id$, so it is likely that between the release of Drupal 7 and
the release of Drupal 8, $Id$ tags will be removed.
You will see all PHP and .info files beginning with the $Id$ marker. Once Drupal uses Git, those tags may go away.The next couple of lines of interest in first.info are these:
name = First description = A first module. package = Drupal 7 DevelopmentThe first two are required in every .info file. The name directive is used to declare what the module's human-readable name is. The description provides a one or two-sentence description of what this module provides or is used for. Among other places, this information is displayed on the module configuration section of the administration interface in Modules.

The third item, package, identifies which family (package) of modules this module is related to. Core modules, for example, all have the package Core. In the screenshot above, you can see the grouping package Core in the upper-left corner. Our module will be grouped under the package Drupal 7 Development to represent its relationship. As you may notice, package names are written as human-readable values.
When choosing a human-readable module name, remember to adhere to the specifications mentioned earlier in this section.
The next directive is the core directive: core = 7.x. This simply declares which main-line version of Drupal is required by the module. All Drupal 7 modules will have the line core = 7.x.Along with the core version, a .info file can also specify what version of PHP it requires. By default, Drupal 7 requires Drupal 5.1 or newer. However, if one were to use, say, closures (a feature introduced in PHP 5.3), then the following line would need to be added:
php = 5.3Next, every .info file must declare which files in the module contain PHP functions, classes, or interfaces. This is done using the files[] directive. Our small initial module will only have one file, first.module. So we need only one files[] directive.
files[] = first.moduleMore complex files will often have several files[] directives, each declaring a separate PHP source code file.
JavaScript, CSS, image
files, and PHP files (like templates) that do not contain functions that
the module needs to know about needn't be included in files[]
directives. The point of the directive is simply to indicate to Drupal
that these files should be examined by Drupal.
One directive that we will not use for this module, but which plays a very important role is the dependencies[]
directive. This is used to list the other modules that must be
installed and active for this module to function correctly. Drupal will
not allow a module to be enabled unless its dependencies have been
satisfied.
Drupal does not contain
a directive to indicate that another module is recommended or is
optional. It is the task of the developer to appropriately document this
fact and make it known. There is currently no recommended best practice
to provide such information.
Now we have created our first.info file. As soon as Drupal reads this file, the module will appear on our Modules page.
With our .info file completed, we can now move on and code our .module file.
Modules checked into
Drupal's version control system will automatically have a version
directive added to the .info file. This should typically not be altered.
Creating a module file
The .module file is a PHP file that conventionally contains all of the major hook implementations for a module. We will gain some practical knowledge of them.A hook implementation is a function that follows a certain naming pattern in order to indicate to Drupal that it should be used as a callback for a particular event in the Drupal system. For Object-oriented programmers, it may be helpful to think of a hook as similar to the Observer design pattern.
When Drupal encounters an event for which there is a hook (and there are hundreds of such events), Drupal will look through all of the modules for matching hook implementations. It will then execute each hook implementation, one after another. Once all hook implementations have been executed, Drupal will continue its processing.
In the past, all Drupal hook implementations had to reside in the .module file. Drupal 7's requirements are more lenient, but in most moderately sized modules, it is still preferable to store most hook implementations in the .module file.
There are cases where
hook implementations belong in other files. In such cases, the reasons
for organizing the module in such a way will be explained.
To begin, we will create a simple .module file that contains a single hook implementation – one that provides help information.<?php
// $Id$
/**
* @file
* A module exemplifying Drupal coding practices and APIs.
*
* This module provides a block that lists all of the
* installed modules. It illustrates coding standards,
* practices, and API use for Drupal 7.
*/
/**
* Implements hook_help().
*/
function first_help($path, $arg) {
if ($path == 'admin/help#first') {
return t('A demonstration module.');
}
}
Before we get to the code itself, we will talk about a few stylistic items.To begin, notice that this file, like the .info file, contains an $Id$ marker that CVS will replace when the file is checked in. All PHP files should have this marker following a double-slash-style comment: // $Id$.
Next, the preceding code illustrates a few of the important coding standards for Drupal.
Source code standards
Drupal has a thorough and strictly enforced set of coding standards. All core code adheres to these standards. Most add-on modules do, too. (Those that don't generally receive bug reports for not conforming.) Before you begin coding, it is a good idea to familiarize yourself with the standards as documented here: http://drupal.org/coding-standards. The Coder module can evaluate your code and alert you to any infringement upon the coding standards.
We will adhere to the
Drupal coding standards. In many cases, we will explain the standards as
we go along. Still, the definitive source for standards is the URL
listed above, not our code here.
We will not re-iterate the coding standards. The details can be found
online. However, several prominent standards deserve immediate mention.
I will just mention them here, and we will see examples in action as we
work through the code.- Indenting: All PHP and JavaScript files use two spaces to indent. Tabs are never used for code formatting.
- The <?php ?> processor instruction: Files that are completely PHP should begin with <?php, but should omit the closing ?>. This is done for several reasons, most notably to prevent the inclusion of whitespace from breaking HTTP headers.
- Comments: Drupal uses Doxygen-style (/** */) doc-blocks to comment functions, classes, interfaces, constants, files, and globals. All other comments should use the double-slash (//) comment. The pound sign (#) should not be used for commenting.
- Spaces around operators: Most operators should have a whitespace character on each side.
- Spacing in control structures: Control structures should have spaces after the name and before the curly brace. The bodies of all control structures should be surrounded by curly braces, and even that of if statements with one-line bodies.
- Functions: Functions should be named in lowercase letters using underscores to separate words. Later we will see how class method names differ from this.
- Variables: Variable names should be in all lowercase letters using underscores to separate words. Member variables in objects are named differently. As we work through examples, we will see these and other standards in action.
| Read more about this book |
(For more resources on this subject, see here.)
Doxygen-style doc blocks
Drupal uses Doxygen to extract API documentation from source code. Experienced PHP coders may recognize this concept, as it is similar to PhpDocumentor comments (or Java's JavaDoc). However, Drupal does have its idiosyncrasies, and does not follow the same conventions as these systems.We will only look at the documentation blocks as they apply to our preceding specific example.
Let's take a closer look at the first dozen lines of our module:
<?php // $Id$ /** * @file * A module exemplifying Drupal coding practices and APIs. * * This module provides a block that lists all of the * installed modules. It illustrates coding standards, * practices, and API use for Drupal 7. */After the PHP processor instruction and the $Id$ line, the part of the code is a large comment. The comment begins with a slash and two asterisks (/**) and ends with a single asterisk and a slash (*/). Every line between begins with an asterisk. This style of comment is called a doc block or documentation block.
A doc block is a comment that contains API information. It can be extracted automatically by external tools, which can then format the information for use by developers.
Doc blocks in action: api.drupal.org
Drupal's doc blocks are used to generate the definitive source of Drupal API documentation at http://api.drupal.org. This site is a fantastic searchable interface to each and every one of Drupal's functions, classes, interfaces, and constants. It also contains some useful how-to documentation.
All of Drupal is documented using doc blocks, and you should always use them to document your code.Drupal's doc blocks are used to generate the definitive source of Drupal API documentation at http://api.drupal.org. This site is a fantastic searchable interface to each and every one of Drupal's functions, classes, interfaces, and constants. It also contains some useful how-to documentation.
The initial doc block in the code fragment above begins with the @file decorator. This indicates that the doc block describes the file as a whole, not a part of it. Every file should begin with a file-level doc block.
From there, the format of this doc block is simple: It begins with a single-sentence description of the file (which should always be on one line), followed by a blank line, followed by one or more paragraph descriptions of what this file does.
The Drupal coding standards stipulate that doc blocks should always be written using full, grammatically correct, punctuated sentences.
If we look a little further down in our module file, we can see our first function declaration:
/**
* Implements hook_help().
*/
function first_help($path, $arg) {
if ($path == 'admin/help#first') {
return t('A demonstration module.');
}
}
Before moving onto the function, let's take a look at the doc block here. It is a single sentence: Implements hook_help().
This single-sentence description follows a Drupal doc block coding
standard, too. When a function is a hook implementation, it should state
so in exactly the format used above: Implements NAME OF HOOK.
Why the formula? So that developers can very quickly identify the
general purpose of the function, and also so that automated tools can
find hook implementations.Note that we don't add any more of a description, nor do we document the parameters. This is okay when two things are true:
- The function implements a hook
- The function is simple
Later we will see how non-hook functions and more complex hook implementations have an extended form of doc block comment. For now, though, we have addressed the basics of doc blocks. We will move on and look at the help function.
The help hook
Drupal defines a hook called hook_help(). The help hook is invoked (called) when a user browses the help system. Each module can have one implementation of hook_help(). Our module provides brief help text by implementing the help hook.function first_help($path, $arg) {
if ($path == 'admin/help#first') {
return t('A demonstration module.');
}
}
How does this function become a hook implementation? Strictly by virtue of its name: first_help(). The name follows the hook pattern. If the hook is named hook_help(), then to implement it, we replace the word hook with the name of the module. Thus, to implement hook_help(), we simply declare a function in our first module named first_help().Each hook has its own parameters, and all core Drupal hooks are documented at http://api.drupal.org.
A hook_help() implementation takes two arguments:
- $path: The help system URI path
- $arg: The arguments used when accessing this URL
Specifically, the module-wide help text should be made available at the URI admin/help#MODULE_NAME, where MODULE_NAME is the machine-readable name of the module.
Our function works by checking the $path. If the $path is set to admin/help#first, the default help screen for a module, then it will return some simple help text.
If we were to enable our new module and then look at Drupal's help text page with our new module enabled, we would see this:


if ($path == 'admin/help#first') {
return t('A demonstration module.');
}
First, the previous code conforms to Drupal's coding standards, which we briefly covered earlier. Whitespace separates the if
and the opening parenthesis (, and there is also a space between the
closing parenthesis ) and the opening curly brace ({). There are also
spaces on both sides of the equality operator ==. Code is indented with
two spaces per level, and we never use tabs. In general, Drupal coders
tend to use single quotes (') to surround strings because of the
(admittedly slight) speed improvement gained by skipping interpolation.Also important from the perspective of coding standards is the fact that we enclose the body of the if statement in curly braces even though the body is only one line long. And we split it over three lines, though we might have been able to fit it on one. Drupal standards require that we always do this.
Finally, in the example above we see one new Drupal function: t().
The t() function and translations
Every natural language string that may be displayed to a user should be wrapped in the t() function. Why? Because the t() function is responsible for translating strings from one language into other.Drupal supports dozens of languages. This is one of the strongest features of Drupal's internationalization and localization effort. The method by which Drupal supports translation is largely through the t() function.
There are three features of this function that every developer should understand:
- What happens when t() is called
- How Drupal builds the translation table
- Additional features you get by using the t() function
The second thing to look at is how Drupal builds the translation information. There are two aspects to this: The human aspect and the technical one. The translations themselves are done by dozens and dozens of volunteers who translate not only Drupal's core, but also many of the add-on modules. Their translations are then made into downloadable language bundles (.po files) that you can install on your site.
On the more technical side, this dedicated group of translators does not simply search the source code looking for calls to the t() function. Instead, an automated tool culls the code and identifies all of the translatable strings. This automated tool, though, can only extract string literals. In other words, it looks for calls like this:
t('This is my string');
It cannot do anything with lines like this, though:$variable = 'This is a string'; t($variable);Why won't the translation system work in the case above? Because when the automated translation system runs through the code, it does not execute the code. It simply reads it. For that reason, it would become cumbersome (and many times impossible) to determine what the correct value of a variable is.
The locale module can,
under certain circumstances, identify other strings that were not
correctly passed into the t() function and make them available to
translators. This, however, should not be relied upon.
So the t() function should always be given a literal string for its first argument.The third thing to note about the t() function is that it does more than translate strings. It offers a method of variable interpolation that is more secure than the usual method.
In many PHP applications, you will see code like this:
print "Welcome, $username.";The code above will replace $username with the value of the $username variable. This code leaves open the possibility that the value of $username contains data that will break the HTML in the output – or worse, that it will open an avenue for a malicious user to inject JavaScript or other code into the output.
The t() function provides an alternate, and more secure, method for replacing placeholders in text with a value. The function takes an optional second argument, which is an associative array of items that can be substituted. Here's an example that replaces the the previous code:
$values = array('@user' => $username);
print t('Welcome, @user', $values);
In the previous case, we declare a placeholder named @user, the value of which is the value of the $username variable. When the t() function is executed, the mappings in $values are used to substitute placeholders with the correct data.But there is an additional benefit: these substitutions are done in a secure way.
If the placeholder begins with @, then before it inserts the value, Drupal sanitizes the value using its internal check_plain() function.
If you are sure that the string doesn't contain any dangerous information, you can use a different symbol to begin your placeholder: the exclamation mark (!). When that is used, Drupal will simply insert the value as is. This can be very useful when you need to insert data that should not be translated:
$values = array('!url' => 'http://example.com');
print t('The website can be found at !url', $values);
In this case, the URL will be entered with no escaping. We can do
this safely only because we already know the value of URL. It does not
come from a distrusted user.Finally, there is a third placeholder decorator: the percent sign (%) tells Drupal to escape the code and to mark it as emphasized.
$values = array('%color' => 'blue');
print t('My favorite color is %color.', $values);
Not only will this remove any dangerous characters from the value,
but it will also insert markup to treat that text as emphasized text. By
default, the preceding code would result in the printing of the string My favorite color is <em>blue</em>. The emphasis tags were added by a theme function (theme_placeholder()) called by the t() function.There are more things that can be done with t(), format_plural(), translation contexts, and other translation system features. To learn more, you may want to start with the API documentation for t() at http://api.drupal.org/api/function/t/7.
We have taken a sizable detour to talk about the translation system, but with good reason. It is a tremendously powerful feature of Drupal, and should be used in all of your code. Not only does it make modules translatable, but it adds a layer of security. It can even be put to some interesting (if unorthodox) uses, as is exemplified by the String Overrides module at http://drupal.org/project/stringoverrides.
At this point, we have created a working module, though the only thing that it does is display help text. It's time to make this module a little more interesting. In the next section we will use the Block API to write code that generates a block listing all of the currently enabled modules.
| Read more about this book |
(For more resources on this subject, see here.)
Working with the Block API
In this section, we are going to learn how to create blocks in code. The Block API provides the tools for hooking custom code into the block subsystem.
The Block API has
changed substantially since Drupal 6. In Drupal 6, there was only one
function used for all block operations. Now there is a family of related
functions.
We are going to create a block that displays a bulleted list of all of the modules currently enabled on our site.There are half a dozen hooks in the Block API, providing opportunities to do everything from declaring new blocks to altering the content and behavior of existing blocks. For our simple module, we are going to use two different hooks:
- hook_block_info(): This is used to tell Drupal about the new block or blocks that we will declare
- hook_block_view(): This tells Drupal what to do when a block is requested for viewing
Since modules should be able to create multiple blocks, that means that the Block API must make it possible for one block implementation to manage multiple blocks. Thus, first_block_info() can declare any number of blocks, and first_block_view() can return any number of blocks.
The entire Block API is documented in the official Drupal 7 API documentation, and even includes an example module: http://api.drupal.org/api/drupal/developer--examples--block_example.module/7.
To keep our example simple, we will be creating only one block.
However, it is good to keep in mind that the API was designed in a way
that would allow us to create as many blocks as we want.Let's start with an implementation of hook_block_info().
The block info hook
All of the functions in our module will go inside of the first.module file—the default location for hook implementations in Drupal. Before, we created first_help(), an implementation of hook_help(). Now, we are going to implement the hook_block_info() hook.The purpose of this hook is to tell Drupal about all of the blocks that the module provides. Note that, as with any hook, you only need to implement it in cases where your module needs to provide this functionality. In other words, if the hook is not implemented, Drupal will simply assume that this module has no associated blocks.
Here's our 'block info' hook implementation declaring a single block:
/**
* Implements hook_block_info().
*/
function first_block_info() {
$blocks = array();
$blocks['list_modules'] = array(
'info' => t('A listing of all of the enabled modules.'),
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
Once again, this function is preceded by a doc block. And since we are writing a trivial implementation of hook_block_info(), we needn't add anything other than the standard documentation.An implementation of hook_block_info() takes no arguments and is expected to return an associative array.
Associative arrays: Drupal's data structure of choice
Arrays in PHP are very fast. They are well supported, and because they serve double duty as both indexed arrays and dictionary-style associative arrays, they are flexible. For those reasons Drupal makes heavy use of arrays—often in places where one would expect objects, linked lists, maps, or trees.
The returned array should contain one entry for every block that this module declares, and the entry should be of the form $name => array($property => $value).Arrays in PHP are very fast. They are well supported, and because they serve double duty as both indexed arrays and dictionary-style associative arrays, they are flexible. For those reasons Drupal makes heavy use of arrays—often in places where one would expect objects, linked lists, maps, or trees.
Thus, the important part of our function above is this piece:
$blocks['list_modules'] = array(
'info' => t('A listing of all of the enabled modules.'),
'cache' => DRUPAL_NO_CACHE,
);
This defines a block named list_modules that has two properties:- info: This provides a one-sentence description of what this block does. The text is used on the block administration screens.
- cache: This tells Drupal how to cache the data from this block. Here in the code I have set this to DRUPAL_NO_CACHE, which will simply forgo caching altogether. There are several other settings providing global caching, per-user caching, and so on.
We have now created a function that tells Drupal about a block named list_modules. With this information, Drupal will assume that when it requests that block for viewing, some function will provide the block's contents. The next function we implement will handle displaying the block.
The block view hook
In the section above we implemented the hook that tells Drupal about our module's new block. Now we need to implement a second hook—a hook responsible for building the contents of the block. This hook will be called whenever Drupal tries to display the block.An implementation of hook_block_view() is expected to take one argument—the name of the block to retrieve—and return an array of data for the given name.
Our implementation will provide content for the block named list_modules.
Here is the code:
/**
* Implements hook_block_view().
*/
function first_block_view($block_name = '') {
if ($block_name == 'list_modules') {
$list = module_list();
$theme_args = array('items' => $list, 'type' => 'ol');
$content = theme('item_list', $theme_args);
$block = array(
'subject' => t('Enabled Modules'),
'content' => $content,
);
return $block;
}
}
By now, the doc block should be familiar. The Drupal coding style should also look familiar. Again, we have implemented hook_block_view() simply by following the naming convention.The argument that our first_block_view() function takes, is the name of the block. As you look through Drupal documentation you may see this argument called $which_block or $delta—terms intended to identify the fact that the value passed in is the identifier for which block should be returned.
The term $delta
is used for historical reasons. It is not a particularly apt description
for the role of the variable, and more recently it has been replaced by
more descriptive terms.
The only block name that our function should handle is the one we declared in first_block_info(). If the $block_name is list_modules, we need to return content.Let's take a close look at what happens when a request comes in for the list_modules block. This is the content of the if statement above:
$list = module_list();
$theme_args = array('items' => $list, 'type' => 'ol');
$content = theme('item_list', $theme_args);
$block = array(
'subject' => t('Enabled Modules'),
'content' => $content,
);
return $block;
On the first line, we call the Drupal function module_list().
This function simply returns an array of module names. (In fact, it is
actually an associative array of module names to module names. This
duplicate mapping is done to speed up lookups.)Now we have a raw array of data. The next thing we need to do is format that for display. In Drupal formatting is almost always done by the theming layer. Here, we want to pass off the data to the theme layer and have it turn our module list into an HTML ordered list.
The main function for working with the theming system is theme(). In Drupal 7, theme() takes one or two arguments:
- The name of the theme operation
- An associative array of variables to pass onto the theme operation
Previous versions of
Drupal took any number of arguments, depending on the theme operation
being performed. That is no longer the case in Drupal 7.
To format an array of strings into an HTML list, we use the item_list theme, and we pass in an associative array containing two variables:- the items we want listed
- the type of listing we want
Now all we need to do is assemble the data that our block view must return. An implementation of hook_block_view() is expected to return an array with two items in it:
- subject: The name or title of the block.
- content: The content of the block, as formatted text or HTML.
One thing you may notice about the $block array in the code above is its formatting:
$block = array(
'subject' => t('Enabled Modules'),
'content' => $content,
);
This is how larger arrays should be formatted according to the Drupal
coding standards. And that trailing comma is not a error. Drupal
standards require that multi-line arrays terminate each line—including
the last item—with a comma. This is perfectly legal in PHP syntax, and
it eliminates simple coding syntax problems that occur when items are
added to or removed from the array code.
Not in JavaScript!
Drupal programmers make the mistake of using a similar syntax in Drupal JavaScript. Object literal definitions (the JavaScript equivalent of associative arrays) do not allow the last item to terminate with a comma. Doing so causes bugs in IE and other browsers.
Now we have walked through our first module's code. For all practical
purposes, we have written an entire module (though we still have some
automated testing code to write). Let's see what this looks like in the
browser.Drupal programmers make the mistake of using a similar syntax in Drupal JavaScript. Object literal definitions (the JavaScript equivalent of associative arrays) do not allow the last item to terminate with a comma. Doing so causes bugs in IE and other browsers.
The first module in action
Our module is written and ready to run. To test this out, we need to first enable the module, and then go to the block administration page.The module can be enabled through the Modules menu. Once it is enabled, go to Structure | Blocks. You should be able to find a block described as A listing of all of the enabled modules. (This text came from our first_block_info() declaration.)
Once you have placed this module in one of the block regions, you should be able to see something like this:

Now that we have a working module, we are going to write a couple of automated tests for it.