Drupal module structure
Part II of the Practical Guide to Creating Basic Drupal 8 Modules
From .info to Tests, Just the Basics
Basic Structure
loremipsum.info.yml
name: Lorem ipsum type: module description: 'Lorem ipsum generator for Drupal' package: Development core: 8.x configure: loremipsum.form
Info files are now formatted as YML, and there's a distinction between modules and themes, made clear through the declaration of the type. The configure
declaration points to a route (more on this later), but beyond that, there’s nothing else. In fact, this is the only file you need for your module. After saving it (in the root/modules folder), you can enable your module at /admin/modules without breaking your site. But as you'll see, that's not quite enough.
loremipsum.module
<?php use Drupal\Core\Routing\RouteMatchInterface; /** * Implements hook_help(). */ function loremipsum_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { case 'help.page.loremipsum': return t(' <h2>Lorem ipsum generator for Drupal.</h2> <h3>Instructions</h3> <p>Lorem ipsum dolor sit amet... <strong>Just kidding!</strong></p> <p>Unpack in the <em>modules</em> folder (currently in the root of your Drupal 8 installation) and enable in <strong>/admin/modules</strong>.</p> <p>Then, visit <strong>/admin/config/development/loremipsum</strong> and enter your own set of phrases to build random-generated text (or go with the default Lorem ipsum).</p> <p>Last, visit <strong>www.example.com/loremipsum/generate/P/S</strong> where:</p> <ul> <li><em>P</em> is the number of <em>paragraphs</em></li> <li><em>S</em> is the maximum number of <em>sentences</em></li> </ul> <p>There is also a generator block in which you can choose how many paragraphs and phrases and it\'ll do the rest.</p> <p>If you need, there\'s also a specific <em>generate lorem ipsum</em> permission.</p> <h3>Attention</h3> <p>Most bugs have been ironed out, holes covered, features added. But this module is a work in progress. Please report bugs and suggestions, ok?</p> '); } }
It's good practice to at least include a hook_help() call here. Also note the use
statement pointing to the RouteMatchInterface
class. This is mainly because hook_menu() no longer exists.
... And as you go further, you’ll notice that the .module file is also used to store theming information. So keep it around.
loremipsum.install
<?php /** * @file * Installation functions for Lorem ipsum module. */ use Drupal\user\RoleInterface; /** * Implements hook_install(). */ function loremipsum_install() { user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array( 'generate lorem ipsum' => TRUE, )); }
Here, we use another class: RoleInterface. Essentially, this file tells Drupal: “once this module is enabled, find the ‘generate lorem ipsum’ permission and enable it.”
But where is that permission defined?
loremipsum.permissions.yml
generate lorem ipsum: title: 'Generate Lorem ipsum'
As you can see, this is much simpler than calling hook_permission(). The full syntax is in the PermissionHandler documentation.
loremipsum.routing.yml
loremipsum.generate: path: '/loremipsum/generate/{paragraphs}/{phrases}' defaults: _controller: '\Drupal\loremipsum\Controller\LoremIpsumController::generate' requirements: _permission: 'generate lorem ipsum' loremipsum.form: path: '/admin/config/development/loremipsum' defaults: _form: '\Drupal\loremipsum\Form\LoremIpsumForm' _title: 'Lorem ipsum settings' requirements: _permission: 'administer site configuration'
This routing file replaces the hook_menu() call. Each top-level entry defines a route, followed by indented lines specifying detailed settings.
The loremipsum.generate
route points to a page that accepts two arguments inside {}
; it maps to a controller (more on that later), unlike loremipsum.form
, which maps to a (settings) form with a title.
Both routes require permissions, but you could substitute them with _access: 'TRUE'
for unrestricted access.
loremipsum.services.yml
Allows you to declare a custom service.
loremipsum.links.menu.yml
loremipsum.form: title: 'Lorem Ipsum settings' description: 'Configure settings for the Lorem Ipsum module.' route_name: loremipsum.form parent: 'system.admin_config_development'
While the routing file creates a page at /admin/config/development/loremipsum
, these definitions are necessary to add the page to the Administration menu.
loremipsum.links.task.yml
Definitions for creating additional local tasks (tabs) for a specific route.
loremipsum.links.action.yml
Definitions for creating additional local actions (buttons) for a specific route.
loremipsum.links.contextual.yml
Definitions for creating additional contextual actions for a specific UI element.
loremipsum.libraries.yml
Used for declaring dependencies for CSS and JavaScript libraries. See more in the related section.
README.md
Unpack into the modules folder (currently at the root of your Drupal 8 installation) and enable via /admin/modules
.
Then visit /admin/config/development/loremipsum
and enter your own set of
phrases to build randomly generated text (or use the default Lorem Ipsum).
Finally, visit www.example.com/loremipsum/generate/P/S
, where:
- P is the number of paragraphs
- S is the maximum number of sentences
There is also a generator block where you can choose how many paragraphs and
phrases to generate, and it’ll take care of the rest.
If needed, there is a specific generate lorem ipsum permission as well.
Attention
Most bugs have been ironed out, holes plugged, and features added. But this module is still a work in progress. Please report any bugs or suggestions, ok?
Yes, README files are now written in markdown format. Pretty neat if you ask me.
Now let’s dig deeper into the folders for a more detailed look at specific components.
LICENSE.TXT
Do not include a LICENSE.txt (or similar) file. The packaging script will add it automatically.
/config/install/loremipsum.settings.yml
loremipsum: page_title: 'Lorem ipsum' source_text: "Lorem ipsum dolor sit amet, consectetur adipisci elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
This file stores default settings, which are assigned to the appropriate form fields via the following file:
/config/schema/loremipsum.schema.yml
loremipsum.settings: type: config_object label: 'Lorem Ipsum settings' mapping: loremipsum: type: mapping mapping: page_title: type: text label: 'Lorem ipsum generator page title:' source_text: type: text label: 'Source text for lorem ipsum generation:' block.settings.loremipsum_block: type: block_settings label: 'Lorem ipsum block' mapping: loremipsum_block_settings: type: text label: 'Lorem ipsum block settings'
The schema file is used even if you don’t define a custom table for your module—here you can see the default values assigned to the configuration form fields.
While developing this code, I found that populating fields “out of the box” was one of the trickiest tasks. Luckily, there’s a module for that: Config Inspector for Drupal 8, which helps debug default settings.
Additionally, the schema YML file is useful in many ways.
/src/Controller/LoremIpsumController.php
<?php
namespace Drupal\loremipsum\Controller;
// Change following https://www.drupal.org/node/2457593
// See https://www.drupal.org/node/2549395 for deprecated methods information
// use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Html;
// use Html instead of SafeMarkup
/**
* Controller routines for Lorem ipsum pages.
*/
class LoremIpsumController {
/**
* Constructs Lorem ipsum text with arguments.
* This callback is mapped to the path
* 'loremipsum/generate/{paragraphs}/{phrases}'.
*
* @param string $paragraphs
* The amount of paragraphs that need to be generated.
* @param string $phrases
* The maximum amount of phrases that can be generated inside a paragraph.
*/
public function generate($paragraphs, $phrases) {
We’ve now reached the core of this module — a class with a single method that generates placeholder text. As you can see, the method defined in the LoremIpsumController
class corresponds to the entry in the .routing.yml
file:
The white box in the image shows the loremipsum.routing.yml
file, and in the background is the file we are currently working on.
Next, the following code snippet retrieves the module’s settings and stores them for use:
// Default settings.
$config = \Drupal::config('loremipsum.settings');
// Page title and source text.
$page_title = $config->get('loremipsum.page_title');
$source_text = $config->get('loremipsum.source_text');
The values (loremipsum.page_title
and loremipsum.source_text
) come from the YAML config settings file:
Then we break the source_text
into an array of phrases:
$repertory = explode(PHP_EOL, $source_text);
And we use this array to build the paragraph text:
$element['#source_text'] = array();
// Generate X paragraphs with up to Y phrases each.
for ($i = 1; $i <= $paragraphs; $i++) {
$this_paragraph = '';
// When we say "up to Y phrases each", we can't mean "from 1 to Y".
// So we go from halfway up.
$random_phrases = mt_rand(round($phrases / 2), $phrases);
// Also don't repeat the last phrase.
$last_number = 0;
$next_number = 0;
for ($j = 1; $j <= $random_phrases; $j++) {
do {
$next_number = floor(mt_rand(0, count($repertory) - 1));
} while ($next_number === $last_number && count($repertory) > 1);
$this_paragraph .= $repertory[$next_number] . ' ';
$last_number = $next_number;
}
$element['#source_text'][] = Html::escape($this_paragraph);
}
Note:
['#source_text']
is a render array passed to the theme, and each element is processed withHtml::escape()
for safety.
Finally, we assign a title, specify the theme hook, and return the render array:
$element['#title'] = Html::escape($page_title);
// Theme function.
$element['#theme'] = 'loremipsum';
return $element;
}
}
Before we render this variable in the template, we need to define the actual theme.
Next steps:
Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.