Drupal 6 for Developers

From Training Material
Jump to: navigation, search

Drupal 6 for Developers Training Material

Introduction - Why You Should not Develop with Drupal

When you start developing with Drupal for the first time, you can get quite irritated. Especially when you are used to dealing with well documented platforms and environments like Oracle or Visual Studio .NET. When you don't know something, you simply try to find it in the documentation. If you can't find something in the documentation, you don't bother and you try to find a way around the problem.

This is not the case with Drupal. Documentation is scarce, if it exists at all, and mostly inaccurate.


The easiest way to know how to use Drupal to its full potential is simply to read the code. This is painful and time consuming.


A lot of people simply hack a module or write their own.


But before you start tinkering with the code, maybe you should think twice. It isn't a problem to write thousands of lines of code. The problem is to find a solution to a problem, not to write code. As developers we tend to think that we are paid for writing code. But in my experience in business and as a project manager it has taught me something quite different. Programmers should be paid for solving problems in general, not writing code.

We can always argue that the job of the project manager is to be split into various tasks so that programmers don't have to think about the big picture. But how is possible for project managers to split the tasks if they don't know programming well? So ironically, there is a huge gap between the project manager and the developers. We can argue about who is responsible for what, or maybe we can even hire a system architect.

Years spent working on software projects has taught me one thing: try to find and learn existing solutions before you write a single line of code.

In one of the companies I used to work for as a project manager we hung sheets of paper all over the office showing the order we should follow when starting to solve a problem. It looked like this:

  1. Check http://drupal.org for existing solutions
  2. Check the company's repository
  3. Ask your colleagues!!
  4. Check the Drupal CVS repository
  5. Check Google
  6. If you don't want to do this task, ask if anyone else wants to do it
  7. Create a sub-module to extend the functionality of an existing module
  8. Create a patch to modify an existing module
  9. Write a module from scratch

Of course a developer should only write any code as a last resort! So, should we hire developers or rather people with good presentation skills who are willing to learn? Of course the latter seems to be more reasonable, but not necessarily.

Even if a person only has to assess the quality of the code, export it from the repository, apply a patch or write a very small piece of code, they still have to be able to understand the code, they have to read the code, find the problem or assess whether it is worth spending a few seconds more on this problem. To deal with successful Drupal projects we definitely need broad thinkers who are also able to understand and sometimes write a couple of lines of code.

Another thing which can irritate you in Drupal 6 is its procedural oriented architecture. I used to spend hours with objects and simply can't think in any different ways now. Writing a good procedural code is a much harder job than writing good object oriented code. Some people argue whether Drupal complies to some object oriented standards. You can find a must-read article about it at http://api.drupal.org/api/file/developer/topics/oop.html/6. But to be honest, I love object oriented programming and have a real dilemma when I have to give away my object oriented thinking and deal with Drupal 6. It was painful, I even wanted to give up the project and move to some more "serious" job with Java or C#.

One thing which made me decide to play with Drupal for some more time is: I don't have to write any code. Everything that I want is there! All I have to do is to learn it! Drupal is written in quite a general way. You have nodes but you can create your own content types like invoices, order forms, etc. So instead of writing code, you actually spend time on configuring Drupal. Is it worth it? Of course, writing any code requires testing and bug fixing. Even if you can write required code quite quickly, you DO NOT know how much time you are going to spend testing and bug fixing. From my experience, you always spend much more time on analysing, design, testing and bug fixing than writing code itself.

So, to summarise, Drupal sucks, Drupal is poorly written, not object oriented, but it is there, tried and tested and waiting for you to use it.

Stop developing with Drupal, start to use it.

Analysis, Design and Documentation

Do not try to create any documentation! It is a waste of time. Documentation post mortems, especially, are highly dangerous and unhealthy.

But do analysis. When you have a problem try to describe it, write use cases, scenarios,UML, pictures, schemas, charts, tables, crayons, paints or whatever may make the problem more understandable to the rest of the team, yourself, and of course the person who - by definition - knows little about requirements: the client.

When you have information about what the client actually wants, you can think about design and implementation. This means you can choose some modules or write your own, etc. The use cases and other documents, like activity diagrams (work flows), class diagrams, state diagrams, etc. should constitute documentation. Don't try to synchronize it with real applications which, of course, will be totally different. It is a waste of time.

