Module Structures – Mark Jones – @JustAnotherMark



Module Structures – Mark Jones – @JustAnotherMark

0 0


module-dev-slides

Presentation about Drupal 7 module structures as given at DrupalCamp Yorkshire 2014.

On Github JustAnotherMark / module-dev-slides

Module Structures

Mark Jones

@JustAnotherMark

  • Intro
  • Who am I?
  • What do I do?
  • Drupal Experience
  • Talk Experience

Structures / Components

Basic Include files Custom Hooks Custom Info Hooks Custom Include Files UI Modules API Modules
  • Brief explanation of each
  • My classification, may be missing some
  • UI/API modules uses the others

Principles

Separation of concerns

  • Too difficult to picture everything
  • Define interfaces
  • Focus on individual part

Keep it simple

  • Don't over-engineer
  • Code for everyone on the team
  • Code for future you

Allow for changes

  • Contradicts 'Keep it simple'
  • Flexibility over perfection
  • Drupal hook system helps

Learn from others

  • Look at core
  • Look at contrib
  • Look at Drupal 8
  • Look outside Drupal
  • Talk to people!

Examples

Examples project

http://dgo.to/examples

Onto the Code

1. Basic

  • Everything in .info, .module and .install
  • Perfect for simple modules
  • Will get unwieldy for complex modules

.info Files

date_popup.info

name = Date Popup
description = Enables jquery popup calendars and time entry widgets for selecting dates and times.
dependencies[] = date_api
package = Date/Time
core = 7.x
configure = admin/config/date/date_popup

stylesheets[all][] = themes/datepicker.1.7.css

.module Files

date_popup.module

/**
 * Load needed files.
 */
