Feb 16 2010

48 Essential Drupal Development Tips From Lullabot

Published by Justin at 8:34 am under Drupal,Programming,Security

(See end of post for updates.)

Your correspondent was fortunate enough to recently partake in a four day, hands-on, in-depth Drupal training (agenda: theming, forms API, menu API, module development, jQuery, best practices) conducted by the firm that literally wrote the book on Drupal: Lullabot.

Our excellent instructors were Nate Haug and Karen Stevenson. The training was tremendously helpful and Nate and Karen really know Drupal through to the core. It was a blast getting a deeper look at Drupal and being able to have all of our questions answered.

These tips were collected during training and every effort was made to capture them accurately, but do point out any errors or feedback in the comments (any errors were mine in capture, not Lullabot’s in training).

  1. Output the $body_classes variable in the class attribute of your body tag on your page.tpl.php files to have access to handy classes like “front”, “not_front”, “logged-in”, etc.
  2. The /admin/build/block page is the only admin page that doesn’t use an admin theme so that the block location helper overlays make sense.
  3. The two most common elements left out of your page.tpl.php files are $closure and $tabs.
  4. The basic convention for theming is to copy the file you want to create from the code module folder (like node.tpl.php) and then edit it as needed.
  5. Use placeholder tokens to handle languages that order words differently. Example:
    $variables['submitted'] = t('On @date', array('@date'=>format_date($variables['created'],'custom','F jS')));
  6. Best practice: For major Drupal upgrades the best practice is to remove and recreate any theme functions you overrode, so that you can include any code changes that you haven’t captured.
  7. If you don’t want to use $content in node.tpl.php files, then don’t. Just output each field individually.
  8. Performance: Use the row style “Fields” rather than “Nodes” on views. The “Nodes” mode does a node_load() call on each node in your view which costs more than 50 queries per node. “Fields” mode just grabs the data you need.
  9. The dsm() dpm() function is like the insanely helpful pr() function in CakePHP, offering nice output of complex data for debugging. You need devel installed and enabled to use it.
  10. Group all custom modules under the same “package” name in the module list to make it clear to future developers where all of the custom modules are.
  11. Use the coder module to identify Drupal 6 to 7 api changes.
  12. Best practice/convention: The variable name user means the logged in user to the site at the moment of code execution; the variable name account means information about some user on the site, unrelated to the logged in user.
  13. Performance: The entire variable table is loaded on every page load, so limit what you store in variables.
  14. Performance: The variable_get() function is free (no hit to the database) as all variable information is stored in memory.
  15. Don’t call t() on menu title and descriptions as they are stored in the cache on build, meaning that the language used at the point of cache creation gets set for everyone.
  16. Use the MENU_LOCAL_TASK in the hook_menu array to add a tab to a page, like node or user.
  17. Performance: Move large menu item callback functions to inc files, using the ‘file’ attribute of the menu array. This results in more efficient memory management as the entire .module file is loaded on every page load, but inc files are only loaded when specifically needed.
  18. Using %user and %node in your hook_menu array will cause Drupal to automatically call node_load or user_load on the passed in IDs. Nate called these magic handlers. (Clarification: these are load functions in menu.inc)
  19. You can use your own magic handler by using “%” in the hook_menu array. Example: %yourown will cause Drupal to run yourown_load(). These functions must reside in main .module file, not in inc files.
  20. In your module, you can use $GLOBALS['conf']['cache'] = false to turn off caching for a page. (Note: If your page is currently cached you won’t see caching disabled until you clear the cache or it expires.) This appears to stop your entire site cache, not just the page.
  21. A handy way to determine if a site is Drupal is to look for a page expire date in the headers of 11/19/1978 (the birth date of the creator of Drupal).
  22. Doing routes in Drupal: custom_url_rewrite_inbound() and custom_url_rewrite_outbound().
  23. Choosing the right date field type in CCK: Date (iso date), good for historical and imprecise dates , like year only; Datestamp (Unix epoch), same format as Drupal core, better to use Datetime as it offers the same precision; Datetime stores in native format of a date at database tier (you can then do date manipulation functions on it at the database level which is fast).
  24. Best practice: Split your sites/all/modules folder into contrib and custom.
  25. Best practice: If you have to modify a contributed module for a project, track the change in a patch file and create a folder in your site to store all of you individual patch files. Each time you update a contributed module with a newer release verify the issue still needs to be patched manually (reapply the patch you have) or that the patch is now part of the official module (delete your patch file). This way your patches can be stored in version control, easily submitted to the module maintainer and reapplied in the future when you apply important Drupal security patches to your site.
  26. Best practice: hook_menu() should be the first function in your custom module because it serves as an index for the module, describing what it does and where.
  27. The reason the keys in form arrays start with a pound sign is to allow the nesting of form elements in the array.
  28. The “clicked_button” attribute is added to the form state by Drupal to handle images used as submit buttons because Internet Explorer doesn’t use the submit button’s name like other browsers (and like a regular submit button is handled by all browsers).
  29. You set the error on a nested field by using the format parent][child. Example: "home][street", where home is the parent form, and street is the field.
  30. form_error() is cleaner and more logically formatted than form_set_error(), but they do the same thing.
    form_set_error('home][street','You must enter the street address.');
    form_error($form['home']['city'], ‘You must enter the street address.’);
  31. If anything is placed in $form_state['storage'] Drupal will ignore any redirects specified and rebuild the form on the submit. To avoid this you must unset $form_storage.
  32. Best practice: You can use any HTML you want in a theme function because a theme is empowered to override the HTML in it's own theme function.
  33. Drupal will render any remaining parts of $form that you haven't rendered already, so it's not necessary to manually render every part, only the ones you want to handle individually.
  34. Use db_set_active() to switch between the database connections you have specified in your settings file on-the-fly in a routine to easily go outside of the core Drupal database for external content or data. (Switch back quickly as later parts of the page will need access to the core Drupal database.)
  35. The Table Wizard module lets you expose any database table to Drupal views (you can even define keys to allow for joins).
  36. If you specify a set value ("#value") on a form element (like hidden), the Drupal handler will set the form element back to that value regardless of what comes in from the user.
  37. A form type ("#type") of "value" is never sent to the user but kept in the form data so you can access the data in other functions in your module. This appears to be deprecated in Drupal 6. Use $form['#foo'] instead.
  38. HTML tip: Inline JavaScript stops the browser from loading anything after it (HTML, JavaScript, etc) until the browser finishes loading the code.
  39. JQuery tip: VisualjQuery.com is a handy visual API reference for JQuery
  40. Firebug tip: The ">>>" at the bottom of the console tab in Firebug lets you run JavaScript you type in.
  41. HTML tip: Some browsers will strip out A tags if they don't have an href attribute specified.
  42. JQuery performance: Specifying a tag name when looking for a class is much faster than just looking for the class as browsers have built-in support for getElementysByTagName().
    Fast: $('.content');
    Faster: $('div.content');
  43. JQuery performance: Using $(this) inside of selector functions is faster than using the selector again.
  44. An easy way to manage your views is to export them and install them in a custom module. This way you can keep the code in version control, protect them from user (you don't need to even install the Views UI) and you can always revert in the Views UI if changes are applied by a user accidentally to the view.
  45. Managing major patches: Create an empty module and use hook_update() to push major site configuration changes (settings, etc). This way you can update the module code then run update.php and install the patch in Drupal.
  46. When to sanitize: Generally speaking data at the template layer tends to be safe, anywhere else (or higher) isn't. Use check_plain (no tags) or check_markup (runs through default input filter of site).
  47. You can create a Drush profile drush_make script that you can feed into a drush command to build a clean install of Drupal with all of the current versions of modules downloaded live from Drupal.org.
  48. Use cache_get() and cache_set() as much as possible as serving data from the cache does not require a reduces database hits.

Thanks again to Nate and Karen for the great training!

Thanks to the commentators that helped with these refinements:

  • Refined #47. Thanks Boris!
  • Refined #48. Thanks zzolo!
  • Refined #9. Thanks abaddon!
  • Refined #31. Thanks zserno!
  • Refined #18, 20, 34, 37 and 27. Thanks chx!

25 responses so far

25 Responses to “48 Essential Drupal Development Tips From Lullabot”

  1. Jeff Robbinson 16 Feb 2010 at 5:42 pm

    Glad you enjoyed the class, Justin. Nate and Karen had a great time with you guys.

    If anyone else reading this would like Lullabot to come do training at their company or institution, we’re always happy to do it. You can find more info at http://www.lullabot.com/on-site-drupal-training.

  2. Nick Lewison 16 Feb 2010 at 7:54 pm

    You (or they) missed what i’d consider the bigtime jQuery tips:

    1. Always use an ID to select unless you really aren’t sure what you are selecting. $(‘#block-menu-1′) is ludicrously faster [we are talking 100x faster i believe if not more] than (‘div.block’), which is somewhat faster than $(‘.block’). $(‘#sidebar div.block’) is a good workaround sometimes.

    2. CACHE what you select when you can’t chain it.
    BAD:
    $(‘div#wombat’).hide();
    $(‘div#wombat’).remove();
    BETTER
    var wombat = $(‘div#wombat’);
    wombat.hide();
    wombat.remove():
    BEST
    $(‘div#wombat’)
    .hide()
    .remove();

    3. You can pass variables in firebug using console.log(); for example:
    var lemmeSee = $(‘#wombat’);
    console.log(lemmeSee)

    Hope these help. :-D

  3. Nick Lewison 16 Feb 2010 at 8:08 pm

    LOL, I just learned i said something really stupid. Never use the tag with an ID. (‘#wombat’) is much faster then $(‘div#wombat’) because it uses a native javascript function getElementNameById. If you prepend a div to it it uses another function which searches first for “div” then checks for the id. The reason you use div.wombat is because its much faster to search first for a div then a class then it is just for a class. So the speed is #id is faster than div which is faster than class.

  4. Boris Gordonon 16 Feb 2010 at 8:23 pm

    In tip 47 you mention a Drush “profile script”. Do you mean a drush_make make file?

  5. zzoloon 17 Feb 2010 at 2:21 am

    Minor detail on tip 48, cache_get() and cache_set() do use the database by default, but you make far less database calls and prcocessing storing things in cache. Great list!

  6. Justinon 17 Feb 2010 at 7:06 am

    @Nick – Great jQuery tips!

  7. wojthaon 17 Feb 2010 at 8:09 am

    Hi Justin, very nice summary.

    Could you give me permission to translate these tips and publish them on the Czech Drupal community site (http://drupal.cz)? With appropriate credits for sure!

    Thank you in advance,
    Vojta

  8. wojthaon 17 Feb 2010 at 8:21 am

    Another useful Devel debug functions:
    dpm() – print object in message

    dargs($once = TRUE) – print arguments of the current function, set $once to TRUE if you want to print it only once

    dd() – this print object into /tmp/drupal_debug.txt – useful i.e. when debugging Ajax requests from Flash player – it has different session ID. You can use also watchdog(‘debug’, print_r($object, TRUE)) to write debug in the DB.

    dfb() – print debug output into the firebug (FirePHP must be installed inside Devel)

    What variables are accessible in template TIP:

    In template you can quickly see what variables you have defined there by running dpm(get_defined_vars()). Running Theme Developer only for this is sooo expensive.

    Cheers Vojta

  9. Justinon 17 Feb 2010 at 9:12 am

    Hi Vojta,

    Thanks. Sure thing, you can translate and re-use. Credit and link to this post would be great.

    jpe

  10. Justinon 17 Feb 2010 at 9:12 am

    Great tips! Thanks,
    jpe

  11. Davidon 17 Feb 2010 at 10:09 am

    Best thing I’ve read relating to Drupal development in a long time.

  12. wojthaon 17 Feb 2010 at 3:40 pm

    Few more tips:

    You can easy create DB schema array to your module by defining tables in your favourite database tool like phpMyAdmin and than export the tables using Schema module. Now you can just copy the array to the hook_schema in your module.

    You can export your CCK types using CCK Content Copy module (incl. in CCK pack) and place them in the install hook of your module.

    Using module Devel and its PHP execute block/page, you can easily test pieces of the code in the Drupal environment and print results using rich output functions like dpm() … this is useful for very fast debug or for testing hypothesis, or just for testing snippets later placed inside page and bloks.

    Vojta

  13. Damien McKennaon 17 Feb 2010 at 5:40 pm

    Great collection of tips. The lullabots are a great gang, had the pleasure of personal training from Nate and Angie in 2008 at Bonnier Corporation in Orlando, FL.

  14. abaddonon 24 Feb 2010 at 9:21 pm

    regarding tip #9, dpm is recommended over deprecated dsm http://drupal.org/node/188906 :-) but its a nice way to detect old school zen masters

  15. Justinon 25 Feb 2010 at 7:13 am

    @abaddonon – Cool, thanks! Updated list.

  16. [...] Source : http://www.missingfeatures.com/2010/02/16/48-essential-drupal-development-tips-from-lullabot/ [...]

  17. [...] Drupal cette semaine, voici un lien vers une traduction d’un article de Justin 48 Essential Drupal Development Tips From Lullabot These tips were collected during training and every effort was made to capture them accurately, but [...]

  18. zsernoon 18 May 2010 at 1:22 am

    Great write up!

    One small observation: in 31. are you sure it’s $form_storage?
    I think it should be $form['storage'].

  19. zsernoon 18 May 2010 at 1:23 am

    Correction to my previous comment: it should be $form_state['storage']

  20. Justinon 18 May 2010 at 8:35 pm

    @zsernoon – You are right, sorry about that. Thanks for the note!

  21. zsernoon 19 May 2010 at 6:23 am

    Added a translation to the Hungarian Drupal handbook. Thanks again! http://drupal.hu/48-fejlesztoi-tipp-lullabottol

  22. chxon 19 May 2010 at 11:49 pm

    Nate called these magic handlers. <= He might but these are called just load functions in menu.inc. There is nothing magical about them, same as _submit or _validate for forms.

    In your module, you can use $GLOBALS['conf']['cache'] = false to turn off caching for a page. <= If you want this for development reasons, then in settings.php add $conf['cache_inc'] = './includes/cache-install.inc'; this nulls every cache operation and so breaks multistep forms but otherwise it's great.

    To avoid this you must unset $form_storage. <= $form_state['storage']

    Use db_set_active() to switch between the database connections you have specified in your settings file on-the-fly in a routine to easily go outside of the core Drupal database for external content or data. <= and switch back ASAP as watchdog and session writes and so on will not be happy when the table they want to write is not there.

    A form type ("#type") of "value" is never sent to the user but kept in the form data so you can access the data in other functions in your module. <= this is largely deprecated in Drupal 6, just use $form['#foo']. Earlier $form was not passed around so you needed the values in $form_values (which is now $form_state['values']).

    Managing major patches: Create an empty module and use hook_update() to push major site configuration changes (settings, etc). <= nothing to do with patches. Good tip nonetheless.

    The reason the keys in form arrays start with a pound sign is to allow the nesting of forms in the array. <= NO! It allows nesting of form _elements_. Nesting forms is a very complicated matter.

  23. Mr Calsaireon 10 Jun 2010 at 7:05 am

    Bien intéressant tous ces ptits tips !

  24. Darwin Betancourton 13 Jul 2010 at 7:31 am

    Great post.
    Very interesting.

    I’d like to translate to spanish this article into overview for the Latin America Community.

    Thanks again.

  25. Justinon 13 Jul 2010 at 8:26 am

    Sure, that’s fine. Please link back to here at the top of the article.

Trackback URI | Comments RSS

Leave a Reply