Environment and Deployment

PHP is a dynamic language. It means it requires a hell of a lot less hustling with compilers, variable declarations, types, etc.

You simply write what you want and it may work or not. The downside of that is of course much worse performance and a lot more errors.

Good IDE (Integrated Development Environment) allows you to avoid most of the errors. Personally, when I see people developing under vim, I feel like during the last 20 years the software industry hasn't moved a bit.

Before you start even installing Drupal, you should think of the whole environment and deploying process.

First of all is to choose a repository. There are actually two solutions available: SVN or CVS (unless you try to use Microsoft solutions like SourceSafe with Visual Studio, which are brilliant unless you try to use them with something which didn't originate from Redmond). The CVS repository is a little bit outdated but it does the work. I can only think of one reason you might use CVS: legacy. If you don't have any code already in CVS, you could use Subversion (SVN) which is much more robust and offers much more functionality. Even if you develop a Drupal application yourself, you should create a repository. Why? Because if you patch something, change any code or even modify configuration files, it is crucial to know what and when it has been changed.

From my experience, putting tests, configuration files and some example database schema into a repository sooner or later pays off.

The second issue is good IDE. There are different solutions. I personally use Eclipse with PHPeclipse and I connect through SVN repository by Subclipse plug-in along with svn+ssh protocol.

There are of course different solutions like http://www.eclipse.org/pdt/, but it is just a matter of taste rather than a strategic decision. It is quite easy to switch from one environment to another unless the new one is much worse.


If you are up and running, import your project and try to deploy it. A good environment and deploying procedure saves you a lot of time and frustration.


If you are not familiar with Subversion, you may be interested in this course: http://www.nobleprog.co.uk/training/subversion-users

Project Managing and Drupal Projects

Most Drupal companies have never heard about project management. When I talk about critical paths, tasks, concurrency, Gant charts, etc. They simply have no idea what I am talking about. Of course it is obvious that in most cases project management is not really necessary. But if you have more than one developer, or if you have some nasty thing called a deadline, then, you should definitely consider doing some project management work. The simplest way is to use a spreadsheet like Ex**l or Open Office Calc. You simply (it is not simple, believe me) split your job into stories and try to estimate the time needed to do it. After that you may multiply this by 4 or 8, and you end up with an underestimated time which you can submit to your client. Your client will tell you that it is too long, then you may say it may be done earlier, and you know it is never going to be done on time. But don't worry, you tried.


At the other extreme, I have seen Drupal projects where heavy methodologies like Price 2 or RUP were in place. Of course they suffocated everything.


In my experience a modified Scrum framework or XP methodology worked perfectly well. We worked with XPlanner and our overall estimations were almost 100% accurate, though single stories or tasks were over- or underestimated significantly. At the end, they even out. If you need more information about it I can recommend a fantastic course.

Writing Your Own Module

Unless you want to hack Drupal modules, you have to put your code somewhere. Actually, you don't have much choice. You can only put it in these places:

  1. Configuration file (yes it works, but don't do it)
  2. Themes (don't put any logic there)
  3. Database (some modules allow you to put a code in the setting pages in the admin area - don't do it)
  4. Your own module

Of course you know that only the last option is - in most cases - the most reasonable solution. If you are unsure whether the problem is caused by your code, you can uninstall your module, and then you can be 100% sure that the problem lies elsewhere.

Even if you need to tweak your application a tiny bit, you have to write your own module.

How to do it?

Sloth is a virtue! Copy any other module and change what you need.

Exercise: Create Own Module

Copy blog module and modify it to your needs.

  1. Name the directory to mymodule.
  2. Rename blog.module to mymodule.module
  3. Remove all content form mymodule.module
  4. Rename blog.info to mymodule.info
  5. Remove rest of the files
  6. Change entries in mymodule.info as shown below
; $Id
name = My Module
description = My first module
package = My Package
version = VERSION
core = 6.x
  1. Install the module.

Drupal and Onion

You have probably heard of layer or tiered architecture. Sometimes you even come across buzz words or acronyms like MVC. Drupal tends to pretend to comply with some of these standards. Drupal is mostly written by incredibly ambitious junior programmers, we can see selects in theme layer, some logic in queries or in phptemplate files, as well as logic functions splitting out HTML straight to the theme function or even on the screen.


The main reason we want to split a program into layers is to make code more universal and easier to read.

The lower layer should not assume what kind of layer there is above. In other words, we are able to change higher layers without changing anything in the lower one.

For example, we are able to change the theme without modifying a Drupal module. In more advanced models, we use the same code for desktop applications as well as for web applications.

If a logic layer assumed that this application is a web application and printed some HTML code (which, for example Drupal does), we would not be able to create a proper desktop application.

The same with the database. A database layer does not assume that the programming language you are going to use is PHP, Java or any other.

We cannot say the opposite. The higher level must know something about the lower level. For example Drupal assumes that a database is MySQL or Postgres. Or, every theme function assumes a certain type of data is "injected" into a template file. Good practice shows, that the less dependence between layers, the easier the application is to maintain.

TODO Upload the picture

Exercise: Where to make the change

Choices:

  • Theme
  • Module
  • Databases

Where would you make changes or write a code in order to achieve the goals below?

  1. A button should be hidden when a user is logged in
  2. Swap the right pane column with the left one
  3. Change an HTML list to an HTML table
  4. Add an additional field to a form
  5. Add additional information to a form only if a user is anonymous
  6. Remove unnecessary descriptions
  7. Display additional information from a different node on the node view page
  8. Hide field labels
  9. Change font size
  10. Add additional validation to a field
  11. Add an additional HTML table with some information

What Would You Call a Person Who Implements Hooks

This hook allows you to modify a node when you view, load, insert or update it. Use Cases

  • Adding your own extension to a node (like additional functionality, so you can add it when the node is inserted and remove it when the node is deleted)
  • Notifying when an operation happens (like sending email when a node has been modified)
  • Counting node views
  • Validation, when you want to add additional constraints


Injecting Extra Stuff to a Node Example

/**
* Implementation of hook_nodeapi().
*/
function mymodule_nodeapi(&$node, $op, $arg = 0) {
    if($node->type == 'page' and $op == 'view'){
       $node->content['extra_stuff']['#value'] = 'Extra Stuff'; 
    } 
}

Changing Form (hook_form_alter)

This hook allows you to modify any form generated by the Drupal core or a module. Use Cases

  1. Hide a field in a form
  2. Change the label of a field
  3. Change the size, type or shape of a field

How to find a form id?

  1. Find the form on the web page
  2. Implement a hook_form_alter and display the form_id
function mymodule_form_alter(&$form, $form_state, $form_id) {
 print_r($form_id);
}

How to display the form structure?

  1. Find the form on the web page
  2. Implement a hook_form_alter and display the form_id
function mymodule_form_alter(&$form, $form_state, $form_id) {
 if($form_id == 'user_login_block'){
  print_r($form);
 }
}

Exercise 1

  1. Show the user login block
  2. Change the label "Username" to "Login name" (use hook_form_alter)

Exercise 2

  1. Add a shortcut key (e.g. "S") to a submit button to every node. (hint: accesskey html attribute)

Creating Pages and Menu Items (hook_menu)

Use Cases

  1. Creating a page (often containing forms)
  2. Creating a menu item
  3. Controlling permission to a page and menu item

Example

 /**
 * Implementation of hook_menu().
 */
 function mymodule_menu() {
   $items['my_first_page1'] = array(
   'title' => t('My First Page 1'),
   'description' => "My Fist Page 1 Description.",
   'page callback' => 'mymodule_my_first_page',
   'access arguments' => array('access content'),
  );
 return $items;
 }

Create two pages and permissions

/**
 * Implementation of hook_menu().
 */
function mymodule_menu() {
 $items['my_first_page1'] = array(
       'title' => t('My First Page 1'),
   'description' => "My Fist Page 1 Description.",
   'page callback' => 'mymodule_my_first_page',
   'access arguments' => array('view my second page'),
   'type' => MENU_SUGGESTED_ITEM
 );
       
 $items['my_second_page'] = array(
       'title' => t('My First Page 2'),
   'description' => "My Fist Page 2 Description.",
   'page callback' => 'mymodule_my_second_page',
   'access arguments' => array('view my second page'),
       'type' => MENU_SUGGESTED_ITEM
 );
   
 
 return $items;
}
function mymodule_my_first_page(){
       return 'First page';
}
function mymodule_my_second_page(){
       return 'Second page';
}
/**
* Implementation of hook_perm().
*/
function mymodule_perm() {
 return array('view my first page','view my second page');
}

Exercises

Exercise 3

  1. Create a page with the text "Hello not so cruel Drupal" and place it under the path "/cruel_drupal" (use hook_menu)

Exercise 4 Add a menu item in the admin menu called "Don't click me" which points to the page created above. Exercise 5

  1. Create a permission called "view hello drupal".
  2. Allow only users with this permisition to view a page and menu item created above.

Node that Down

A node is an abstract concept of data. If you know the object oriented concept, you can think of a node as a top level class. Almost every other content type inherits from the node. It means that whatever functionality works with nodes will also work with any content type you are going to create. So, if you are lazy you should definitely take advantage of the benefits of nodes. For example, if you use nodes instead of your own data type, you don't have to worry about a search mechanism, comments, access controls, workflow, classification or any of the many other things which are already implemented in Drupal.

Because of the history of Drupal, not all things in Drupal are nodes. For example, a user is not a node. Of course you can install a module which creates one node per user (like usernode module) and then you can comment on the user profile, manage profile visibility, etc. But if a user were a node, everything would be simpler.

From many different reasons, comments which have been implemented in the Drupal core are not nodes. If you think that your application may become complicated in the future, it might be a good idea to stop using core comment content type and install nodecomment module instead.

Loading and Saving Nodes

In terms of programming, you can very easily manipulate nodes, no matter how many fields or other things a node contains. For example, you can load which id is 1 like this.

$node = node_load(1);

If you print the result (e.g. print_r($node)) it looks like this:

stdClass Object
(
   [nid] => 1
   [type] => page
   [language] => 
   [uid] => 1
   [status] => 1
   [created] => 1218902492
   [changed] => 1218902492
   [comment] => 0
   [promote] => 0
   [moderate] => 0
   [sticky] => 0
   [tnid] => 0
   [translate] => 0
   [vid] => 1
   [revision_uid] => 1
   [title] => test page
   [body] => test body
   [teaser] => test body
   [log] => 
   [revision_timestamp] => 1218902492
   [format] => 1
   [name] => admin
   [picture] => 
   [data] => a:0:{}
   [last_comment_timestamp] => 1218902492
   [last_comment_name] => 
   [comment_count] => 0
   [taxonomy] => Array()
)

The first thing you may notice is the type of object: stdClass. It is a generic PHP class without any specific type. It is NOT an array!

You can easily modify the node and save it with the node_save statement like this:

$node = node_load(1);
$node->title = "My New Title";
node_save($node);

Finding Nodes

In this case it doesn't really matter whether we pass the node or the node id. All of those parameters are interpreted by the node_load function in exactly the same way:

node_load(array('nid'=>1)); 
node_load(1);

But you can search a node which has a specific title, creation date, type, etc.

$node = node_load(array('title'=>'asdf'));

Of course if there is more than one node with the title "asdf" this statement will return only one node. Drupal does not specify which of the nodes with the same criteria will be return returned.

We can specify as many criteria as we want:

$node = node_load(array('title'=>'asdf', type' => 'page'));

We should read it like this: find a node with a title "asdf" and of type "page".

Extra Topics

  • NodeAPI
  • Using your Content Type in Your Module
  • Saving Nodes with revisions

Forming Forms

In Drupal we can distinguish between different type of forms:

  1. Node submission forms
  2. Other forms which submit content (user registration, comment submission, etc).
  3. Forms which manipulate system variables (most forms in admin section)

Form Creation and Submission

All of them, though, are created and rendered in an almost identical way.

A simple example of a form could look like this:

function mymodule_menu() {
       $items['test_form'] = array (
               'title' => 'Test Form',
               'page callback' => 'drupal_get_form',
       // Name of the function which generates the form
       'page arguments' => array('mymodule_myform_form'),
               'access callback' => 'access_allowed',
       );
       return $items;
}
function access_allowed(){
       return true;
}
function mymodule_myform_form(){
       //Create a text field
       $form['name'] = array(
           '#type' => 'textfield',
           '#title' => t('Your name'),
           '#size' => 20,
           '#maxlength' => 20,
           '#required' => TRUE,
           '#default_value' => 'John Smith',
       );
       //Create a submit button
       $form[] = array(
           '#type' => 'submit',
           '#value' => t('Submit'),
       );
       return $form;
}
function mymodule_myform_form_submit($form, &$form_state){
       $welcome_message = "Hello " . $form['#post']['name'] . '!';
       drupal_set_message($welcome_message);
       // Where sites will be redirected after submission, null = the same page
       //$form_state['redirect'] = 'node/1';
}


D6fd test form.gif


The first function implements the hook_menu. Let's focus on these two lines:

       $items['test_form'] = array (
               'title' => 'Test Form',
               'page callback' => 'drupal_get_form',
       // Name of the function which generates the form
       'page arguments' => array('mymodule_myform_form'),
               'access callback' => 'access_allowed',
       ); 

When users visit ?q=test_form Drupal invokes the 'drupal_get_form' function. The argument passed to this function is simply a function name which generates a form.

The mymodule_myform_form() function generates an array with structures described in this document: http://api.drupal.org/api/file/developer/topics/forms_api_reference.html/6

The last function: mymodule_myform_form_submit processes the form after submission.

function mymodule_myform_form_submit($form, &$form_state){
      $welcome_message = "Hello " . $form['#post']['name'] . '!';
       drupal_set_message($welcome_message);
       // Where sites will be redirected after submission, null = the same page
       //$form_state['redirect'] = 'node/1';
}

It is a kind of hook, since Drupal will search for this function automatically. The proper name for a submission function is a form function name with the "_submit" suffix. Drupal core form functionality will search for this function automatically. The first parameter contains form structure and, in the #post element, submitted form values.

The second parameter contains information about form state. Optionally, you can set redirect field, which contains the path where Drupal will redirect a user after form submission.

Form Validation

For example if we add age field to our form:

function mymodule_myform_form(){
//Create a text field   
 $form['name'] = array(
   '#type' => 'textfield',
   '#title' => t('Your name'),
   '#size' => 20,
   '#maxlength' => 20,
   '#required' => TRUE,
   '#default_value' => 'John Smith',
 );
 
 //New fragment
  $form['age'] = array(
   '#type' => 'textfield',
   '#title' => t('Your age'),
   '#required' => false,
 );
 
//Create a submit button
 $form[] = array(
   '#type' => 'submit',
   '#value' => t('Submit'),
 );
 return $form;
} 

Then, we add validation function. In order to validate a form, we have to create a function with the same name as the function which creates a form and add _validate suffix.

function mymodule_myform_form_validate($form_id, $form_values) {
       if($form_values['values']['age'] < 18){

form_set_error('error', t('You are too young and you would cause some minotirty complext to our staff!'));

       }
}

Related Drupal Documentation:

Exercises

Modify the form above so it should take default vlaue from a paramter in the URL Example:

www.mypage.com/test_form/John%20Mill

should use "John Mill" as a default value in the form.

Validation Forms Generated by Different Modules

function mymodule_form_alter( &$form, $form_state,$form_id) {
  if($form_id == 'user_register'){
    $form['#validate'][] = 'mymodule_validation';
  }
}
function mymodule_validation($form, $form_state){
//print_r($form);
 if(strlen($form['#post']['name']) < 5 ){
   form_set_error('name','Name must be longer than 5 charaters');
 }
}

Abstracting Themeing From Code

/**
* Implementation of hook_menu().
*/
function mymodule_menu() {
 $items['my_first_page1'] = array(
       'title' => t('My First Page 1'),
   'description' => "My Fist Page 1 Description.",
   'page callback' => 'mymodule_my_first_page',
   'access arguments' => array('view my second page'),
   'type' => MENU_SUGGESTED_ITEM
 );
 return $items;
}
function mymodule_my_first_page(){
   $rows = array(
     array(
       'Cell 1', 'Cell 2', 'Cell 3'
     ),
     // Row with attributes on the row and some of its cells.
     array(
       'data' => array('Cell 1', 
               array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky'
     )
   );
       $header = array('column1','column2','column3');
       return theme('table',$header,$rows);
}

Using Variables

  • variable_set
  • variable_get


Caching

  • cache_get
  • cache_get

Admin