Need some help with your project? Contact me

Multistep forms using the CTools object cache in Drupal 7

In this article I am going to look a bit at using the CTools object cache. What is that you ask? Well, it's quite cool actually. It allows you to store some information into a cache in order to be retrieved and used later. The cool thing about it is that it is dependent on the user's session making it very easy to work with. This means you don't have to worry about users clearing the stored information of another user.

Right off the bat I want to say that I am not an expert in this but after some experimenting with it, I want to share with you what I learned. Doing so, I will create a very simple multi step form. So in the end we'll have the following:

  • A form that collects our name.
  • After submitting this form we'll be redirected to another form which collects our email address.
  • After submitting this form, we'll display the name and email on a custom page.
  • The connection between the forms will be made using the CTools object caching.

How? Read on and find out.

First thing we need to take care of is the ever so common hook_menu() to declare ourselves three paths (the two forms and the final page that displays the submitted information):

function your_module_menu() {
  $items['form1'] = array(
    'title' => 'Form Page 1',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('your_module_form_one'),
    'access arguments' => array('access content'),
    'type' => MENU_NORMAL_ITEM,
  );

  $items['form2'] = array(
    'title' => 'Form Page 2',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('your_module_form_two'),
    'access arguments' => array('access content'),
    'type' => MENU_NORMAL_ITEM,
  );

  $items['form-results'] = array(
    'title' => 'Your results',
    'page callback' => 'your_module_display_results',
    'access arguments' => array('access content'),
    'type' => MENU_NORMAL_ITEM,
  );

  return $items;
}

This is pretty standard. You'll notice that in the interest of good practice I prefix everything with your_module to make sure form element names or stuff do not interfere with other modules on the site.

Our hook_menu() returns three paths. The first two have as callbacks drupal_get_form() which will load the respective forms we will soon create. The third one is a simple path with a function callback that we will create at the end to display the information users submit in the form. So let's see the first form declaration:

function your_module_form_one($form, &$form_state) {
  $form['your_module_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Your name'),
    '#description' => t('What is your name?'),
    '#required' => TRUE,
  );
  $form['your_module_one_submit'] = array(
    '#type' => 'submit',
    '#value' => t('Next'),
  );
  return $form;
}

This form has just two elements. A text field that collects a name and a submit button. If you save and clear your cache, you'll already be able to see this first form if you navigate to yoursite.com/form1. It won't do anything but unless I copied something wrong (or you did), it should render. Let's see the second form:

function your_module_form_two($form, &$form_state) {
  $form['your_module_email'] = array(
    '#type' => 'textfield',
    '#title' => t('Your email address'),
    '#description' => t('Where can we reach you'),
    '#required' => TRUE,
  );
  $form['your_module_two_submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

Similar to the one before except that this one collects the email address and can be reached at the path form2. And again nothing will happen if you submit it. It's now time to write the 2 callback functions that get triggered when these two forms are submitted respectively:

function your_module_form_one_submit($form, &$form_state) {
  $object = (object) array(
    'name' => $form_state['values']['your_module_name']
  );
  ctools_include('object-cache');
  ctools_object_cache_set('submission', 'your_module_form_submission', $object);
  $form_state['redirect'] = 'form2';
}

Drupal is so friggin awesome so to make sure that this function gets called when our first form is submitted is to name it as such: the name of the form function followed by _submit.

So what happens in here? First, we create an object that contains the value of the name field the user submitted. Then we include the CTools object cache (remember you always have to include it if you want to access it - and yes, you need the CTools module installed for this). Next we use the ctools_object_cache_set() function to store our object into the cache. The first parameter is a string that defines what kind of an object you are storing (mainly used to avoid collisions). The second one is the name of the object you are storing and the third is the object itself. Finally, we redirect our user to the path of the second form to collect the email. Let's now see the callback function of that form:

function your_module_form_two_submit($form, &$form_state) {
  ctools_include('object-cache');
  $object = ctools_object_cache_get('submission', 'your_module_form_submission');
  $object->email = $form_state['values']['your_module_email'];
  ctools_object_cache_set('submission', 'your_module_form_submission', $object);
  $form_state['redirect'] = 'form-results';
}

Again we include the CTools object cache so then we can use the ctools_object_cache_get() function to retrieve the cache we stored before (using the same parameters). Then we add to that object the value of the email address we just got submitted and store it back into the cache before redirecting the user to the form-results path. Let's now declare our final function - the callback for this third path (your_module_display_results):

function your_module_display_results() {
  ctools_include('object-cache');
  $object = ctools_object_cache_get('submission', 'your_module_form_submission');
  ctools_object_cache_clear('submission', 'your_module_form_submission');
  if ($object) {
    return array(
      '#markup' => 'Your name: ' . $object->name . '<br />Your email: ' . $object->email,
    );
  }
  else {
    drupal_goto('form1');
  }
}

After we retrieve the object in the normal fashion, we can go ahead and clear it from the cache using ctools_object_cache_clear(). No point in keeping it there (you can keep it if you need it, I just wanted to show you that you can also do that). Then depending on whether or not there is an object containing information, we render the name and email. If not, we redirect the user to the first form. So basically once you get to this page, you can see your information but if you refresh, you get redirected to the first form to start the process again.

So that's pretty much it. I did a bit of testing and there don't seem to be any collissions between users - due to this session_id() CTools uses to manage this process. So that's pretty cool. But also the documentation on the object cache is very scarce on the internet so if you know a bit about it, please drop a line below and tell us what more we can do with it. Cheers.

Comments

I think there are so many places we can use this. I usually end using other means which could have been easily done this way.

I like what you have put together here and always looking for new ways of integrating modules like ctools and making life easier. Is there any benefit of using the ctools cache over the core modules/methods suggested in the Drupal Examples module (using the $form_state array)? I saw you mentioned the users session being cleared but I didn't think form_state would have that issue either.

Hey there,

You have to use the $form_state param because that is where the form values are stored. But you cannot use just that to pass them so easily to another form...or wherever else you might need those values for that matter.

The multistep form was just an example of how you might use the object cache from ctools.

D

Very useful. Thanks!

How can i pass the variable from a form to another form ?
Thx in advance

you can use these lines as mentioned in the tutorial.

ctools_include('object-cache');
$object = ctools_object_cache_get('submission', 'your_module_form_submission');
$object = ctools_object_cache_get('submission', 'your_module_form_submission', $object);

Thank You

Hi, It's there any way to get like a list of all the cache in drupal, to see for example how many users tried to fill the form, but actually didn't finish it?

Add new comment

You can post comments in Markdown and basic HTML tags.
For code blocks, wrap your code within '~~~'. For example:
~~~
$var = 'my variable';
~~~