Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the practices for working from a "Fat model, skinny controller" perspective with Laravel Eloquent ORM? [closed]

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

  • http://culttt.com/2013/07/01/setting-up-your-first-laravel-4-controller/
  • http://culttt.com/2013/05/13/setting-up-your-first-laravel-4-model/

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.

like image 547
mike Avatar asked Mar 21 '14 18:03

mike


1 Answers

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

like image 190
12 revs Avatar answered Oct 18 '22 14:10

12 revs