Tuesday 27 September 2011

mvc widgets

how did my controllers get so messy?

Some of the code that I've been working with is suffering from two problems that I imagine are endemic amongst web applications built upon MVC frameworks: controllers bloated by complex logic and controller logic that has crept in to templates.

Clearly as programmers we should strive for reusability, but the two most widely known and practised forms of MVC code reuse are globally accessible helper functions and partial views. Sadly neither of these approaches allow appropriate reuse of controller logic. If you add controller logic to your partial you're combining the view and controller aspects of your program and if you use a global helper for anything more than simple formatting then you're no longer writing MVC code.

isn't there a better way?

In order to tackle this problem I've been experimenting with the idea of using composite controllers that can be created and used within any other controller.

With a conventional controller, typically a router or front controller script would instantiate a controller and pass it information about the user's request, such as their post or get variables. The action method of the controller would then be called and this would either render the output of the page or return enough information to the caller for the code in charge of the controller to be able to render the page. Our composite controller works a bit differently.

In a simple example, consider a LoginWidget that uses a model that provides authorization services and a template consisting of a login form and some javascript includes. You could initialise this widget within a controller action for any page and render it at a place determined within the view loaded by the host controller.

The hosting controller needs to instantiate the widget in its action. In its most simplistic form this means binding it to a variable that the view can make reference to later and passing configuration values to the widget.

// Create a login widget. It will access our request and response
// objects
$this->loginWidget = new LoginWidget($this);
// We might want to set up a form prefix so that our form fields
// don't conflict with others
$this->loginWidget->setFormPrefix('login');
// We act as the router for this controller. The action name could
// be anything, in this case checkLogin(), etc.
$this->postAction(); 

The widget implements controller actions just as if it was a normal controller. A bit of extra care needs to be taken to ensure that it wont accidentally read the form fields of other controllers on the page. A prefix works fine for this.

function postAction()
{
 // map fields from the login form using the prefix
 $this->model = new AuthorizationForm();
 $this->model->mapFields(
  $this->request->getPost(), 
  $this->getFormPrefix()
 );



 // ensure it's this widget we're actually submitting
 if($this->getPostPrefixed('submit')) {
  // try to use a service to log us in and pass any 
  // validation errors to the view
  try {
   $service = new AuthorizationService();
   $service->authorize($model);
  } catch (ValidationException $ex) {
   $this->validation = $ex;
  } 

  // if there were no validation errors we have 
  // logged in. don't show any text
  if(!$this->validation) {
   $this->setTemplate(null);
  }
 }
}

Inside your template you can then do this somewhere in the markup:

<?php $loginWidget->render(); ?>

This is pretty straightforward and means that any page which needs login functionality doesn't need to explicitly implement this behaviour.

wow this could change the course of human history

This is no new idea. In fact, reusable widgets have long been a part of desktop application development and lively communities of independent widget makers once sprung up as part of the software ecosystems of major operating systems. It seems to just be the case that web developers haven't quite caught on yet, or don't mind being frustrated.

What's interesting is that major web MVC frameworks already have this concept it's just nobody I know uses it:

  • Zend Framework has Action Helpers
  • Symfony has Custom Widgets
  • ASP.NET MVC has Component Controllers