02.11.2009

Generating Drupal content types with Xtext and Xpand, part 1

Generating Drupal content types with Xtext and Xpand, part 1

Recently I've implemented a module for the Drupal CMS that contributes some custom content types for Drupal. While this is a pretty straightforwad task, a lot of (technical) PHP-code is required. For example you have to write code for the database schema, for accessing the database, for the editor form etc. After implementing the first custom type I didn't want to do it again manually and tried to implement a domain specific language (DSL) that allows me to describe my content types in a more compact way, leaving out the code generation to a generator.

The DSL is implemented using Xtext, a very simple but powerful framework for building DSLs. The PHP code is generated with xpand templates. Both projects a part of the Eclipse 3.5 (Galileo) release.

The screenshot above shows a sample content type definition within the editor for my content type "ct"-language (BTW the editor, including an outline-view, code completion and type checking, is automatically generated by the Xtext framework - for free!).

While I haven't finished the generator yet, it currently generates PHP code that implements the Drupal hook methods hook_node_info, hook_form, hook_insert and hook_load (actually it generates methods that will later be invoked from the hook implementations, that are not generated yet). Based on the content definition above the following (untested!) PHP code is generated:

<?php
// -------------------------------------------------------------------------
// ---            AUTOMATICALLY GENERATED. DO NOT EDIT !                 ---
// -------------------------------------------------------------------------
function news_article_node_info() {
  return array(
    'name' => t('Article'),
    'module' => 'news',
    'description' => t('A content type for an article that can be published to a news page'),
    'has_title' => TRUE,
    'title_label' => t('Article title'),
    'has_body' => TRUE,
    'body_label' => t('Content')
  );
}

function news_article_load($node) {
  $result = db_query ('SELECT subtitle, author, date, topic FROM {article} WHERE vid=%d',
    $node->vid);
  return db_fetch_object($result);
}

function news_article_insert($node) {
  db_query("INSERT INTO {article} (vid, nid, subtitle, author, date, topic)"
    . " VALUES ('%s', '%s', '%s', '%s')",
    $node->vid, $node->nid,
    $node->subtitle,
    $node->author,
    $node->date,
    $node->topic
  );
}

function news_article_form(&$node, &$form) {
  // --- Default Values --------------------------------------------
  // - The default value for Subtitle field
  $default_subtitle = '';
  // - The default value for Author field
  $default_author = $user->name;
  // - The default value for Date field
  $default_date = date("d.m.Y H:i");
  // - The default value for Topic field
  $default_topic = '';

  // --- Form definition --------------------------------------------
  // - Widget definition for 'Subtitle'
  $form['subtitle'] = array(
    '#type' => 'text',
    '#title' => t('Subtitle'),
    '#description' => t('A subtitle for your article'),
    '#required' => FALSE,
    '#default_value' => isset($node->subtitle) ? $node->subtitle : $default_subtitle,
  );

  // - Widget definition for 'Author'
  $form['author'] = array(
    '#type' => 'text',
    '#title' => t('Author'),
    '#description' => t('The name of the author of this article. Typically you'),
    '#required' => FALSE,
    '#default_value' => isset($node->author) ? $node->author : $default_author,
  );

  // - Widget definition for 'Date'
  $form['date'] = array(
    '#type' => 'date_text',
    '#title' => t('Date'),
    '#description' => t('A date for your article. Typically today but can be set to another date'),
    '#required' => TRUE,
    '#default_value' => isset($node->date) ? $node->date : $default_date,
  );

  // - Widget definition for 'Topic'
  $form['topic'] = array(
    '#type' => 'select',
    '#title' => t('Topic'),
    '#description' => t('The topic of your article'),
    '#required' => TRUE,
    '#default_value' => isset($node->topic) ? $node->topic : $default_topic,
    '#options' => array (
      politics => 'Politics',
      sport => 'Sport',
      culture => 'Culture',
      business => 'Business',
      travel => 'Travel'
    ),
  );
}
?>

Conclusion

For me it seems to be a very good deal having generated all this PHP code from only a few lines of DSL code (remember that there is even more code needed by Drupal that will be generated in future!). Implementing the DSL's grammar and the code generator with using Xtext and xpand is a very easy task and fun to do!. Highly recommended tools :-)

More informations about this little project will follow as soon as I've implemented the missing parts of the generator.

Update: read part two here.