Difference between #4 and #3 of An easy way to display a success page using flash messages

unchanged
Title
An easy way to display a success page using flash messages
unchanged
Category
Tips
unchanged
Tags
flash message, Forms
changed
Content
We often need to display a success page after the user has submitted a form
without problem. We may show some welcome message after a user registers a new
account. There are many ways to implement this workflow. In this article, I will
explain a method that exploits flash messages.

As we know, a flash message is a message that is available on the current and
the next page request. Yii provides [CWebUser::getFlash()] and
[CWebUser::setFlash()] to support this feature.

In order to implement a success page, we can have the following action code.

~~~
[php]
public function actionRegister()
{
	// display the success page if the register form was previously
	// submitted successfully
	if (Yii::app()->user->hasFlash('register.success'))
	{
		$this->render('registerSuccess',
Yii::app()->user->getFlash('register.success'));
		return;
	}
	
	$model = new User('register');
	if (isset($_POST['User']))
	{
		$model->attributes = $_POST['User'];
		if ($model->save())
		{
			Yii::app()->user->setFlash('register.success', array(
				'username' => $model->username,
				'email' => $model->email,
			));
			$this->refresh();
		}
	}
	$this->render('register', array('model'=>$model));
}
~~~

We also need to create two views named `register.php` and `registerSuccess.php`,
respectively. I will not give more details about these two views since they are
very straightforward: one is to display the register form, the other is to
display the success message.

Let me explain a bit more about the action code. 

In the register action, we first check if there is a flash message named
`register.success`. If so, it means the user has just finished registration and
we should display a success page to him. We render the `registerSuccess` view
with the `register.success` flash message. As I will explain shortly, the
`register.success` flash message is an array, instead of a string. As a result,
all data in this array will be available in the `registerSuccess` view. In this
example, we will be able to use `$username` and `$email` about the new user.

Next, we see a typical model create/update action code. When the model is
successfully saved, we set the flash messages with an array (NOT a string). The
array contains the data that will be used to render in the success page. We call
[CController::refresh()] to refresh the current page, which will discard the
post data and prevent the user from resubmitting the data by
reloading/refreshing the page.

So what are new stuff here? 

* First, instead of saving a string as flash message, we save an array of data
that will persist to the next page request.

* Second, we use the action code to control which view to render. The code
conforms better to the MVC pattern.

> Note: Flash messages are essentially stored as session data. As a result,
they will be serialized when saving and deserialized when loading. For this
reason, the data saved as flash message should be simple data, such as strings,
numbers. Do not store objects (e.g. data model objects) or resource handles in
it because their serialization and deserialization may cause problem.


## Refactoring the code

If an application needs to display different success pages in different actions,
we may refactor the above code so that the usage is even simpler and more DRY.

First in the [base controller
class](http://www.yiiframework.com/wiki/121/extending-common-classes-to-allow-better-customization),
define the following method:

~~~
[php]
/**
 * Renders a success view.
 * Note that this method will redirect the browser to the 'site/success' route
 * which will then render the specified success view.
 * @param string $view the view name. It can be a view relative to
 * the current controller or an absolute view (starting with '/')
 * @param array $data the data to be passed to the view.
 */
public function success($view, $data=array())
{
	if($view[0]!=='/')  // relative to current controller
		$view = '/' . $this->id . '/' . $view;
	$data['_view_'] = $view;
	Yii::app()->user->setFlash('_success_', $data);
	$this->redirect(array('site/success'));
}
~~~

Then, in the `SiteController` class, define the following `success` action:

~~~
[php]
/**
 * Displays a success page.
 * The success page content is specified via the flash data '_success_'
 * which is generated by {@link Controller::success()} method.
 * If the flash data does not exist, it will redirect the browser to the
homepage.
 */
public function actionSuccess()
{
	if (!Yii::app()->user->hasFlash('_success_'))
		$this->redirect(Yii::app()->homeUrl);
	$data = Yii::app()->user->getFlash('_success_');
	$view = $data['_view_'];
	unset($data['_view_']);
	$this->render($view, $data);
}
~~~

Now, the previous `actionRegister()` method can be greatly simplified:

~~~
[php]
public function actionRegister()
{
	$model = new User('register');
	if (isset($_POST['User']))
	{
		$model->attributes = $_POST['User'];
		if ($model->save())
		{
			$this->success('registerSuccess', array(
				'username' => $model->username,
				'email' => $model->email,
			));
		}
	}
	$this->render('register', array('model'=>$model));
}
~~~

And we can call the `success()` method in any other action as long as it needs
to display a success page.