The fat controller and its misuse, part 1

Luke John Steadman
6 min readDec 20, 2016

--

I’ve been working with PHP And MVC for some time now, and when I first started I never really thought of where domain logic should lye, thus ending up with 100’s of lines of code within my controllers.

The common mistake found within controllers is that domain logic is usually implemented within them (and sometimes business logic).

I’m going to take a really basic example and show how easy it is to become confused on what domain logic actually is, what a controller might look like, and how you can separate the C part of your MVC application.

Let’s start off by discussing some of the terminology which is frequently used.

MVC

Most would class this as a ‘framework’ or ‘pattern’ of how code might be laid out. MVC stands for Model, View and Controller. I won’t go into much depth about these, especially Model and View, more so than controller.

Most modern (PHP) frameworks are MVC driven, some use MVP (Model View Presenter), which differs from View logic, but I won’t discuss that. Most of the big boys in PHP, to name a few; Laravel, Symfony, Zend, CodeIgniter, FuelPHP, CakePHP etc; are all MVC based. They provide all three components as part of their kitchen sink.

Model

Models house the business data, meaning you should write logic here, that interacts with persistent storage. Example, fetching and saving a User record

There are multiple patterns which can be adhered to when writing models. Most of the frameworks I’ve used employ ActiveRecord. Others include DataMapper / Repository.

View

Views are the presentation layer. This doesn’t necessarily mean HTML, but most of the time it is, because that’s what’s usually served over the internet.

Generally, you’ll find (PHP) logic within views. This should always be kept to the bare minimum. Personally, I follow The PHP League: Plates guidelines when it comes to syntax.

Note: This doesn’t apply when writing Twig templates.

Controller

Controllers are the glue that pulls the business data and the presentation together. The bit in between. This potentially means that layer becomes complex when additional logic is required outside the realm of both business data and presentation.

Example, sending an email doesn’t relate to your business data and shouldn’t be housed along with models. i.e. Business logic / data doesn’t need to know how to send an email.

Nowadays, sending an email within an application would usually be stored within the domain, which the controller uses. This action, referred to by most, would be called a service, and the service would be bound to something that knows how to bootstrap itself, usually a service locator or (hopefully) something that’s injected using Dependency Injection (DI).

Domain

I’d class domain logic where your non-business logic remains. Actions that are performed outside of your data. Domain logic is a level up, meaning it may interact with your business logic at any point, but it should live here. Services are consumed within the domain.

I tend to not rely on my services knowing about the state of business logic within the domain. That way, I can pass objects through the service without worrying about the state of the service will end up in.

It’ll simply take what’s required from the object, parse, and pass something back to suggest whether the service was successful or not, usually a boolean or an exception if something failed that we might be able to recover from.

Service

A service is an action which is housed within the domain. A domain can house many services. Usually speaking, each service is self contained and doesn’t depend or rely on anything around it. It shouldn’t really know too much about the domain, thus making it decoupled from the application.

Services can be found within a Service Locator, which some class as an anti-pattern, mainly because of the global, monolithic state it becomes. DI is preferred here. A good example of DI is Auryn.

For example, an email service will most likely depend on a to/from email address, subject and body.

Fat Controller

Let’s take a look at an example which displays a “fat controller”.

Note: none of the code below is specific to a framework and merely demonstrates how to remove domain logic from a controller-like class.

<?phpnamespace App\Controllers;class NewsletterController extends AbstractController
{
public function postSignup()
{
// Fetch the email address from $_POST array
$email = isset($_POST['email']) ? $_POST['email'] : false;
// Validate
if($email && filter_var($email, FILTER_VALIDATE_EMAIL)) {
mail($email, 'Welcome to Foo', 'You have signed up');
}
header('Location: '/');
}
}

As you can see, this is simple controller that sets $email to the $_POST[‘email’] value, attempts to validate, and if all is well, tries to send an email. Easy.

Let’s split that up and digest a little:

  1. Set $email t0 either $_POST[‘email’] if set, else set to false.
  2. Validate the email if $email isn’t false(ish) (using filter_var)
  3. Attempt to send the email
  4. Redirect the user back

The problem with this controller is 2 fold:

  1. Hard to test and assert each part of the process
  2. Duplication is most likely to occur because the logic cannot be reused.

Refactor

OO suggests single responsibility, paving the way for less headaches, better testable code, easier to manage etc.

Let’s start off by creating an Email service that does one thing, send an email. The service needs some information:

  • An email address
  • Subject
  • Message content

We’ll assume we want to house validation within the service, so we’ll couple an EmailValidator to the Email service. We’ll use setter injection so at any point, we can override the validation object, if necessary.

<?phpnamespace App\Domain\Service;use App\Domain\Validation\Email as EmailValidator;class Email
{
/**
* @var string
*/
protected $to;
/**
* @var string
*/
protected $message;
/**
* @var string
*/
protected $subject;
/**
* @var App\Domain\Validator
*/
protected $validator;
public function __construct($to, $message, $subject = null)
{
$this->to = $to;
$this->message = $message;
$this->subject = $subject;
$this->validator = new EmailValidator($to);
}
public function setValidator(App\Domain\Validator $validator)
{
$this->validator = $validator;
}
public function send()
{
if($this->validator->validate()) {
return mail($this->to, $this->subject, $this->message);
}
}
}

The validation will be stored within the domain as well. We’re going to use PHP’s filter_var function and pass the FILTER_VALIDATE_EMAIL constant, saving us to write copious amount of regular expression which may or may not support certain email addresses.

<?phpnamespace App\Domain\Validation;use App\Domain\Validator;class Email implements Validator
{
/**
* @var string
*/
protected $email;
public function __construct($email)
{
$this->email = $email;
}
public function validate()
{
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
}

Next, redirection. Our controller would redirect the user back to a certain path once attempting to send the email had completed. This works fine but logic within the controller redirecting wouldn’t aid extensibility. For example, if the request was made over XHR then we’d need to handle that outside of this domain.

Instead, if we create an object that suggests we need to redirect, the domain can handle that if a XHR request is found, and instead, return a JSON object.

Our Redirect class within the domain takes a path. Once the go method is invoked, a header is set to suggest the new location.

<?phpnamespace App\Domain;class Redirect
{
/**
* @var $path
*/
protected $path;
public function __construct($path)
{
$this->path = $path;
}
public function go()
{
header('Location: ' . (string) $path);
}
}

Putting the refactored code together shows that we now have a leaner approach and the controller no longer houses domain logic. We’ve achieved the following by splitting the code up into separate parts, and more importantly, a service. Let’s summarise.

  1. Reusable Email service which can be used anywhere within the domain.
  2. Easier code to test due to DI.
  3. Better support for extensibility. (e.g. XHR requests)

The service can be swapped out at any point so long as the params are adhered to.

Because the controller returns an instance of Redirect, we can easily catch this during the request and discard should we need to (i.e. during test or detecting an XHR request).

<?phpnamespace App\Controllers;class NewsletterController extends AbstractController
{
public function postSignup()
{
$service = new App\Domain\Service\Email(
$_POST['email'],
'Welcome',
'You have signed up'
);
$service->send(); return new App\Domain\Redirect('/');
}

That’s all for now. This post has shown us splitting up a fat controller. In the next part, I’ll discuss testing the service and how to use DI correctly within controllers.

--

--

Luke John Steadman

Lead Software Engineer. Avid fan of AVFC and The Killers. High desire to see the world.