PhpRiot
Become Zend Certified

Prepare for the ZCE exam using our quizzes (web or iPad/iPhone). More info...


When you're ready get 7.5% off your exam voucher using voucher CJQNOV23 at the Zend Store

Creating Multi-Step Forms And Wizards In PHP

ZervWizard Class

I have developed a class that I’ve used in several projects, specifically for managing custom multi-step forms (or wizards, whatever you want to call them). This is a generic class that deals with the issues covered on the previous page. That is, storing of data, managing jumping between steps and various other useful aspects.

The full class is available from the right-hand column of this article (under "Additional Files"). This code is free under the terms of the Apache License, which basically means you can use it as you please.

Here I will go over the general concepts for creating your own wizards. Then on the page we’ll create a full example of a custom wizard, and then on the following page we’ll create the full example including HTML forms and processing the form. The example we’ll create will be for accepting a user’s shipping and billing details to fulfill a product order.

General Concepts

The ZervWizard class is fairly straightforward to use. As it is a generic handler, it does not deal with the actual creation of the HTML markup or JavaScript like HTML_Quickform tries to do. There’s really only a few important things you need to know when developing custom wizards.

Step 1: Extending the class

When creating your custom class, you need to extend the ZervWizard class. You then call the constructor, passing in the container where form data is stored (usually $_SESSION), and also a unique name for your wizard (this is so if you have several wizards, the data will be stored in separate places within the container). Usually I just use the built in macro __CLASS__ as this will generally give it a unique name.

Note that if you are using $_SESSION for your container (and I’m almost certain you will be), you must ensure you have already called session_start() somewhere in your code.

Listing 2 listing-2.php
<?php
    require_once('ZervWizard.class.php');
 
    class MyWizard extends ZervWizard
    {
        function MyWizard()
        {
            session_start();
            parent::ZervWizard($_SESSION, __CLASS__);
 
            // ... other code ...
        }
 
        // ... other code ...
    }
?>

Step 2: Creating the steps

Next, we define the steps that make up the form. This is achieved using the addStep() method. This method takes two arguments. The first is an internal name for the step, and is used when creating the various callbacks (see below). The second is the step title, which is string describing the step. This is useful to use as the header on the corresponding form page.

The order of the steps in your form is defined by the order in which you call the addStep() method.

Listing 3 listing-3.php
<?php
    require_once('ZervWizard.class.php');
 
    class MyWizard extends ZervWizard
    {
        function MyWizard()
        {
            session_start();
            parent::ZervWizard($_SESSION, __CLASS__);
 
            // create the form steps in the order in which they should be processed
 
            $this->addStep('userdetails', 'Enter your shipping details');
            $this->addStep('billingdetails', 'Enter your billing details');
            $this->addStep('confirm', 'Confirm your details');
        }
 
        // ... other code ...
    }
?>

Step 3: “Prepare” methods

For every step that is created, there may be a “prepare” callback. The purpose of this callback is to fetch all necessary data for rendering that step of the form.

Suppose for example that in a step for accepting a user’s shipping details, they had to select their country. In the prepare callback for that, you would probably generate the list of countries from a database and return them so they can be used within a HTML select field.

The prepare callback is optional, because it’s rather pointless having a prepare step if there’s no data that needs preparing.

Prepare callbacks have the name prepare_stepname(). So for the userdetails step created above, the prepare callback would be called prepare_userdetails(). The callback accepts no arguments and need not return any data. Any data prepared that you will need to use you should just assign a property of your class.

Here’s an example of creating the list of countries:

Listing 4 listing-4.php
<?php
    require_once('ZervWizard.class.php');
 
    class MyWizard extends ZervWizard
    {
        function MyWizard()
        {
            // ... other code ...
 
            $this->addStep('userdetails', 'Enter your shipping details');
 
            // ... other code ...
        }
 
        function prepare_userdetails()
        {
            $this->countries = array('au' => 'Australia',
                                     'de' => 'Germany'
                                     'fr' => 'France',
                                     'uk' => 'United Kingdom',
                                     'us' => 'United States');
        }
    }
?>

So you know that when you’re outputting the form for the userdetails step, that the countries data will be available from the wizard class. You’ll see this in further use in the full example.

