Hey all, welcome to part 2, a continuation of the Hello World module in Drupal 6 – Part 1 I know what you’re thinking: “A full 8 months later? wtf?”. If you werent thinking that then you are now. Or something along those lines. I apologise for the delay(if you can even call it that). Blah blah blah

{begin: excuse}
//TODO: enter excuses here
{end: excuse}

Now that we’ve got that out of the way, Shall we then get to the Semicontacts Module tutorial. Right.

In this tutorial we are going to learn the following

1. How to use the Drupal Database Abstraction Layer.
2. How to use the Drupal Form API to create and validate forms.
3. How to use the Drupal Schema API to create a database table during module installation.

NOTE: Please note that this tutorial is used for educational purposes only.<br />
 It is nowhere near production ready. Also what we are doing here can be done<br />
 without any coding at all using the <a href="http://drupal.org/project/cck">CCK module</a>. This is just meant to show you actual Drupal Code

Our module is an address book much like the one you have on your phone. It will require a first name, last name, phone number. These will then be saved in a database in a table called semicontacts that we will create.

We will also create a page to list all these contacts.

Okay less yada yada more code you say? Say no more. So were going to create module folder and the two required files like in part one.

So now I have a folder in sites/all/modules/ called semicontacts(the name of my module):

-sites/all/modules/semicontacts
—semicontacts.info
—semicontacts.module

Let’s start by telling Drupal about our module in the semicontacts.info file:

; $Id$
name = Semicolon Contacts Module
description = Contact Management Module(CMM 😀 )
core = 6.x
package = Semicolon

Now Drupal knows about our module. If you actually go into modules list you will see this new module.

Now lets edit the semicontacts.module file and add a page(tell Drupal the URL for the module), you will remember we do this by using a hook_menu() hook. If you remember in part 1, hooks naming follows nameofmodule_hookname() what does this mean for us?:

<?php
function semicontacts_menu(){
$items = array();
$items['semicontacts'] = array(
'title' => t('Semicontacts - Form'),
'page callback' => 'semicontacts_page',
 'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
 );
 return $items;
}

As you can see the module will be accessible from the http://mysite.com/semicontacts page. The page call-back is the function that will be called when this URL is accessed. So lets go ahead and create that. This page will contain our form. We will use the Form API to create the form.

function semicontacts_page($argument) {
    $content = "Hello World! Wow It really works
    This is a simple module to save contacts to the database.
    Enter Contact Details.";
    $content .= drupal_get_form('semicontacts_form');
    return $content;
}

There’s our function. So we put a little into text at the top and we add a form to that using the drupal_get_form() function, which takes a form function as a parameter. So we need to create this form function that will return our form.

function semicontacts_form(){
 $form = array();
 //create a text field called firstname with label First Name
 $form['firstname'] = array(
 '#type' => 'textfield',
 '#title' => t('First Nmae'),
 );
 //create a text field called lastname with label Last Name
 $form['lastname'] = array(
 '#type' => 'textfield',
 '#title' => t('Last Name'),
 );
 //create a text field called phonenumber with label Phone Number
 $form['phonenumber'] = array(
 '#type' => 'textfield',
 '#title' => t('Phone Number'),
 );
 //create submit button
 $form['submit'] = array(
 '#type' => 'submit',
 '#value' => t('Save Contact'),
 );
 //return the form
 return $form;
}

The Form API makes creating our form simpler and cleaner. The form is declared as an array of arrays(the fields are themselves arrays). By the ways this function can be called whatever you want. As long you fetch the right form in the page function above.

So now we should have a form. Lets try access the page. Go to http://www.my-site.com/semicontacts You should be greeted with a form which at the moment does absolutely nothing. Try click submit. It should just redirect you to the same page.

Okay. Now lets get to the fun stuff. Lets create a table in our database called semiccontacts to store our contacts:

CREATE TABLE `semicontacts`.`helloworld_contacts` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`firstname` VARCHAR( 255 ) NOT NULL ,
`lastname` VARCHAR( 255 ) NOT NULL ,
`phonenumber` VARCHAR( 255 ) NOT NULL
)

Copy sql query above and run in a mysql client(i use phpMyAdmin). Be sure you have already selected your drupal database.

Now we just need to create a function to save our form. We do this by implementing form api’s _submit() function

function semicontacts_form_submit($form_id, &$form_data){
 db_query("INSERT INTO semicontacts (firstname, lastname, phonenumber) VALUES ('%s', '%s', '%s')", $form_data['values']['firstname'], $form_data['values']['lastname'],  $form_data['values']['phonenumber']);
 drupal_set_message(t('Your contact has been saved to the database.'));
}

The data submitted by the form is stored in an array $form_data[‘values’] with field names as keys so to fetch the value of firstname field just $form_data[‘values’][‘firstname’], simple enough right? Yep.

In Drupal 7, We use the db_insert to save our data. The API allows for method chaining, much like RUBY’s active record pattern or Jqeury. Looks better and makes the code easier to follow.

So to save this in drupal 7 we would use:

db_insert('semicontacts')->fields($form_data)->execute();

So now our form should be saving to the database. Try it now. Works? Yes? No? If it doesnt then something went wrong up there. You or i(i hope not) missed something.

