假设每当我进行CRUD操作或以特定方式修改关系时,我也想做其他事情。例如,每当有人发布帖子时,我也想将某些内容保存到表中以进行分析。也许不是最好的例子,但总的来说有很多这种“分组”的功能。
通常我会将这种逻辑放入控制器中。在你想要在很多地方重现这个功能之前,这一切都很好。当你开始进入局部,创建一个API并生成虚拟内容时,这就成了一个让事情变得干燥的问题。
我看到管理它的方法是事件,存储库,库和添加到模型。以下是我对每个人的理解:
服务:这是大多数人可能会放置此代码的地方。我对服务的主要问题是,有时很难在其中找到特定功能,我觉得他们会忘记人们何时专注于使用Eloquent。当我可以publishPost()
时,我怎么知道我需要在库中调用方法$post->is_published = 1
?
我认为这种方法运作良好的唯一条件是,如果你只使用服务(理想情况下,不管怎么说,Eloquent都不能从控制器中获取)。
如果您的请求通常遵循您的模型结构,最终似乎只会创建一堆额外的不必要文件。
存储库:根据我的理解,这基本上就像一个服务,但是有一个接口,所以你可以在ORM之间切换,我不需要。
事件:从某种意义上说,我认为这是最优雅的系统,因为您知道您的模型事件总是会在Eloquent方法上调用,因此您可以像平常一样编写控制器。我可以看到这些变得混乱,如果有人有大型项目的例子使用事件进行关键耦合我想看到它。
模型:传统上我会有一些执行CRUD并处理关键耦合的类。这实际上让事情变得简单,因为你知道CRUD的所有功能+无论如何都要做到这一点。
简单,但在MVC架构中,这通常不是我所看到的。从某种意义上说,虽然我更喜欢这种服务,因为它更容易找到,而且需要跟踪的文件较少。但它可能会有点混乱。我想听听这种方法的失败,以及为什么大多数人似乎都没有这样做。
每种方法的优点/缺点是什么?我错过了什么吗?
答案 0 :(得分:138)
我认为只要您遵循SOLID原则,您提供的所有模式/架构都非常有用。
对于添加逻辑的位置,我认为引用Single Responsibility Principle非常重要。另外,我的回答是你正在研究中/大型项目。如果它是一个扔在页面上的东西项目,请忘记这个答案并将其全部添加到控制器或模型中。
简短的回答是:对您有意义的(使用服务) 。
答案很长:
控制器:控制器的责任是什么?当然,您可以将所有逻辑放在控制器中,但控制器是否有责任?我不这么认为。
对我来说,控制器必须接收请求并返回数据,这不是放置验证,调用db方法等的地方。
模型:这是一个添加逻辑的好地方,例如当用户注册或更新帖子的投票数时发送欢迎电子邮件?如果您需要从代码中的其他位置发送相同的电子邮件,该怎么办?你创建一个静态方法吗?如果该电子邮件需要来自其他模型的信息怎么办?
我认为模型应该代表一个实体。使用Laravel,我只使用模型类添加fillable
,guarded
,table
和关系(这是因为我使用存储库模式,否则模型也会有save
,update
,find
等方法。
存储库(存储库模式):一开始我对此非常困惑。而且,就像你一样,我想"好吧,我使用MySQL就是那样。"。
但是,我已经平衡了使用存储库模式的优点和缺点,现在我使用它。我认为现在,就在这个时刻,我只需要使用MySQL。但是,如果从现在起三年后我需要改变像MongoDB这样的东西,大部分工作都已完成。所有这些都是以一个额外的接口和$app->bind(«interface», «repository»)
为代价的。
事件(Observer Pattern):事件对于任何给定时间可以在任何类中抛出的事物都很有用。例如,考虑向用户发送通知。
在您需要时,您可以触发事件以在您的应用程序的任何类别发送通知。然后,您可以拥有类似UserNotificationEvents
的类来处理用户通知的所有已触发事件。
服务:到目前为止,您可以选择向控制器或模型添加逻辑。 对我来说,在服务中添加逻辑是完全合理的。让我们面对现实吧,服务是课程的一个奇特名称。你可以在你的应用程序中拥有尽可能多的课程。
举个例子:不久前,我开发了像Google Forms这样的东西。我从CustomFormService
开始,最后得到CustomFormService
,CustomFormRender
,CustomFieldService
,CustomFieldRender
,CustomAnswerService
和CustomAnswerRender
。为什么?因为它对我有意义。如果您与团队合作,您应该将逻辑放在对团队有意义的地方。
使用服务与控制器/模型的优点是您不受单个控制器或单个模型的约束。您可以根据应用程序的设计和需求根据需要创建任意数量的服务。再加上在应用程序的任何类中调用服务的优势。
这很长,但我想告诉你我是如何构建我的应用程序的:
app/
controllers/
MyCompany/
Composers/
Exceptions/
Models/
Observers/
Sanitizers/
ServiceProviders/
Services/
Validators/
views
(...)
我将每个文件夹用于特定功能。例如,Validators
目录包含一个BaseValidator
类,负责处理验证,基于特定验证器的$rules
和$messages
(通常每个模型一个)。我可以很容易地将这些代码放在一个服务中,但即使它只在服务中使用(现在),对我来说有一个特定的文件夹也是有意义的。
我建议你阅读以下文章,因为它们可能会向你解释一些事情:
Breaking the Mold by Dayle Rees(CodeBright的作者):这是我把它放在一起的地方,即使我改变了一些东西以满足我的需要。
Chris Goosey的Decoupling your code in Laravel using Repositories and Services:这篇文章很好地解释了什么是服务和存储库模式以及它们如何组合在一起。
Laracasts也有Repositories Simplified和Single Responsibility这些都是很好的资源和实际例子(即使你需要付费)。
答案 1 :(得分:17)
我在控制器和模型之间创建逻辑的过程是创建服务层。基本上,这是我的应用程序中的任何操作的流程:
我就是这样做的:
这是控制器创建内容的方法:
public function processCreateCongregation()
{
// Get input data.
$congregation = new Congregation;
$congregation->name = Input::get('name');
$congregation->address = Input::get('address');
$congregation->pm_day_of_week = Input::get('pm_day_of_week');
$pmHours = Input::get('pm_datetime_hours');
$pmMinutes = Input::get('pm_datetime_minutes');
$congregation->pm_datetime = Carbon::createFromTime($pmHours, $pmMinutes, 0);
// Delegates actual operation to service.
try
{
CongregationService::createCongregation($congregation);
$this->success(trans('messages.congregationCreated'));
return Redirect::route('congregations.list');
}
catch (ValidationException $e)
{
// Catch validation errors thrown by service operation.
return Redirect::route('congregations.create')
->withInput(Input::all())
->withErrors($e->getValidator());
}
catch (Exception $e)
{
// Catch any unexpected exception.
return $this->unexpected($e);
}
}
这是执行与操作相关的逻辑的服务类:
public static function createCongregation(Congregation $congregation)
{
// Log the operation.
Log::info('Create congregation.', compact('congregation'));
// Validate data.
$validator = $congregation->getValidator();
if ($validator->fails())
{
throw new ValidationException($validator);
}
// Save to the database.
$congregation->created_by = Auth::user()->id;
$congregation->updated_by = Auth::user()->id;
$congregation->save();
}
这是我的模特:
class Congregation extends Eloquent
{
protected $table = 'congregations';
public function getValidator()
{
$data = array(
'name' => $this->name,
'address' => $this->address,
'pm_day_of_week' => $this->pm_day_of_week,
'pm_datetime' => $this->pm_datetime,
);
$rules = array(
'name' => ['required', 'unique:congregations'],
'address' => ['required'],
'pm_day_of_week' => ['required', 'integer', 'between:0,6'],
'pm_datetime' => ['required', 'regex:/([01]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5][0-9]/'],
);
return Validator::make($data, $rules);
}
public function getDates()
{
return array_merge_recursive(parent::getDates(), array(
'pm_datetime',
'cbs_datetime',
));
}
}
有关这种方式的更多信息,我用来整理Laravel应用的代码:https://github.com/rmariuzzo/Pitimi
答案 2 :(得分:17)
我想对自己的问题发表回复。我可以谈论这几天,但我会尽快发布,以确保我得到它。
我最终利用了Laravel提供的现有结构,这意味着我将我的文件主要保留为模型,视图和控制器。我还有一个Libraries文件夹,用于可重用的组件,而不是真正的模型。
我没有在服务/图书馆中包装我的模型。所提供的所有理由并非100%让我相信使用服务的好处。虽然我可能错了,但据我所知,它们只会产生大量额外的空文件,我需要在处理模型时进行创建和切换,并且真正降低使用雄辩的好处(特别是当涉及到RETRIEVING模型时)例如,使用分页,范围等)。
我将业务逻辑放入模型并直接从我的控制器访问eloquent。我使用了许多方法来确保业务逻辑不会被绕过:
解决使用模型时人们的顾虑:
附加说明:我觉得在服务中包装你的模型就像拥有一把瑞士军刀,有很多工具,并在它周围建造另一把刀基本上做同样的事情?是的,有时候你可能想要关掉刀片或确保两个刀片一起使用......但通常还有其他方法可以做到......
何时使用服务:这篇文章非常清楚地阐述了何时使用服务的好例子(提示:它不经常)。他说基本上当你的对象在其生命周期的奇怪部分使用多个模型或模型时它是有道理的。 http://www.justinweiss.com/articles/where-do-you-put-your-code/
答案 3 :(得分:6)
在我看来,Laravel已经有很多选项供您存储业务逻辑。
简短回答:
Request
个对象自动验证您的输入,然后将数据保留在请求中(创建模型)。由于所有用户输入都可直接在 请求中使用,因此我认为在此处执行此操作是有意义的。Job
个对象来执行需要单个组件的任务,然后只需调度它们。我认为Job
包含服务类。他们执行任务,例如业务逻辑。长(呃)回答:
在需要时使用存储库:
存储库必然会过度浮动,并且大多数时候只是用作模型的accessor
。我觉得他们肯定有一些用处,但除非你正在开发一个大量的应用程序,需要这样的灵活性让你能够完全抛弃laravel,远离存储库。您以后会感谢自己,而且您的代码会更直接。
问问自己,您是否有可能将PHP框架或更改为laravel不支持的数据库类型。
如果您的答案是"可能不是",那么就不要实施存储库模式。
除此之外,请不要像Eloquent那样在一个极好的ORM上打一个模式。您只是增加了不需要的复杂性,而且根本不会让您受益。
谨慎使用服务 对我来说,服务类只是存储业务逻辑以执行具有给定依赖关系的特定任务的地方。 Laravel开箱即用,称为“乔布斯”,它们比自定义服务类具有更大的灵活性。
我觉得Laravel对MVC
逻辑问题有一个全面的解决方案。它只是一个问题或组织。
示例:
请求强>:
namespace App\Http\Requests;
use App\Post;
use App\Jobs\PostNotifier;
use App\Events\PostWasCreated;
use App\Http\Requests\Request;
class PostRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required',
'description' => 'required'
];
}
/**
* Save the post.
*
* @param Post $post
*
* @return bool
*/
public function persist(Post $post)
{
if (!$post->exists) {
// If the post doesn't exist, we'll assign the
// post as created by the current user.
$post->user_id = auth()->id();
}
$post->title = $this->title;
$post->description = $this->description;
// Perform other tasks, maybe fire an event, dispatch a job.
if ($post->save()) {
// Maybe we'll fire an event here that we can catch somewhere else that
// needs to know when a post was created.
event(new PostWasCreated($post));
// Maybe we'll notify some users of the new post as well.
dispatch(new PostNotifier($post));
return true;
}
return false;
}
}
<强>控制器强>:
namespace App\Http\Controllers;
use App\Post;
use App\Http\Requests\PostRequest;
class PostController extends Controller
{
/**
* Creates a new post.
*
* @return string
*/
public function store(PostRequest $request)
{
if ($request->persist(new Post())) {
flash()->success('Successfully created new post!');
} else {
flash()->error('There was an issue creating a post. Please try again.');
}
return redirect()->back();
}
/**
* Updates a post.
*
* @return string
*/
public function update(PostRequest $request, $id)
{
$post = Post::findOrFail($id);
if ($request->persist($post)) {
flash()->success('Successfully updated post!');
} else {
flash()->error('There was an issue updating this post. Please try again.');
}
return redirect()->back();
}
}
在上面的示例中,请求输入会自动验证,我们需要做的就是调用persist方法并传入一个新的Post。我认为可读性和可维护性应该总是胜过复杂和不必要的设计模式。
然后您可以使用完全相同的持久方法来更新帖子,因为我们可以检查帖子是否已经存在并在需要时执行交替逻辑。