8

I've been picking other developers' brains on the concept of "fat models, skinny controllers" after reading:

Most respondents are using what I'd consider fat controllers.

While the topic has come up on Stack Overflow I haven't found thorough description of the method in practice.

I just found an old related question here.

tereško
  • 58,060
  • 25
  • 98
  • 150
mike
  • 1,786
  • 2
  • 19
  • 31
  • 1
    I don't see enough in either of those posts to say he's using fat/skinny controllers/models. Developers have a tendency to create fat controllers over several iterations... they usually don't start out like that and it's not a conscious decision. But in general I think fat models is the way to go. Not sure what kind of answer you're looking for. – Mike B Mar 21 '14 at 18:20
  • @MikeB Thanks. I'm looking for practical solutions / design patterns in building fat models. – mike Mar 21 '14 at 18:23
  • 1
    There's several patterns to help solve the Business Logic (models) to Persistence Layer (database) question. http://martinfowler.com/eaaCatalog/index.html. But they don't really get into fat models skinny controllers. I think it's implied that business logic belongs in the model layer and more often than not they sneak themselves into the controller layer or view. – Mike B Mar 21 '14 at 18:25
  • The main motivation behind the fat models and skinny controllers is that, you should not do any any business logic related coding in your controllers, for example, often developers do validation in controllers but in this case you should do these things in your model and call them from your controllers. It's a broad question and not possible to cover the term in an answer here. You mentioned about `design pattern`, in Laravel the repository pattern is recommended and kind of best practice (includes dependency injection in controllers using abstract type hinting using interfaces). – The Alpha Mar 21 '14 at 18:28
  • 1
    Also you may check [this article](http://heera.it/laravel-repository-pattern). – The Alpha Mar 21 '14 at 18:32

1 Answers1

37

Skinny Controllers

What you are going to see in PHP (vanilla or Laravel or Symfony) more and more are the skinniest controllers ever. This is something you already see in Rails, and people are also starting to call it (with some other practices) hexagonal. One line of code is all you need in your controller, actually they say this should be a goal for all your methods. This is an example with, yes, a little bit more than that, but still skinny:

<?php

class PostController extends Controller {

    private $repository;

    public function __construct(PostRepositoryInterface $repository)
    {
        $this->repository = $repository;
    }

    public function store()
    {
        try 
        {
            $this->repository->create(Input::all());
        }
        catch (ValidationException $e) 
        {
            return Redirect::back()->withInput()->withErrors($e->all());
        }

        return Redirect::route('posts');
    }

}

A controller is a bridge between the HTTP requests, your business logic and your presentation layer. So it should receive one request, send it to an injected object which will process it and redirect to the route (or render a view) responsible for giving feedback to a client (or user). Everything else, including validation, should happen in your repositories, services, models (MVC, yay!), etc.

But we could refactor this controller, in the hexagonal way, to reach the one-line-per-method goal:

<?php

class PostController extends Controller {

    private $repository;

    public function __construct(PostRepositoryInterface $repository)
    {
        $this->repository = $repository;
    }

    public function store()
    {
        return $this->repository->create(Input::all(), $this);
    }

    public function createSucceeded()
    {
        return Redirect::route('posts');
    }

    public function createFailed()
    {
        return Redirect::back()->withInput()->withErrors($e->all());
    }

}

Basically your repository classes will use the own caller ($this) to fire the succeeded and failed methods.

Fat Repositories / Services / Models

Models are too related to your data, sometimes they are your ORM and talk directly to your database server, so, these days you'll see people use repositories and services as layers between them.

Repositories

A repository is a class that, by talking directly to your models, processes and gather the information your application needs. Your application should not be aware of what is necessary to select some information in your database, select, where, order, group by, those are things sometimes only your models should be aware of, so this is a repository:

class PostRepository implements PostRepositoryInterface {

    private $model;

    public function __construct(PostInterface $model)
    {
        $this->model = $model;
    }

    public function create($input)
    {
        return $this->model->create($input);
    }

    public findBySlug($slug)
    {
        return $this->model->where('slug', $slug)->first();
    }

}

Services

Everything that doesn't belongs directly to your business logic, mostly external services, the farthest from your application code, the more decoupled you build them, the better. Creating external packages (Composer packages) for those services are a good way of decoupling them from everything else, and you if you make them framework agnostic you're entitled to receive 10 Sturgeon points. In Laravel you can create services by integrating three kind of classes:

1) Service Class(es): responsible for doing what your service must do, all your service logic goes here.

2) Service Provider: responsible for booting up your service and adding it to Laravel's IoC container so it can be ready to use at any time, but note that Laravel will only instantiate your service classes when your application really use them.

3) Facade: lets you access your service from anywhere in your application using the static (::) syntax:

Mailer::send($user->id, 'Thanks for registering', 'emails.registered');

This the Mailer service:

Service Class

<?php namespace ACR\Services\Mailer;

