Basic structure

Last updated on
31 July 2017

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 difference between modules and themes that must be made clear via the type declaration. The config declaration points to a route (more on that later) but other than that, there's not much else. In fact, this is the only file you'll ever need for your module. After saving this (in the root /modules folder) you can enable your module in /admin/modules without breaking your website. But, as you'll see further ahead, that's not nearly 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 put at least a call to hook_help() here. Also, note the use statement pointing to the RouteMatchInterface class. It's there basically because, well, hook_menu() is no more.

... And as we progress, you'll notice the .module file will also be used to store theming information. So keep it.

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 have the use of another class: RoleInterface. Basically, this file tells Drupal: "once this module is enabled, look for the generate lorem ipsum permission and enable it".

But where is this permission defined?

loremipsum.permissions.yml

generate lorem ipsum:
  title: 'Generate Lorem ipsum'

As you can see, it's much simpler than a call to hook_permission().

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'

The routing file replaces the hook_menu() call. Each entry (without indentation) points to a route, with subsequent indented lines detailing specific settings.

The loremipsum.generate route points to a page which takes two arguments between { }; it corresponds to a Controller (more on that later on), unlike loremipsum.form which points to a (settings) form with a title.

Both routes require permissions, but you can replace them with _access: 'TRUE' for unrestricted access.

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 needed to add the pages to the Administration menu.

README.md

Lorem ipsum
===========

Lorem ipsum generator for Drupal.

Instructions
------------

Lorem ipsum dolor sit amet... **Just kidding!**

Unpack in the *modules* folder (currently in the root of your Drupal 8
installation) and enable in `/admin/modules`.

Then, visit `/admin/config/development/loremipsum` and enter your own set of
phrases to build random-generated text (or go with the default Lorem ipsum).

Last, 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 in which you can choose how many paragraphs and
phrases and it'll do the rest.

If you need, there's also a specific *generate lorem ipsum* permission.

Attention
---------

Most bugs have been ironed out, holes covered, features added. But this module
is a work in progress. Please report bugs and suggestions, ok?

Yes, README files are now written in markdown format. Pretty cool if you ask me.

Now let's dive into the folders for an in-depth look at specific details.

/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 configuration defaults, which are assigned to the correct fields via the next 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:

The schema file is used even if you don't define a custom table for your module - here you can see defaults being assigned to the fields of a configuration form.

While developing this code, I found that populating fields out-of-the-box was one of the most difficult tasks. But fortunately, there's a module for that: Configuration inspector for Drupal 8 which will help you debug your defaults.

Also, the Schema YML file is super useful in many different ways.

/src/Controller/LoremIpsumController.php

<?php

namespace Drupal\loremipsum\Controller;

use Drupal\Core\Url;
// Change following https://www.drupal.org/node/2457593
use Drupal\Component\Utility\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 arrived at the core of this module, a class with a single method that generates filler text. As you can see, the method generate inside the LoremIpsumController class relates to the entry in the routing YML file:

Controller method

The white bordered picture shows the code of the: loremipsum.routing.yml and the background that of the file we are working in.

Now lets continue, the next piece of code gets module settings and stores them for later 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 above parameters (loremipsum.page_title and loremipsum.source_text) come from the YML file:

Default settings

Then we break down the phrases from $source_text into a single array:

    $repertory = explode(PHP_EOL, $source_text);

And use this array to build paragraphs of 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'][] = SafeMarkup::checkPlain($this_paragraph);
    }

Note that ['#source_text'] is a render array passed to the template, and that each item in this array goes through SafeMarkup::checkPlain() for safety.

Finally we give our render array a title, assign a theme function, and return it:

    $element['#title'] = SafeMarkup::checkPlain($page_title);

    // Theme function.
    $element['#theme'] = 'loremipsum';

    return $element;
  }

}

But before we pass this variable to our template, there are theming functions to be taken care of.

Next Step;

Theming the module.

Followed by;

Building the settings form.

Defining a block for this module.

Writing tests for this module.