問題描述
閱讀后,我一直在為胖模型,瘦控制器"的概念挑選其他開發(fā)人員的大腦:
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/
大多數(shù)受訪者正在使用我認(rèn)為的胖控制器.
Most respondents are using what I'd consider fat controllers.
雖然這個(gè)話題出現(xiàn)在 Stack Overflow 上,但我還沒有在實(shí)踐中找到對該方法的詳盡描述.
While the topic has come up on Stack Overflow I haven't found thorough description of the method in practice.
我剛剛在這里找到了一個(gè)舊的相關(guān)問題.
I just found an old related question here.
推薦答案
Skinny Controllers
您將在 PHP(vanilla 或 Laravel 或 Symfony)中看到的越來越多的是有史以來最瘦的控制器.這是您在 Rails 中已經(jīng)看到的東西,人們也開始將其稱為(通過其他一些實(shí)踐)六邊形.你的控制器中只需要一行代碼,實(shí)際上他們說這應(yīng)該是你所有方法的目標(biāo).這是一個(gè)例子,是的,比這多一點(diǎn),但仍然很瘦:
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');
}
}
控制器是 HTTP 請求、業(yè)務(wù)邏輯和表示層之間的橋梁.所以它應(yīng)該接收一個(gè)請求,將它發(fā)送到一個(gè)注入的對象,該對象將處理它并重定向到負(fù)責(zé)向客戶端(或用戶)提供反饋的路由(或呈現(xiàn)視圖).其他一切,包括驗(yàn)證,都應(yīng)該在您的存儲(chǔ)庫、服務(wù)、模型(MVC,是的!)等中進(jìn)行.
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.
但是我們可以以六邊形的方式重構(gòu)這個(gè)控制器,以達(dá)到每個(gè)方法一行的目標(biāo):
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());
}
}
基本上,您的存儲(chǔ)庫類將使用自己的調(diào)用者 ($this
) 來觸發(fā) succeeded
和 failed
方法.
Basically your repository classes will use the own caller ($this
) to fire the succeeded
and failed
methods.
模型與您的數(shù)據(jù)過于相關(guān),有時(shí)它們是您的 ORM 并直接與您的數(shù)據(jù)庫服務(wù)器通信,因此,現(xiàn)在您會(huì)看到人們使用存儲(chǔ)庫和服務(wù)作為它們之間的層.
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.
存儲(chǔ)庫是一個(gè)類,它通過直接與您的模型對話來處理和收集您的應(yīng)用程序所需的信息.您的應(yīng)用程序不應(yīng)該知道在您的數(shù)據(jù)庫中選擇某些信息需要什么,選擇,位置,排序,分組依據(jù),這些有時(shí)只有您的模型應(yīng)該知道,所以這是一個(gè)存儲(chǔ)庫:
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();
}
}
服務(wù)
所有不直接屬于您的業(yè)務(wù)邏輯的東西,主要是外部服務(wù),離您的應(yīng)用程序代碼最遠(yuǎn)的地方,您構(gòu)建的越解耦越好.為這些服務(wù)創(chuàng)建外部包(Composer 包)是將它們與其他一切分離的好方法,如果你讓它們與框架無關(guān),你就有權(quán)獲得 10 點(diǎn)鱘魚點(diǎn).在 Laravel 中,您可以通過集成三種類來創(chuàng)建服務(wù):
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):負(fù)責(zé)做你的服務(wù)必須做的事情,你所有的服務(wù)邏輯都在這里.
1) Service Class(es): responsible for doing what your service must do, all your service logic goes here.
2) 服務(wù)提供者:負(fù)責(zé)啟動(dòng)你的服務(wù)并將其添加到 Laravel 的 IoC 容器中,以便隨時(shí)可以使用,但請注意,Laravel 只會(huì)在你的應(yīng)用程序真正使用它們時(shí)實(shí)例化你的服務(wù)類.
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:允許您使用靜態(tài) (::) 語法從應(yīng)用程序的任何位置訪問您的服務(wù):
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');
這是郵件程序服務(wù):
<?php namespace ACRServicesMailer;
use IlluminateMailMailer 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);
});
}
}
服務(wù)提供商
<?php namespace ACRServicesMailer;
use IlluminateSupportServiceProvider as IlluminateServiceProvider;
use ACRServicesMailerService 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 ACRServicesMailer;
use IlluminateSupportFacadesFacade as IlluminateFacade;
class Facade extends IlluminateFacade {
protected static function getFacadeAccessor() { return 'acr.mailer'; }
}
模型/ORM
那些人應(yīng)該是高度可交換的,今天您可能使用 Eloquent 作為 ORM,將數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫中,但您可能需要將其更改為其他內(nèi)容,有些人將 100% 的數(shù)據(jù)存儲(chǔ)在 Redis 中,因此,您最好通過在 ORM 和域 loginc 之間使用接口(合同)層來為這樣的更改做好準(zhǔn)備,并真正為您的接口而不是您的具體類進(jìn)行開發(fā).Taylor Otwell 在他的書中甚至說你應(yīng)該完全刪除你的模型文件夾.
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 {
}
這背后的想法是很容易交換實(shí)現(xiàn),所以在 Laravel 中你可以使用 IoC 容器來告訴 Laravel 你正在使用哪個(gè)實(shí)現(xiàn):
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');
因此,如果您有一個(gè)存儲(chǔ)庫正在使用您的 PostInterface:
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 容器將使用 DbPost 實(shí)例自動(dòng)實(shí)例化此存儲(chǔ)庫.如果您需要將其更改為 Redis,則只需更改一行:
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');
觀看次數(shù)/演示者
最笨的就是最棒的.
Views / Presenters
The dumbest the awesomer.
視圖應(yīng)該只負(fù)責(zé)顯示信息.視圖不應(yīng)該知道您的模型、服務(wù)、存儲(chǔ)庫或系統(tǒng)中的任何其他內(nèi)容.視圖應(yīng)該可以由 websigners 編輯,因此,您擁有的代碼越多,您的非 php-programmer-designer 向它們添加的錯(cuò)誤就越多.您的控制器應(yīng)該從您的存儲(chǔ)庫中收集信息并將它們傳遞給您的視圖:
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());
}
}
您的視圖的唯一責(zé)任應(yīng)該是顯示數(shù)據(jù):
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
演示者
您如何格式化數(shù)據(jù)?您在視圖中編寫原始屬性,但您應(yīng)該在幕后使用演示者來展示您的數(shù)據(jù).展示者通常使用裝飾器設(shè)計(jì)模式來格式化要在頁面中展示的數(shù)據(jù).這是一個(gè)使用 Shawn McCool 的 LaravelAutoPresenter 的例子:
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 AppPresenters;
use McCoolLaravelAutoPresenterBasePresenter;
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 CarbonCarbon::createFromFormat('d-m-Y', $date, 'Sao_Paulo/Brazil')
->toFormattedDateString();
}
}
相關(guān)書籍
Taylor Otwell 的 Laravel:從學(xué)徒到工匠
Chris Fidao 的 Laravel 實(shí)現(xiàn)
這篇關(guān)于從“胖模型,瘦控制器"工作的做法是什么?對 Laravel Eloquent ORM 的看法?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!