I am new to module development, and have spent the past month studying this guide and the various Drupal module development books. I have found each very helpful, but have had some difficulty with the database abstraction layer and the Forms API. To test some of the things that I've learned, I curbed my module development to create a rather simple "Test Module". This module makes a simple table to log messages. Those messages can also execute PHP code if they are prefixed by the string "eval:" (so I can test code).

I have attached my code below for each of the three files in hope that someone can benefit from my research (albeit not perfect). The three files are rather standard: test_module.info, test_module.install, and test_module.module. Remember to remove the "?>" from the code to comply with Drupal coding standards:

test_module.info

; $Id$
name = Test Module
description = A Test Module.
core = 6.x

package = "Test"

version = "6.x-0.1-dev"

test_module.install

// $Id$

/**
 * Install the test_module module, including it's content (node)
 * type.
 * @file
 */

/**
 * Implementation of hook_install()
 */
function test_module_install() {
	drupal_install_schema('test_module');
	db_query("DELETE FROM {cache}");
}

/**
 * Implementation of hook_uninstall()
 */
function test_module_uninstall() {
	drupal_uninstall_schema('test_module');
}

/**
 * Implementation of hook_schema()
 * @return array of Schema API table definitions.
 */
function test_module_schema() {
	$schema['test_module_log'] = array(
		'fields' => array(
			'id' => array('type' => 'serial', 'size' => 'big', 'unsigned' => TRUE, 'not null' => TRUE, 
				'description'=> "Log ID"),
			
			'timestamp' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 
				'description'=> "Timestamp (Unix Timestamp, which is limited to values above Jan 1, 1970)"),
			'message' => array('type' => 'text', 'not null' => FALSE, 
				'description'=> "Log messages."),  //NOTE:  On MySQL, text fields cannot have default values.
		),
		'primary key' => array('id') //Don't put a comma after primary key definition, since doing so will cause database errors.
	);
	
	return $schema;
}

test_module.module

// $Id$
/**
 * @file
 * A Test Module.
 */

/*******************************************************************************
 * Hook Functions (Drupal)
 ******************************************************************************/

/**
 * Display help and module information
 * @param path which path of the site we're displaying help
 * @param arg array that holds the current path as would be returned from arg() function
 * @return help text for the path
 */
function test_module_help($path, $arg) {
	//$output = '<p>'.  t("test_module is a simple module to test functions and pages in Drupal");
	//	The line above outputs in ALL admin/module pages
	switch ($path) {
		case "admin/help/test_module":
		$output = '<p>'.  t("test_module is a simple module to test functions and pages in Drupal") .'</p>';
			break;
	}
	return $output;
} // function test_module_help

/**
 * Valid permissions for this module
 * @return array An array of valid permissions for the test_module module
 */
function test_module_perm() {
	return array('administer test_module', 'access test_module content');
} // function test_module_perm()

/**
 * Menu for this module
 * @return array An array with this module's settings.
 */
function test_module_menu() {
	$items = array();
	
	//Link to the test_module admin page:
	$items['admin/settings/test_module'] = array(
		'title' => 'Test Module',
		'description' => 'Administer Test Module Messages',
		'page callback' => 'test_module_message',
		'access arguments' => array('administer test_module'),
		'type' => MENU_NORMAL_ITEM,
	);
	
	return $items;
}

/**
 * Test Module Messages
 * @return array An array of form data.
 */
function test_module_message() {
	$page_content = '';
	
	$page_content .= drupal_get_form('test_module_message_form');
	
	$get_messages = db_query("SELECT * FROM {test_module_log} ORDER BY timestamp DESC");
	if ($get_messages !== false) {
		$page_content .= "<h2>Test Message Log</h2>";
		$row_count = 1;
		$id = 0;
		while($row = db_fetch_array($get_messages)) {
			$page_content .= "<p>";
			foreach ($row as $key=>$value) {
				if ($key == 'id') $id = $value;
				if ($key == 'timestamp') $value = date('F j, Y G:i:s A', $value);
				if ($key == 'message') {
					if (strpos($value, 'eval:') !== false && $row_count === 1) {
						$value = trim(preg_replace('/eval:/', '', $value, 1));
						eval($value);
						drupal_set_message(t("Executed code:\n").strval($value));
						//Once the "eval:" code is evaluated, remove the "eval:" text to avoid executing the code again.
						db_query("UPDATE {test_module_log} SET message = '%s' WHERE id = %d", $value, $id);
					}
					$page_content .= "<br />\n";
				}
				$page_content .= "<b>".$key."</b> = ".htmlspecialchars(strval($value))."&nbsp;&nbsp;";
			}
			$page_contents .= "</p>\n";
			$row_count += 1;
		}
	}
	
	return $page_content;
}

