session-based PHP-multistep-form with mvc-pattern

Dome of the Rock on Temple-Mount (Jerusalem), by Christoph Burmeister (own photo)

Dome of the Rock on Temple-Mount (Jerusalem), by Christoph Burmeister (own photo)

Let’s have a look at some basics. I just learn to start (real) coding with php and this should be one of the central points within web-applications. The model-view-controller-pattern is not a new invention and I think everybody knows the value of a wizard-like multi-step-form, where you can navigate next and previous and don’t looses your insertions. One of the most disgusting things I see while daily browsing is a form, that gives you a confirmation-overview, you see a type, get back and all the previously entered fields are empty. Maybe those web-applications were built in less time… and less quality!

So let’s start to make it better.

The directory-structure:

|   index.php
|
+---controllers
|       Controller.php
|       DashboardController.php
|       RegistrationController.php
|
+---models
|       DashboardModel.php
|       RegistrationModel.php
|
\---views
        dashboardView.php
        registrationView1.php
        registrationView2.php
        registrationView3.php
        registrationView4.php

First the heart: The Controller

The Controller-class is responsible for the interaction between user and model and model and view. So it takes the user-request (which is basically the desired document and a bunch of $_GET- and $_POST-params) and decides, what view the user want to see. Then it gets all information from the model(s) to create this view.
For my simple registration-example, the Controller looks like this:

<?php
class Controller {
	private $view;
	private $step;

	function __construct(){
		//
	}

	function view( $file_name, $data = null ){
		if( is_array($data) ) {
			extract($data);
		}
		include 'views/' . $file_name;
	}
}
?>

From this Controller we get the more specific DashboardController and the RegistrationController:

<?php 
require_once './controllers/Controller.php'; 
require_once './controllers/RegistrationController.php'; 
require_once './models/DashboardModel.php'; 

class DashboardController extends Controller { 	
	public $dashboardModel; 	 	

	function __construct(){ 		
		parent::__construct(); 		
		$this->dashboardModel = new DashboardModel();

		if (!isset($_GET['view'])){
			$this->view = 'dashboard';
		} else {
			$this->view = $_GET['view'];
		}

		switch ($this->view){
			case 'dashboard' :
				$this->createDashboard();
				break;
			case 'registration' :
				new RegistrationController();
				break;
		}
   	}

   	function createDashboard(){
        	$data = $this->dashboardModel->getDashboardInfo();
        	$this->view('dashboardView.php', $data);
   	}
}
?>

So you see, the Controller-class is just a parent, that encapsulates some members/functions that are used by its children. The main-communication will go via DashboardController, that is instanciated in index.php:

<?php 
require 'controllers/DashboardController.php'; 
session_start(); 
new DashboardController();  
?>

So basically, the DashboardController now gets a request from the user’s client with no view-parameter in $_GET. As a router, it will „route the request“ to createDashboard-function which gets the information it needs from the DashboardModel:

<?php 
class DashboardModel { 	
	public function getDashboardInfo() { 		
		return array( 			
			'first' => 'Christoph',
			'last'  => 'Burmeister'
		);
	}
}

This is simply a data-holder. Later it will get the information from DAO, that gets the desired first and lastname from underlying relational database. And that’s the secret of MVC: all is modular-hidden and you can change behaviour by changing the components without affecting others. For now, it works like this.

Lets resume: The user requests for index.php, which instantiates a DashboardController, there is no view-parameter, so the DashboardController calls the createDashboard-function, that uses the DashboardModel to collect needed information. With these information and the desired view, the DashboardController calls the function „view“ of Controller-class, that extracts the $data-array and includes the dashboardView.php:

<html>
	<body>
		<h1>Hello <!--?php echo $first . ' ' . $last; ?></h1>
 		<a href="index.php">Dashboard</a>
		<a href="index.php?view=registration">Registration</a>
	</body>
</html>

So it’s very easy for frontend-developers to write their views without knowing whats happening in the backend. And its easy to mock, while the backend isn’t yet ready. They only have to know the param-names to use (That’s what documentation is for 🙂 )

That was the easy part, let’s try sth. more interesting: the multistep-behaviour. As you can see in DashboardController, if there comes a $_GET-param view with the value „registration“, another Controller, the RegistrationController is instantiated:

<?php 
require_once './controllers/Controller.php';
require_once './models/RegistrationModel.php';  

class RegistrationController extends Controller { 	
	public $registrationModel; 	

