Drupal

Now, for some nitty-gritty techniques.

  • Using Divide-And-Conquer
    • Example: Binary search (of modules) by disabling modules.  (demonstration)
    • Example: Binary search (of history) by going back in time in the code tree (demonstration)
    • Example: Binary search (of code execution) by step-debugging over significant sections of the site and then zeroing in on the section where the problem occurs.
  • Getting more information: Instrumentation: Many times, you just don't have enough information to understand what a problem consists of, not to mention what's causing it. In this situation you need to instrument the system to coax it to tell you what you need to know.
    • Devel module.
    • Trace module
    • Add your own on-the-fly instrumentation so you get the information you need. To do this sanely you have to be working on a clone of the site you're debugging, and to do it well, it should be under source control, so you can quickly and safely revert your hacks. Most of these are especially valuable in places like hooks, where the code executes thousands of times with no problems, but then displays a disaster on execution 1001. Step-debugging with conditional breakpoints can be used well, but sometimes you just want to find out quickly.  Some quick hacks:
      • Webchick's Quick and Dirty Debugging: Hack drupal_set_message() in bootstrap.inc to get a stack trace, so you can see where the problem comes from. For example, you just see a nasty error about a database issue, but no context: Show the stack trace and you'll suddenly understand.
        Add:
        if ($type == 'error') {
           $message .= '<pre>'. print_r(debug_backtrace(), TRUE) .'</pre>';
        }
        to the top of drupal_set_message() and you'll get a backtrace along with the message. The same exact technique works with watchdog - add to the message in watchdog() in includes/bootstrap.inc.
      • Debugging hook_cron: Hook_cron can be quite mischievous, because it's not intended to provide user output. A misbehaving module can cause it to croak without warning, and weeks later you find that cron hasn't been running and you don't know why. In this case we can add instrumentation right into module_invoke_all() in includes/module.inc:
        Add into the for loop just below $function =
        if ($hook == 'cron') { watchdog('debugging', "CRON: Calling $function"); }
        Every invocation of hook_cron then gets logged into watchdog(). The last one you see is the problem.
      • Debugging the infamous " Cannot use object of type stdClass as array" message:
        Right before the moment of disaster, insert something like:
          if (!is_array($x)) {     print '<pre>' . print_r(debug_backtrace(), TRUE) . '</pre>';  }
      • Debugging Drupal batch operations: This can be the very best technique for figuring out what's going on with batch operations. In fact, the only two ways I know of to get visibility into batch operations are to use a step debugger (Eclipse works wonderfully) or to add watchdog() calls to your code. In order to avoid being overwhelmed by the quantity, try making your watchdog calls conditional on the type of event you're working on.
  • Repetitive testing and refinement where the database is altered: There are a number of situations (mostly upgrade-related) where the activity you're debugging is actually changing the database, so to binary search and zero in on the problem, you have to start over again and again. If this means settings up an entire upgrade environment over and over it can make you tear your hair out. So try this:
    1. Get the test set up (prepare for an upgrade, for example). Get it set right to the point where if you pushed the button your error would be demonstrated.
    2. Dump the database at this point.
    3. Run the test. Use whatever technique you're using to zero in on the error.
    4. Load the dumped database. Rinse and repeat.
  • Modules
    • devel module is all about instrumentation. It lets you see database queries and how long they're taking, tells you how long it took to render a page, and a thousand other indispensable things. Not recommended on a live site.
    • trace module will trace hooks and their execution. This is for people who understand what hooks are.
    • drush is not really a module. It allows you to rapidly and easily change the configuration of your system. Need to install devel? drush dl devel; drush enable devel. Sound pretty easy?
    • smtp is the way you test mail sending when you're not on the production site. Many people need it to do mail testing. Install it and point it at your gmail account and you can send mail all you want.  Also check out reroute_email.
  • IDE Debuggers

    Debugging Drupal Presentation posted

    My presentation for Paris Drupalcon is posted: randyfay.com/debugging_drupal.

    Drupalcon sessions accepted for Drupalcon SF 2010

    [This material was developed for a presentation at Drupalcamp Colorado in June of 2010. If you find any problems with it or want to suggest any improvements, send me an email or catch me on IRC (rfay).]

    All of us know about "Web 2.0" and "AJAX", or "Asynchronous Javascript and XML". The basic idea behind AJAX is that a Javascript program running in the browser communicates (usually via HTTP and XML) with the webserver and gets information that it uses to do a task or update the page. Several Drupal modules use AJAX, and it's widely deployed on the web. It normally requires you to write and maintain some pretty good Javascript along with your PHP, and it's a powerful way to add interactive features to a website. Drupal's excellent contrib module Fivestar, for example, uses XML to allow you to vote without a page load.

    Built into Drupal, though, is a no-visible-Javascript technique for doing AJAX form updates. It uses Javascript under the hood, but you never see it. Everything the programmer does is in PHP. And in Drupal 7 it's pretty darned easy.

    Continue reading the entire presentation.

    Topics: 

    AJAX forms in Drupal are provided by the Form API.

    • You write the form using the Drupal Form API.
    • You specify what element activates the AJAX behavior.
    • You specify what part of the form's HTML is to be replaced by the callback.
    • You write a callback that selects and returns the piece of the form which gets replaced.
    • Drupal manages everything so that when the button or control is used, the callback gets called and the replacement of HTML is done.

    Although Javascript is involved (supplied by Drupal) you don't write any and you don't see any.

    Topics: 
    • Book module (core) (Rebuilds the form so you can select an appropriate book and parent page)
    • Poll module (core) (Click a submit button and it adds another question)
    • Examples module (Drupal 7) provides a number of AJAX examples in the "AJAX Example module".
    • Quicktabs module (Configuration options change with the type of tab)
    Topics: 

    It's pretty easy to forget all the details of the Drupal Forms API, so we'll stop for a minute for a very short review.

    (This one is at ahah_demo/simple_form.)

    You add a form to your page like this:

    <?php
    $output
    .= drupal_get_form('ahah_demo_simplest_form');
    ?>

    Often that's done right from the hook_menu implementation.

    The layout of a form is as below:

    <?php
    /**
     * A Hello-world for Forms API (FAPI)
     */
    function ajax_demo_simplest_form($form, &$form_state) {

     
    $form['explanation'] = array(
       
    '#type' => 'markup',
       
    '#value' => '<div>' . t('This is a basic form with just a bit of information') . '</div>',
      );

     
    $form['experience'] = array(
       
    '#type' => 'select',
       
    '#title' => t('What is your experience level in Drupal?'),
       
    '#options' => array(
         
    'expert' => t('Expert'),
         
    'journeyman' => t('Journeyman'),
         
    'beginner' => t('Beginner')),
      );
     
    $form['more'] = array(
       
    '#type' => 'textfield',
       
    '#title' => t("Say more about yourself"),
       
    '#description' => t('Surely one word can\'t describe you.'),
      );
     
    $form['submit'] = array(
       
    '#type' => 'submit',
       
    '#value' => t('Click Me'),
      );

      return
    $form;
    }


    function
    ajax_demo_simplest_form_submit($form, &$form_state) {
     
    drupal_set_message(t('You have claimed to be %experience: %more',
        array(
         
    '%experience'=>$form_state['values']['experience'],
         
    '%more' => $form_state['values']['more'],
        )
      ));
    }
    ?>
    Topics: 

    The essence of AJAX forms is that you mark an element of a form as the one that activates AJAX behavior, and you tell it what section of the HTML is to be replaced when it's activated.

    The form we're using here has a textfield with a description, which is just plain HTML. It has a prefix and suffix that mention its CSS ID. The CSS ID will be used by the javascript running in the background to determine what's to be replaced.

    $form['changethis'] is marked as AJAX-enabled with the "#ajax" marker, which tells the Form API its callback function and the HTML ID (wrapper) to replace.

    When $form['changethis'] is changed, javascript provided by Drupal makes a request to the server, which rebuilds the form and calls the callback, which selects a piece of the form. The resulting form piece is rendered and returned to the page, and is replaced by the javascript that made the original call.

    (This one is live at http://d7.drupalexamples.info/examples/ajax_example/simplest and the code is on api.drupal.org at http://api.drupal.org/api/function/ajax_example_simplest/7)

    Here's an example of the very simplest AJAX behavior from the Examples module.

    <?php
    /<strong>
     *
    Simple form whose ajax-enabled 'changethis' member causes a text change
     
    * in the description of the 'replace_textfield' member.
     */
    function
    ajax_example_simplest($form, &$form_state) {
     
    $form = array();
     
    $form['changethis'] = array(
       
    '#title' => t("Choose something and explain why"),
       
    '#type' => 'select',
       
    '#options' => array(
         
    'one' => 'one',
         
    'two' => 'two',
         
    'three' => 'three',
        ),
       
    '#ajax' => array(
         
    'callback' => 'ajax_example_simplest_callback',
         
    'wrapper' => 'replace_textfield_div',
         ),
      );

     
    // This entire form element will be replaced with an updated value.
      // However, it has to have the prefix/suffix to work right, as the entire
      // div is replaced.
      // In this example, the description is dynamically updated during form
      // rebuild.

     
    $form['replace_textfield'] = array(
       
    '#type' => 'textfield',
       
    '#title' => t("Why"),
       
    '#prefix' => '<div id="replace_textfield_div">',
       
    '#suffix' => '</div>',
      );

      if (!empty(
    $form_state['values']['changethis'])) {
       
    $form['replace_textfield']['#description'] = t("Say why you chose") .  " '{$form_state['values']['changethis']}'";
      }
      return
    $form;
    }
    /</
    strong>
     *
    Callback for ajax_example_simplest.
     *
     *
    The form item 'replace_textfield' has already been processed and changed
     
    * when the form was submitted and rebuilt during the AJAX call, so now
     
    * we just return the piece of the form that changed.
     */
    function
    ajax_example_simplest_callback($form, $form_state) {
     
    // The form has already been submitted and updated. We can return the replaced
      // item as it is.
     
    return $form['replace_textfield'];
    }
    ?>
    Topics: 

    In this example we use a select control to determine how many checkboxes are generated.

    (This one is live at http://d7.drupalexamples.info/examples/ajax_example/autocheckboxes and the code on api.drupal.org at http://api.drupal.org/api/function/ajax_example_autocheckboxes/7)

    <?php
    /<strong>
     *
    AJAX-enabled select element causes replacement of a set of checkboxes
     
    * based on the selection.
     */
    function
    ajax_example_autocheckboxes($form, &$form_state) {

     
    $default = !empty($form_state['values']['howmany']) ? $form_state['values']['howmany'] : 1;

     
    $form['howmany_select'] = array(
       
    '#title' => t('How many checkboxes do you want?'),
       
    '#type' => 'select',
       
    '#options' => array(1 => 1, 2 => 2, 3 => 3, 4 => 4),
       
    '#default_value' => $default,
       
    '#ajax' => array(
         
    'callback' => 'ajax_example_autocheckboxes_callback',
         
    'wrapper' => 'checkboxes-div',
         
    'method' => 'replace',
         
    'effect' => 'fade',
        ),

      );


     
    $form['checkboxes_fieldset'] = array(
       
    '#title' => t("Generated Checkboxes"),
       
    // The prefix/suffix provide the div that we're replacing, named by
        // #ajax['wrapper'] above.
       
    '#prefix' => '<div id="checkboxes-div">',
       
    '#suffix' => '</div>',
       
    '#type' => 'fieldset',
       
    '#description' => t('This is where we get automatically generated checkboxes'),
      );

     
    $num_checkboxes = !empty($form_state['values']['howmany_select']) ? $form_state['values']['howmany_select'] : 1;
      for (
    $i=1; $i<=$num_checkboxes; $i++) {
       
    $form['checkboxes_fieldset']["checkbox$i"] = array(
         
    '#type' => 'checkbox',
         
    '#title' => "Checkbox $i",
        );
      }

     
    $form['submit'] = array(
       
    '#type' => 'submit',
       
    '#value' => t('Submit'),
      );

      return
    $form;
    }

    /</
    strong>
     *
    Callback element needs only select the portion of the form to be updated.
     *
    Since #ajax['callback'] return can be HTML or a renderable array (or an
     
    * array of commands), we can just return a piece of the form.
     */
    function
    ajax_example_autocheckboxes_callback($form, $form_state) {
      return
    $form['checkboxes_fieldset'];
    }
    ?>
    Topics: 

    Pages

    Subscribe to Drupal
    Drupal theme by Kiwi Themes.