/**
 * The callback function (form constructor) that creates the HTML form for test_module_message().
 * @return form an array of form data.
 */
function test_module_message_form() {
	$form['test_module_message'] = array(
		'#type' => 'textarea',
		'#title' => t('Message'),
		'#default_value' => variable_get('test_module_message', 'Test Message'),
		'#cols' => 50,
		'#rows' => 5,
		'#description' => t("Enter a test message.  Begin the message with \"eval:\" to execute PHPcode."),
	);
	
	//Submit button:
	$form['submit'] = array(
		'#type' => 'submit',
		'#value' => t('Save Message'),
	);
	
	return $form;
}

/**
 * Form validation for this module's settings
 * @param form an array that contains this module's settings
 * @param form_state an array that contains this module's settings
 */
function test_module_message_form_validate($form, &$form_state) {
	$test_module_message = $form_state['values']['test_module_message'];
	if (isset($test_module_message)) {
		if (!is_string($test_module_message) || $test_module_message == '') {
			form_set_error('test_module_message', t('Please enter a test message.'));
		}
	}
}

/**
 * Form submission for user data.
 * @param form an array that contains user data
 * @param form_state an array that contains user data
 */
function test_module_message_form_submit($form, &$form_state) {
	$test_message = $form_state['values']['test_module_message'];
	$exe_query = db_query("INSERT INTO {test_module_log} (timestamp, message) VALUES(%d, '%s')", time(), $test_message);
	
	$last_id = db_last_insert_id('{test_module_log}','id');
	if ($exe_query !== false) {
		$msg = 'Added message to log: %id';
		$vars = array('%id'=>$last_id);
		watchdog('test_module', $msg, $vars, WATCHDOG_INFO);
		drupal_set_message(t('Added message to log: ').strval($last_id));
	} else {
		$msg = 'Could not add message to log: ';
		$vars = array();
		watchdog('test_module', $msg, $vars, WATCHDOG_ERROR);
		drupal_set_message(t('Could not add message to log.'));
	}
	
	$form_state['redirect'] = 'admin/settings/test_module';
}

Comments

steelrh19’s picture

A word of caution:

If you want to practice your coding, type it but don't run it in your drupal site.

I just made that mistake and now I am getting the error:

Fatal error: Unsupported operand types in /home/mavware1/public_html/rickh/includes/common.inc on line 1582

I deleted my files and reinstalled them and I still have the error. I even took out the files for the test module. If anybody has any input on how to fix this problem, please tell me by replying back.

MaWe4585’s picture

i also had this sort of error lately, i figured out, that i had a ,(comma) instead of a .(dot) somewhere

for example there: drupal_set_message(t("Executed code:\n").strval($value));

maartenkuilman’s picture

Maybe its usefull to show your code that contains the bug

We can't smell whats in common.inc on line 1582 ;)

pradeepg_jain’s picture

Hi,
Have a test drupal site where u can run all ur tests. With out testing u can never know mistakes and learn from it. so test / try how much u want. what i suggest is that go to DB and see what u entered last and clean it up.

ibnkhaldun’s picture

Hi

I'd read this useful article a few minutes ago. I found you are using "timestamp" as a key. It appears as SQL reserved word (mysql "permits" it, but ¿your driver?) see the ANSI SQL 2003 link at "List of Sql Reserved Words" (is a Drupal Site's page http://drupal.org/node/141051). There you'll find TIMESTAMP.
It seems as thats what was wrong.

duygu’s picture

This tutorial is very helpful to start learning Drupal-API . Thank you.

There is a little bug in test_module_help function to see module description in Help menu:

case 'admin/help/test_module':

must be

case 'admin/help#test_module':

wheaty’s picture

Very Usefual,cool,this example make me understand the normal module develop

dianacastillo’s picture

I installed and enabled this but I dont know how to test it. how do I make it run? what url?

Diana Castillo

ibnkhaldun’s picture

Hi.
Thank you for your help. I understood wath I'm needing.

Reading around drupal documents I found some issues reporting drupal_install() runs drupall_install_schema() so your table is found when you try to create it. see "on drupal_install_schema search"

I suggest to write a couple of code lines to avoid dificulties:

$testquery = "SHOW TABLES";
$query_result =  db_query($testquery);
... lines to test if your table exists ...
// $not_exists = true | false as result
if ($not_exists) {
// call the function
    drupal_install_schema('your_module_table');
}

Notes:

  • MySql returns a column of single table-names in $query_result. The method db_fetch_object($query_result) reads name by name.
  • The reported issue was fixed on some D7 modules. I read.
  • I did not use db_table_exists() because I found It has issues: see "my search on db_table_exists()"

Hope it helps