Version 6, last updated by JohnBell at 23 Mar 23:16 UTC
Server-side input validation
Normally, when you are writing a component based on Nooku, you do all your validation client-side, using Javascript. However, sometimes, you also need to perform server-side data validation before committing the user's input to the database. This can cater for cases where some of your users might have disabled Javascript execution on their browser, or if your validation rules are too complex to effectively model in Javascript (e.g. checking some input against rows from a complex database query). As you'd expect, this is not just possible with Nooku, it's almost enjoyable.
First things first: What we want to achieve is validating our user's input against a set of validation rules. If the data is valid, we continue our normal execution flow, e.g. redirect the user to the previous view. If however the data is invalid, we need to return the user to the editor page, show him the reason his data is not valid, and – most importantly – not lose all of his modifications so far. The last part is of paramount importance. Would you like to have to retype a long article if you just forgot to use a category? I didn't think so, either!
We can easily implement this advanced behaviour using command chains. For the sake of our example, we'll suppose you're developing a component named com_foobar. Our command chain code lives in the administrator/components/com_foobar/commands/validate.php and looks like this:
class ComFoobarCommandValidate extends KCommand
{
public function _controllerBeforeSave(KCommandContext $context)
{
$identifier = (string)$context->caller->getIdentifier();
$model = $context->caller->getModel();
if(method_exists($model, 'validate'))
{
$data = $context->data;
$validationErrors = $model->validate($data);
if(!empty($validationErrors))
{
$tempdata = $data;
unset($tempdata['_token']);
KRequest::set('session.'.$identifier, serialize((array)$tempdata->getIterator()) );
$referrer = KRequest::referrer();
$query = $referrer->getQuery(true);
$query['id'] = $model->getState()->id;
$referrer->setQuery($query);
$context->caller
->setRedirect((string)$referrer, implode('<br/>',$validationErrors), 'error' );
return false;
}
}
return true;
}
public function _controllerBeforeApply(KCommandContext $context)
{
return $this->_controllerBeforeSave($context);
}
public function _controllerBeforeRead(KCommandContext $context)
{
$identifier = (string)$context->caller->getIdentifier();
$tempdata = KRequest::get('session.'.$identifier,'raw');
if(!empty($tempdata))
{
$tempdata = unserialize($tempdata);
if($tempdata !== false) {
$identifier = (string)$context->caller->getIdentifier();
$model = $context->caller->getModel();
$model->getItem()->setData($tempdata, false);
}
KRequest::set('session.'.$identifier,null);
}
return true;
}
}
Let's see how it works. The controllerBeforeSave and controllerBeforeApply methods are called, as you'd expect, before the controller's save and apply methods fire. First thing they do, they check for the existence of a validate() method on the model and run it. This method is passed our data object (what the user submitted) and is supposed to return an array of validation error strings. If the data is valid, the array should be empty. If there are validation strings, the data object is serialized and “memorized” as a session variable, then sets the redirection to the page which initiated this action. Since in this case it returns false, it “breaks” the command chain and the data is never stored in the database and the user is redirected to the editor page. Cool! We're halfway there. Now, how do we tell Nooku to use our stored data instead of the database data when it gets back to the editor view?
The _controllerBeforeRead method is called, of course, right before the controller's read method fires, which means that it is called exactly before the controller instructs the model to fetch the data to display to the editor. If there are serialized data in the session, it will unserialize them and force them into the model (actually, the row item retrieved by the model's getItem() method). This will allow the editor view to use the memorized data instead of those retrieved from the database. The method also cleans up after itself, removing the memorized data from the session, to avoid any strange artifacts when you'll try editing another row later.
Now that we have the command, let's attach it to the controllers we want to equip with server-side validation. There are two ways:
- Load the command in our dispatcher.
- Create a base class named ComFoobarControllerValidation which extends ComDefaultControllerView and loads this action, then extend all our controller classes from it.
- Add the command on each controller individually.
The first approach is architecturally incorrect, as it will not load the validation logic if we use the MVC triad in a HMVC (Hierarchical Model-View-Controller) context. It also means that you attach the validation command to all controllers, which a. may not be what you want and b. if you don't use it, it will just slow down the execution of your component without any valid reason. When developing remember: do not shoot yourself on the foot!
The second approach is also not very sound. Modern OOP (object-oriented programming) looks down on inheritance and prefers composition. To oversimplify this, if you create a base class you are crippling your code because a. you have to load the ancestor class every time you want to use the child class, and b. this works well if you have only one command, but doesn't scale very well if you want to add multiple commands on a single controller (think of having to add pre-deletion validation).
The third method is, ultimately, the best approach from an architectural perspective, as it overcomes the problems of the other two approaches. The only downside is that you have to write a simple class for every controller implementing the validation. It's very easy, though:
class ComFoobarControllerLollipop extends ComDefaultControllerView
{
public function __construct(KConfig $config)
{
parent::__construct($config);
$command = KService::get('com://admin/foobar.command.validate');
$this->getCommandChain()->enqueue($command);
}
}
As you can see, doing this sort of advanced operation using Nooku is almost a child's play, thanks to its robust architecture and clever use of design patterns. We wrote, like, 50 lines of code? Hardly!
That said, I won't claim to have reinvented the wheel. This tutorial sprung out of necessity and is just a quick solution I hacked together with the great guidance of the Nooku community. All the code is GPL. You can readily reuse it on your own components or even improve it. Feel free to modify and share back if you discover any issues or come up with a cool improvement.