	function __construct(){ 		
		parent::__construct(); 		
		$this->registrationModel = new RegistrationModel();

		if (!isset($_GET['step'])){
			$this->step = '1';
		} else {
			$this->step = $_GET['step'];
		}

		switch ($this->step){
			case '1' :
				$this->createRegistrationStep1();
				break;
			case '2' :
				if (isset($_POST['name'])) $_SESSION['registration.name'] = $_POST['name'];
				if (isset($_POST['fname'])) $_SESSION['registration.fname'] = $_POST['fname'];
				$this->createRegistrationStep2();
				break;
			case '3' :
				if (isset($_POST['age'])) $_SESSION['registration.age'] = $_POST['age'];
				$this->createRegistrationStep3();
				break;
			case '4' :
				$this->createRegistrationStep4();
				unset($_SESSION['registration.name']);
				unset($_SESSION['registration.fname']);
				unset($_SESSION['registration.age']);
				break;
		}
   	}

   	function createRegistrationStep1(){
      		$data = $this->registrationModel->getRegistrationInfo();
      		$this->view('registrationView1.php', $data);
   	}

   	function createRegistrationStep2(){
      		$data = $this->registrationModel->getRegistrationInfo();
      		$this->view('registrationView2.php', $data);
   	}

   	function createRegistrationStep3(){
      		$data = $this->registrationModel->getRegistrationInfo();
      		$this->view('registrationView3.php', $data);
   	}

   	function createRegistrationStep4(){
		// do sth. with the registration-data
      		$data = $this->registrationModel->getRegistrationInfo();
     		$this->view('registrationView4.php', $data);
   	}
}
?>

It makes use of the RegistrationModel :

<?php 
   class RegistrationModel { 	
	public function getRegistrationInfo(){ 		
		(!isset($_SESSION['registration.name'])) ? $name = '' : $name = $_SESSION['registration.name']; 		(!isset($_SESSION['registration.fname'])) ? $fname = '' : $fname = $_SESSION['registration.fname']; 		(!isset($_SESSION['registration.age'])) ? $age = '' : $age = $_SESSION['registration.age']; 	
		return array( 			
			'registration_name' => $name,
			'registration_fname' => $fname,
			'registration_age' => $age
		);
	}
}
?>

This RegistrationController is similiar to the DashboardController, but it will look into the request searching for a $_GET-param called „step“. If there is no step, it assumes, it must be step 1.

Now here’s the order of steps:
1) enter name, fname
2) enter age
3) confirm
4) sent-message

the idea is to be able to get back from 3 to 1, 2 to 1 and so on. The last step is simply the result-page, that all was successfully.
The registration-views are straight-forwarded html-forms:

<html>
	<body>
		<h1>Registration-View 1</h1>
		<a href="index.php">Dashboard</a>
		<form action="index.php?view=registration&step=2" method="POST">
			Name :<input type="text" name="name" value="<?php echo $registration_name ?>" />
			Fname:<input type="text" name="fname" value="<?php echo $registration_fname ?>" />
			<input type="submit" value="step 2" />
		</form>
	</body>
</html>
<html>
	<body>
		<h1>Registration-View 2</h1>
 		<a href="index.php">Dashboard</a>
		<form action="index.php?view=registration&step=3" method="POST">
			Age :<input type="text" name="age" value="<?php echo $registration_age ?>" />
			<input type="submit" value="step 3" />
 			<input onclick="window.location='index.php?view=registration&step=1' " type="button" value="back" />
 		</form>
	</body>
</html>
<html>
	<body>
		<h1>Registration-View 3</h1>
 		<a href="index.php">Dashboard</a></pre>
		<form action="index.php?view=registration&step=4" method="POST">
			Name :<?php echo $registration_name  ?>
			Fname :<?php echo $registration_fname  ?>
			Age :<?php echo $registration_age ?>
			<input type="submit" value="confirm" />
		 	<input onclick="window.location='index.php?view=registration&step=2' " type="button" value="back" />
		 </form>
	</body>
</html>
<html>
	<body>
		<h1>Registration-View 4</h1>
 		Congratulations, you confirmed!
		<a href="index.php">Dashboard</a>
		<a href="index.php?view=registration">Registration</a>
	</body>
</html>

All over, the magic is to store your post-variables in the session, after they will are submitted. Then the model will prepare them to be used by the frontend-developer.
What’s missing? The validation-failure-handling. Client-side-validation is not a way at all! So basically there is only on place:The RegistrationController has to check the values before he gets the $_POST-values into the session. That keeps the session clean and he can call the step with the failure again.

I know there are frameworks like Zend, CakePHP, CodeIgniter, Symfony and many more. But to understand the principles, I still think, taking a complete framework is the wrong way. That’s the reason for breaking my head when writing it myself 🙂 It keeps you thinking. Of course, later on in production-mode, the framework of your choice will help you to complete you work in less time and (obviously) better quality.

Sources: