从“胖模型,瘦控制器”的角度使用 Laravel Eloquent ORM 工作的做法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22566572/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are the practices for working from a "Fat model, skinny controller" perspective with Laravel Eloquent ORM?
提问by mike
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/
- 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.
虽然这个话题已经出现在 Stack Overflow 上,但我还没有在实践中找到对该方法的详尽描述。
I just found an old related question here.
回答by Antonio Carlos Ribeiro
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(vanilla 或 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');
}
}
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.
控制器是 HTTP 请求、业务逻辑和表示层之间的桥梁。所以它应该接收一个请求,将它发送到一个注入的对象,该对象将处理它并重定向到负责向客户端(或用户)提供反馈的路由(或呈现视图)。其他一切,包括验证,都应该在您的存储库、服务、模型(MVC,是的!)等中进行。
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.
基本上,您的存储库类将使用自己的调用者 ( $this
) 来触发succeeded
和failed
方法。
Fat Repositories / Services / Models
Fat 存储库 / 服务 / 模型
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.
模型与您的数据过于相关,有时它们是您的 ORM 并直接与您的数据库服务器对话,因此,现在您会看到人们使用存储库和服务作为它们之间的层。
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:
所有不直接属于你的业务逻辑的东西,主要是外部服务,离你的应用程序代码最远,你构建的越解耦越好。为这些服务创建外部包(Composer 包)是将它们与其他一切分离的好方法,如果您使它们与框架无关,则您有权获得10 Sturgeon 积分。在 Laravel 中,您可以通过集成三种类来创建服务:
1) Service Class(es): responsible for doing what your service must do, all your service logic goes here.
1)服务类:负责做你的服务必须做的事情,你所有的服务逻辑都在这里。
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.
2)Service Provider:负责启动你的服务并将其添加到 Laravel 的 IoC 容器中,以便它可以随时使用,但请注意,Laravel 只会在你的应用程序真正使用它们时实例化你的服务类。
3) Facade: lets you access your service from anywhere in your application using the static (::) syntax:
3) Facade:允许您使用静态 (::) 语法从应用程序的任何位置访问您的服务:
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
模型 / 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.
那些家伙应该是高度可交换的,今天你可能使用 Eloquent 作为你的 ORM,将数据存储在数据库中,但你可能需要将其更改为其他东西,有些人将 100% 的数据存储在 Redis 中,所以你最好通过在 ORM 和域 loginc 之间使用接口(合同)层为这样的更改做好准备,并真正为您的接口而不是您的具体类进行开发。Taylor Otwell 在他的书中甚至说你应该完全删除你的模型文件夹。
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:
这背后的想法是轻松交换实现,因此在 Laravel 中,您可以使用 IoC 容器来告诉 Laravel 您正在使用哪个实现:
App::bind('PostInterface', 'DbPost');
So, if you have a Repository is using your PostInterface:
所以,如果你有一个 Repository 正在使用你的 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:
Laravel IoC 容器将使用 DbPost 实例自动实例化此存储库。如果您需要将其更改为 Redis,您只需要更改一行:
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:
视图应该只负责显示信息。视图不应该知道您的模型、服务、存储库或系统中的任何其他内容。视图应该可以由 websigners 编辑,因此,您拥有的代码越多,您的非 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());
}
}
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:
你如何格式化你的数据?您在视图中编写原始属性,但您应该在幕后使用演示者来展示您的数据。展示者通常使用装饰器设计模式来格式化要在页面中展示的数据。这是一个使用 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();
}
}
Related Books
相关书籍
Taylor Otwell's Laravel: From Apprentice To Artisan
Taylor Otwell 的 Laravel:从学徒到工匠