Wait don’t we need to validate our form data? We do. Lets. Form validation is done using the form api _validate() function. It means our function will be called semicontacts_validate().

function semicontacts_form_validate($form_id, &$form_data){
 //var_dump($form_value);
 if(!is_string($form_data['values']['firstname'])|| empty($form_data['values']['firstname'])){
 form_set_error('firstname', t("Please Enter a valid First Name"));
 }elseif(!is_string($form_data['values']['lastname']) || empty($form_data['values']['lastname'])){
 form_set_error('lastname', t("Please Enter a valid Last Name"));
 }elseif(!is_numeric($form_data['values']['phonenumber']) || empty($form_data['values']['phonenumber'])){
 form_set_error('phonenumber', t("Please Enter a valid Phone Number"));
 }
}

We used the the form_set_error function to set errors if any for the field we are currently validating. the method takes a fieldname and an error message. You will also notice like the submit function we did not have to call them. This is the beauty of hooks. They’re like that little kid in class with his hand up going “me,me,me, pick me sir”. And the class wont continue until they get picked. I thought that was deep. You should write that down 😉

So hooks break drupal flow of execution cause drupal will first check if there are any relevant hooks in your module before continuing to the next task. So its almost like you modified the Drupal core without really needing to see the core code.

Okay now try submitting without entering any values. You should get the error messages we defined above.

But wait would’nt it be cool if we had a page to list contacts? Lets go ahead and do that.

Remeber that secontacts_menu function up there, the one that defines our modules urls? Yeah that one. Lets add another url there. Say… http://www.my-site.com/semicontacts/list

function semicontacts_menu(){
$items = array();
$items['semicontacts'] = array(
'title' => t('Semicontacts - Form'),
 'page callback' => 'semicontacts_page',
 'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
 );
 $items['semicontacts/list'] = array(
 'title' => t('Semicontacts - List'),
 'page callback' => 'semicontacts_list_page',
 'page arguments' => array("1"),
 'access arguments' => array('access content'),
 'type' => MENU_CALLBACK,
 );
return $items;
}

Easy enough. If at this point you are thinking we need to create the call back function you are indeed correct young grasshoper 😀 Here it is:

function semicontacts_list_page($argument) {
 $content = t("List of contacts<br /><br />");
 $content .= t(l("Add New Contact","semicontacts"));
 $result = db_query('SELECT c.id, c.firstname, c.lastname, c.phonenumber FROM semicontacts c order by
lastname asc');
 $content .= "<ul>";
 while ($contact = db_fetch_object($result)) {
 $content .= "<li>$contact->lastname, $contact->firstname - $contact->phonenumber</li>";// Perform
operations on $node->body, etc. here.
 }
 $content .= "</ul>";
 $content .= l(t("Add New Contact"),"semicontacts");
 return $content;
}

At this point our module is fully functional. But we dont wan’t the end-user to always run a sql command to create the semicontacts table. We need this to happen during module installation. Enter the

semicontacts.install module with the Schema API Create a new file in the semicontacts folder called semicontacts.install with the following contents:

<?php
//$Id
/**
 * Implement hook_schema()
 */
function semicontacts_schema(){
 $schema['semicontacts'] = array(
 'description' => t('Store .'),
 'fields'=>array(
 'id'=>array(
 'type'=>'serial',
 'unsigned'=>TRUE,
 'not null'=>TRUE
 ),
 'firstname'=>array(
 'type'=>'varchar',
 'length'=>255,
 'default' => '',
 'not null'=>TRUE
 ),
 'lastname'=>array(
 'type'=>'varchar',
 'length'=>255,
 'default' => '',
 'not null'=>TRUE
 ),
 'phonenumber'=>array(
 'type'=>'varchar',
 'length'=>255,
 'default' => '',
 'not null'=>TRUE
 ),
 ),
 'primary key'=>array("id"),
 );
 return $schema;
}
/**
 * Implement hook_install()
 */
function semicontacts_install(){
 drupal_install_schema('semicontacts');
}
/**
 * Implement hook_uninstall
 */
function semicontacts_uninstall(){
 drupal_uninstall_schema('semicontacts');
}

This file contains implementation of 3 drupal hooks, hook_install(), hook_uninstall(), hook_schema().

hook_install() is used to run any configuration during install like say creating a database table for our module.

hook_uninstall() is used to run any configuration during install like say deleting a database table for our module.

hook_schema() is where we define our database table. The fields about are clearly self explanatory. Check a full list and description.

To test your new install file you must remove the module from Drupal system table. This makes sure that Drupal will run hook_install() since this only runs the first time a module is installed. To do that I usually just run this query

delete from system where name ="semicontacts"

Not the best way, not even advisable. to execute queries directly on the database, but it should suffice since we are on our localhost.

So now you need to package your module in to a .tar.gz file. If you are on windows download 7zip.
To create the file just right click on the semicontacts folder, go to the 7zip submenu and click on “add to archive”, on the window that pops up,choose “tar” format. you should have a newfile semicontacts.tar. Right click this file and do the same as above but choose gzip as the archive format. Good luck.

Comments welcome positive and negative. I apologise for the lenghth of the tutorial, was just so much stuff to cover. We are going to improve and extend semicontacts in the next tutorial(Part 3)

Download Source Code