Step 4: “Process” methods

Every step created must have a process method. While the prepare method was optional for each step, the form can’t progress unless there’s a process method that returns true for the corresponding step.

The process method should only return true if the form data was valid. For example, if the user must enter a valid email address but does not, then this should set an error (using the the addError() method), and the process method should return false. The easiest way to do this is to use the isError() method to generate the return value.

If a the process method returns false, then the user will be shown the same step once again. Assuming you have created code to display the generated errors, then the user will see these errors.

The process method has similar naming technique to the prepare method. That is, process_stepname(). So going back to the userdetails step, the process method would be called process_userdetails().

The process method accepts the raw form data as an argument.

And one more important step — once you’ve validate a value, you must manually set it to the container using setValue(). This method takes two arguments: the value name and the value. You’ll see it in the example just below.

Here’s an example of some simple processing of the user details step:

Listing 5 listing-5.php
<?php
    require_once('ZervWizard.class.php');
 
    class MyWizard extends ZervWizard
    {
        function MyWizard()
        {
            // ... other code ...
 
            $this->addStep('userdetails', 'Enter your shipping details');
 
            // ... other code ...
        }
 
        function process_userdetails(&$form)
        {
            // initialize the form data we're processing
            $country = $this->coalesce($form['country'], '');
            $email = $this->coalesce($form['email'], '');
 
            // call the prepare method so we can then access the countries list.
            // it is only automatically called when displaying that step, so we
            // manually need to call it here.
            $this->prepare_userdetails();
 
            if (array_key_exists($country, $this->countries))
                $this->setValue('country', $country);
            else
                $this->addError('country', 'Please select your country');
 
            if (isValidEmail($email))
                $this->setValue('email', $email);
            else
                $this->addError('email', 'Please enter a valid email address');
 
            return !$this->isError();
        }
    }
?>

For the sake of the above example we just assume there’s a method called isValidEmail() which returns true if passed an email address in a valid form.

We also used the ZervWizard coalesce() method, which is useful for initializing form values. Basically, you pass it the form value as the first argument and a default value as the second argument, so if the form value doesn’t exist, the default value will be return and no PHP warning is generated (i.e. the undefined index warning).

Step 5: Form complete callback

Once the final step has been completed, it is possible to then run some extra code. This is basically the code that does something with all the data submitted. For example, you may write the data to a database, send the user an email, or process a credit card transaction.

Here we also introduce the getValue() method. This method takes the value name as an argument and returns the corresponding value. Additionally, you can pass a second argument which is the value to return if the specified value doesn’t exist in the container.

You may end up using the getValue() method in prepare or process callbacks too, as values selected earlier may have some bearing on how you process future steps or prepare data for future steps.

Anyway, back to the form completion callback. This is just a method called completeCallback(). Just define this in your class with whatever code you need executed and it will automatically be run when the final step is complete.

Listing 6 listing-6.php
<?php
    require_once('ZervWizard.class.php');
 
    class MyWizard extends ZervWizard
    {
        function MyWizard()
        {
            // ... other code ...
 
            $this->addStep('userdetails', 'Enter your shipping details');
 
            // ... other code ...
        }
 
        function completeCallback()
        {
            $creditCard = $this->getValue('credit_card');
            $creditExpiry = $this->getValue('credit_card');
 
            processCreditCard($creditCard, $creditExpiry);
            sendUserConfirmationEmail($this->getValue('email'));
        }
    }
?>

Obviously the methods here are fictional.

Once the final step is complete, the wizard is complete. As such, completeCallback() is only ever called once: when the final step’s process method returns true.

Listing of methods

  • addStep([step name], [step title]) – Adds a step to the wizard
  • coalesce([value], [default value]) – Initialize a value or set it to the default value
  • setValue([key], [value]) – Save a value in the wizard container
  • getValue([key], [default]) – Get a value from the wizard container, or return the default value (optional)
  • addError([key], [error message]) – Add an error to the container
  • isError() – Check if an error has occurred
  • clearContainer() – Empty out all values in the container, including saved values and errors
  • getStepProperty() – Get information about the current step. Currently the only step is title, but this may be extended in the future to store other properties.

In This Article


Additional Files