从“胖模型,瘦控制器"工作的实践是什么? Laravel Eloquent ORM的观点? [英] What are the practices for working from a "Fat model, skinny controller" perspective with Laravel Eloquent ORM?

查看:63
本文介绍了从“胖模型,瘦控制器"工作的实践是什么? Laravel Eloquent ORM的观点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

阅读以下内容后,我一直在吸引其他开发人员的头脑:胖模型,瘦控制器".

大多数受访者都在使用我认为的肥胖控制者.

虽然话题已经在Stack Overflow上出现了,但我在实践中还没有找到对该方法的详尽描述.

我刚刚在这里发现了一个旧的相关问题.

解决方案

骨感控制器

您将在PHP(香草,Laravel或Symfony)中看到的越来越多的东西是有史以来最纤细的控制器.这是您在Rails中已经看到的东西,并且人们也开始将其称为六边形(通过一些其他实践).控制器中只需要一行代码,实际上他们说这应该是所有方法的目标.是一个示例,是的,还不止于此,但仍然很瘦:

<?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');
    }

}

控制器是HTTP请求,业务逻辑和表示层之间的桥梁.因此,它应该接收一个请求,将其发送到注入的对象,该对象将对其进行处理并重定向到负责向客户(或用户)提供反馈的路由(或渲染视图).其他所有内容,包括验证,都应在您的存储库,服务,模型(MVC,是的!)等中进行.

但是我们可以以六边形的方式重构此控制器,以达到每方法一线的目标:

<?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());
    }

}

基本上,您的存储库类将使用自己的调用方($this)触发succeededfailed方法.

脂肪存储库/服务/模型

模型与您的数据过于相关,有时它们是您的ORM,并且直接与您的数据库服务器对话,因此,如今,您会看到人们将存储库和服务用作它们之间的层.

存储库

存储库是一类,通过直接与您的模型进行对话,处理和收集应用程序所需的信息.您的应用程序应该不知道在数据库中选择某些信息,选择,位置,顺序,分组依据所必需的东西,而这些东西有时只是您的模型应该知道的,所以这是一个存储库:

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();
    }

}

服务

与业务逻辑不直接相关的所有事物,主要是外部服务,离您的应用程序代码越远,构建它们的解耦越多越好.为这些服务创建外部程序包(Composer程序包)是将它们与其他所有功能分离的一种好方法,如果您使它们与框架无关,则您有权获得 10个St鱼点.在Laravel中,您可以通过集成三种类来创建服务:

1)服务类:负责执行您的服务必须做的事情,所有服务逻辑都在这里.

2)服务提供者:负责启动您的服务并将其添加到Laravel的IoC容器中,以便可以随时使用,但是请注意,Laravel仅在您的应用程序真正使用它们时才实例化您的服务类. /p>

3)外观:使您可以使用静态(::)语法从应用程序中的任何位置访问服务:

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

这是Mailer服务:

服务等级

<?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);
        });
    }

}

服务提供商

<?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');
    }

}

门面

<?php namespace ACR\Services\Mailer;

use Illuminate\Support\Facades\Facade as IlluminateFacade;

class Facade extends IlluminateFacade {

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

}

模型/ORM

这些人应该具有很高的互换性,今天您可能使用Eloquent作为您的ORM,将数据存储在数据库中,但是您可能需要将其更改为其他内容,某些fok将其数据的100%存储在Redis中,因此,您最好通过在ORM和域loginc之间使用接口(合同)层来为这样的更改做好准备,并真正为接口而不是具体的类进行开发.泰勒·奥特威尔(Taylor Otwell)在他的书中甚至说过,您应该完全删除您的models文件夹.

interface PostInterface {

    public function all();

    public function find($id);

}

class DbPost extends Eloquent implements PostInterface {

}

class RedisPost extends Eloquent implements PostInterface {

}

其背后的想法是轻松交换实现,因此在Laravel中,您可以使用IoC容器来告诉Laravel您正在使用哪个实现:

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

因此,如果您的存储库正在使用PostInterface:

class PostRepository implements PostRepositoryInterface {

    private $model;

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

}

Laravel IoC容器将使用DbPost实例自动实例化此存储库.而且,如果您需要将其更改为Redis,则只需更改一行:

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

观看次数/演示者

最愚蠢的敬畏者.

观看次数

视图仅应负责显示信息.视图不应该知道您的模型,服务,存储库或系统中的任何其他内容. Webesigners应该可以编辑视图,因此,您在视图上拥有的代码越多,您的非php-programmer-designer所添加的错误也就越多.您的控制器应从存储库中收集信息,并将其传递给您的视图:

<?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());
    }

}

您的视图的唯一责任应该是显示数据:

@extends('layout')

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

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

演示者

您如何格式化数据?您在视图中编写了原始属性,但是在幕后,应该使用演示者来演示数据.演示者通常使用装饰器设计模式来格式化要在页面中呈现的数据.这是使用Shawn McCool的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();
    }    
}

相关书籍

泰勒·奥特威尔(Taylor Otwell)的Laravel:从学徒到工匠

Chris Fidao的实施Laravel

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.

解决方案

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

这篇关于从“胖模型,瘦控制器"工作的实践是什么? Laravel Eloquent ORM的观点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