use Illuminate\Mail\Mailer as IlluminateMailer;
use Sentry;

class Service {

    public function __construct(IlluminateMailer $mailer)
    {
        $this->mailer = $mailer;    
    }

    public function send($userId, $subject, $view, $data = [])
    {
        return $this->mailer->queue($view, $data, function($message) use ($userId, $subject)
        {
            $user = Sentry::findUserById($userId);

            $message->to($user->email, $user->name);

            $message->subject($subject);
        });
    }

}

Service Provider

<?php namespace ACR\Services\Mailer;

use Illuminate\Support\ServiceProvider as  IlluminateServiceProvider;
use ACR\Services\Mailer\Service as Mailer;

class ServiceProvider extends IlluminateServiceProvider {

    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('acr.mailer', function($app) {

            return new Mailer($app->make('mailer'));

        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return array('acr.mailer');
    }

}

Facade

<?php namespace ACR\Services\Mailer;

use Illuminate\Support\Facades\Facade as IlluminateFacade;

class Facade extends IlluminateFacade {

    protected static function getFacadeAccessor() { return 'acr.mailer'; }

}

Models / ORM

Those guys should be highly swappable, today you may be using a Eloquent as your ORM, storing data in a database, but you might need to change it to something else, some foks are storing 100% of their data in Redis, so you better be prepared for a change like this by using an Interface (contract) layer between your ORM and your domain loginc and really develop for your interfaces, not your concrete classes. Taylor Otwell in his book even say that you should completely delete your models folder.

interface PostInterface {

    public function all();

    public function find($id);

}

class DbPost extends Eloquent implements PostInterface {

}

class RedisPost extends Eloquent implements PostInterface {

}

The idea behind this is to swap implementations easily, so in Laravel you can use the IoC container to tell Laravel which implementation you are using:

App::bind('PostInterface', 'DbPost');

So, if you have a Repository is using your PostInterface:

class PostRepository implements PostRepositoryInterface {

    private $model;

    public function __construct(PostInterface $model)
    {
        $this->model = $model;
    }

}

Laravel IoC container will automatically instantiate this repository with an instance of DbPost. And if you ever need to change it to Redis, you just need to change one line:

App::bind('PostInterface', 'RedisPost');

Views / Presenters

The dumbest the awesomer.

Views

Views should be responsible only for displaying information. Views should not be aware of your models, services, repositories, or anything else in your system. Views should be editable by webesigners so, the more code you have on them, the more bugs your non-php-programmer-designer will add to them. Your controller should gather the information from your repositories and pass them to your views:

<?php

class PostController extends Controller {

    private $repository;

    public function __construct(PostRepositoryInterface $repository)
    {
        $this->repository = $repository;
    }

    public function index()
    {
        return View::make('posts.index')->with('posts', $this->repository->getPaginated());
    }

}

And the only responsibility of your view should be show that data:

@extends('layout')

@section('contents')
    <ul>
        @foreach($posts as $post)
            <li>
                {{ $post->title }} - {{ $post->author }} - {{ $post->published_at }}
            </li>
        @endforeach
    </ul>

    {{ $users->links() }}
@stop

Presenters

How do you format your data? You write raw properties in your views, but you should, behind the scenes, be using presenters to, yeah, present your data. Presenters usually use the Decorator Design Pattern to format your data to be presented in your pages. This is an example using Shawn McCool's LaravelAutoPresenter:

<?php namespace App\Presenters;

use McCool\LaravelAutoPresenter\BasePresenter;

class Post extends BasePresenter {

    public function __construct(UserModel $user)
    {
        $this->resource = $user;
    }

    public function author()
    {
        return $this->resource->author->name;
    }

    public function published_at()
    {
        return $this->date($this->resource->created_at);
    }

    public function dateTime($date)
    {
        return \Carbon\Carbon::createFromFormat('d-m-Y', $date, 'Sao_Paulo/Brazil')
                     ->toFormattedDateString();
    }    
}

Related Books

Taylor Otwell's Laravel: From Apprentice To Artisan

Chris Fidao's Implementing Laravel

Antonio Carlos Ribeiro
  • 86,191
  • 22
  • 213
  • 204
  • What's the reasoning behind your first sentence? Sounds like a marketing statement akin to Mustache's Logicless Templates claim. All languages, frameworks, tools, etc are as good as the developer using them. – Mike B Mar 21 '14 at 18:56
  • I was watching for @antonio to respond. I'd like to see more of the process that goes into building out your models beyond CRUDing. – mike Mar 21 '14 at 19:08
  • PHP is evolving and this may take a while, but as times goes, you are going to see it more and more. Just that. – Antonio Carlos Ribeiro Mar 21 '14 at 19:09
  • If you are interested in learning more about these topics, check out 'Laravel: From Apprentice to Artisan'. It goes more into depth in these topics and is a very good read. – user1669496 Mar 21 '14 at 19:50