function date_popup_add() {
  static $loaded = FALSE;
  if ($loaded) {
    return;
  }

.install Files

commerce_coupon.install

function commerce_coupon_schema() {
  $schema['commerce_coupon'] = array(
    'description' => 'The base table for coupons.',
      'fields' => array(
        'coupon_id' => array(
          'description' => 'The primary identifier for the coupon.',
          'type' => 'serial',
          'unsigned' => TRUE,
          'not null' => TRUE,
      ),

Writing .install files

Asset files

  • CSS/JavaScript
  • Images
  • Readme.txt

Examples

Pros & Cons

  • Keeps it simple
  • Separation through modules
  • Doesn't build for the future

Why & When?

  • Function order
    • Hooks
    • Alters
    • Other functions
  • Be consistent
  • Can't really go wrong

2. Include Files

  • Some standard ones:
    • [module].rules_defaults.inc
    • [module].views_defaults.inc
    • [module].admin.inc
  • Allows related hooks & functions to be grouped together

Aside 1 [Classes]

  • Can autoload classes
  • Reference in .info file
    files[] = path/to/filename.inc
  • Class loaded with module
  • Used by SimpleTest

Aside 2 [Features]

  • Features uses them heavily
  • Generated code only
  • Allows .module to use custom code only
  • Be wary of files/hooks Features might use

Examples

commerce_discount.rules.inc

Pros & Cons

  • Still simple
  • Extra separation within the module
  • Number of files limits future
  • Not always well documented
Particularly for exported code

Why & When?

  • Consider if multiple functions could use a standard .inc file
  • Be suspicious if .module has more than 15 functions
  • Little reason not to

3. Custom Hooks

  • Allow other modules to add or alter
  • Module can implement its own hooks
  • Prefix hook with module name
    • hook_[my_module]_office_presave()

How?

Examples

  • hook_node_presave()
    // Let modules modify the node before it is saved to the database.
    module_invoke_all('node_presave', $node);
    module_invoke_all('entity_presave', $node, 'node');

Examples

A Third Way

  • hook_menu()
    $callbacks = array();
      foreach (module_implements('menu') as $module) {
        $router_items = call_user_func($module . '_menu');
        if (isset($router_items) && is_array($router_items)) {
          foreach (array_keys($router_items) as $path) {
            $router_items[$path]['module'] = $module;
          }
          $callbacks = array_merge($callbacks, $router_items);
        }
      }?

A Third Way

function module_invoke_all($hook) {
  $args = func_get_args();
  // Remove $hook from the arguments.
  unset($args[0]);
  $return = array();
  foreach (module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    if (function_exists($function)) {
      $result = call_user_func_array($function, $args);
      if (isset($result) && is_array($result)) {
        $return = array_merge_recursive($return, $result);
      }
      elseif (isset($result)) {
        $return[] = $result;
      }
    }
  }

  return $return;
}

Pros & Cons

  • Allows much greater reusability
  • Others can alter behaviour without hacking your module
  • Starting to get more complex
  • Some performance effect (although caching helps)

Why & When?

  • Don't overuse
  • Very useful for plugins/future extensions
  • E.g. Social media integration

4. Custom Info Hooks

  • Allow other modules to define
    • multiple parameters
    • callbacks
    • classes

Examples

poll_node_info()

function poll_node_info() {
  return array(
    'poll' => array(
      'name' => t('Poll'),
      'base' => 'poll',
      'description' => t('A <em>poll</em> is a question with a set of possible responses. A <em>poll</em>, once created, automatically provides a simple running count of the number of votes received for each response.'),
      'title_label' => t('Question'),
      'has_body' => FALSE,
    ),
  );
}

Examples

_node_types_build()

foreach (module_implements('node_info') as $module) {
  $info_array = module_invoke($module, 'node_info');
  foreach ($info_array as $type => $info) {
    $info['type'] = $type;
    $_node_types->types[$type] = node_type_set_defaults($info);
    $_node_types->types[$type]->module = $module;
    $_node_types->names[$type] = $info['name'];
  }
}

Pros & Cons

  • More implementer control
  • Easier for implementers to share code
  • One call for all implementing info
  • More complicated
  • Need to store/reload/update info
Static variables, cache table, etc

Why & When?

  • Info only & use callbacks/hooks for code
  • Useful if implementers may define many
  • Reusable modules most of the time

5. Custom Include Files

  • Enable implementers to separate code
  • Improve implementers structure

Examples

  • rules.rules.inc
    /**
     * Load all module includes as soon as this file gets included, which is done
     * automatically by module_implements().
     */
    foreach (rules_core_modules() as $module) {
      module_load_include('inc', 'rules', "modules/$module.rules");
      module_load_include('inc', 'rules', 'modules/events');
    }
    
  • Note: not in a function

Pros & Cons

  • Fairly complex
  • Can cause confusion
  • Only helpful with lots of implementers
  • Provides separation for implementers
  • Developers know where to start looking

Why & When?

  • Beyond most day to day use cases
  • Only for modules with many custom hooks
  • Only use it for big contrib modules
  • Useful to understand for debugging

6. UI Modules

  • Allow disabling of admin pages
  • Prevent config/settings tampering
  • Functionality still works

Examples

  • Rules
  • Views

Pros & Cons

  • Increase separation
  • Simpler than permissions
  • Restrict curious users
  • Prevent fixing on one environment

Why & When?

  • It depends
  • Unnecessary for most modules
  • Depends on client for custom development

7. API Modules

  • Put previous steps together
  • No implementations within the module(ish)
  • Provide submodule for default behaviours
  • If not required consider making submodules a separate project

Examples

More Examples

Pros & Cons

  • Makes no assumptions about usage
  • Standard interface for similar modules
  • Separation of core from implementations
  • Prevents duplication

Why & When?

  • Only for contrib or widely reusable
  • Plugins can focus on what they're good at
  • Can be more difficult to find relevant code

Summary

What do I hope you'll take away from this talk?

Overview of module structures

API docs are your friend

Publish & ask others

Get it working then get it right

Questions

  • Twitter: @JustAnotherMark
  • Drupal.org: justanothermark