MVC(Laravel)在哪里添加逻辑
比方说,每当我做一个CRUD操作或以特定的方式修改关系时,我也想做其他的事情。 例如,每当有人发布post,我也想保存一些东西到分析表。 也许不是最好的例子,但总的来说有很多“分组”function。
通常我会将这种types的逻辑放入控制器中。 这一切都很好,直到你想在很多地方重现这个function。 当你开始进入partials,创build一个API和生成虚拟内容,这成为一个干事情的问题。
我见过的pipe理方式是事件,存储库,库和添加到模型。 这是我对每个人的理解:
服务:这是大多数人可能会把这个代码的地方。 我的服务主要问题是,有时很难find它们的具体function,我觉得他们忘记了什么时候人们专注于使用雄辩。 我怎么会知道我需要调用一个方法publishPost()
在库中,当我可以做$post->is_published = 1
?
我看到这个工作正常的唯一条件是,如果你只使用服务(理想情况下使控制器不能从某种程度上无法访问)。
最终,如果你的请求通常遵循你的模型结构,这似乎只会创build一堆额外的不必要的文件。
知识库:从我的理解,这基本上就像一个服务,但有一个接口,所以你可以切换ORM,我不需要。
事件:从某种意义上说,我认为这是最优雅的系统,因为你知道你的模型事件总是要在Eloquent方法上调用,所以你可以像平常一样编写你的控制器。 我可以看到这些变得杂乱无章,如果任何人有大型项目使用关键耦合事件的例子,我想看看它。
使事件成为“不”的最大的事情是,当关系被修改时(至less不是多对多),你不能调用它。更新:我相信我喜欢解决这个问题。 将很快发布。
模型:传统上我会有执行CRUD并处理关键耦合的类。 这实际上使得事情变得简单,因为你知道CRUD的所有function,无论是在那里做什么。
简单,但在MVC架构中,这通常不是我所看到的。 从某种意义上说,尽pipe我比较喜欢这个服务,但是因为它比较容易find,并且有更less的文件需要跟踪。 它可以得到一点混乱,但。 我想听听这种方法的失败,为什么大多数人似乎没有这样做。
每种方法的优点和缺点是什么? 我错过了什么吗?
我认为,只要您遵循SOLID原则,您呈现的所有模式/体系结构都非常有用。
对于何处添加逻辑,我认为参照单一责任原则很重要。 另外,我的答案认为你正在从事一个大中型项目。 如果这是一个抛出一些东西的项目,忘记这个答案,并将其全部添加到控制器或模型。
简短的回答是: 对你有意义(有服务) 。
漫长的回答:
控制器 : 控制器的责任是什么? 当然,你可以把所有的逻辑放在一个控制器中,但是控制器的责任是? 我不这么认为。
对我来说,控制器必须收到请求并返回数据,这不是放置validation的地方,调用db方法等。
模型 :这是一个好的地方添加逻辑,如发送一个欢迎电子邮件时,用户注册或更新投票的计数? 如果您需要从代码中的其他地方发送相同的电子邮件,该怎么办? 你创build一个静态方法? 如果这些电子邮件需要其他模式的信息呢?
我认为模型应该代表一个实体。 使用Laravel,我只使用模型类添加fillable
, guarded
, table
和关系(这是因为我使用了Repository模式,否则模型也会有save
, update
, find
等方法)。
版本库(版本库模式) :一开始我很困惑。 而且,就像你一样,我想“好吧,我用MySQL和那个”。
不过,我已经平衡了使用Repository Pattern的优点和缺点,现在我使用它。 我认为现在在这个时候,我只需要使用MySQL。 但是,如果三年后,我需要改变一些像MongoDB一样的工作。 所有的代价都是一个额外的接口和$app->bind(«interface», «repository»)
。
事件( 观察者模式 ):事件对于任何时候可以在任何类中抛出的东西都是有用的。 例如,想一想向用户发送通知。 当你需要的时候,你可以在任何一个级别的申请表上发起一个通知。 然后,您可以拥有一个像UserNotificationEvents
这样的类来处理用户通知的所有触发事件。
服务 :到目前为止,您可以select将逻辑添加到控制器或模型。 对我来说,在服务中添加逻辑是很有意义的 。 让我们面对现实吧,服务是一个很好的名字。 而且你可以在你的应用程序中拥有尽可能多的类。
以这个例子为例:不久之前,我开发了类似Google Forms的东西。 我从一个CustomFormService
开始,最后是CustomFormService
, CustomFormRender
, CustomFieldService
, CustomFieldRender
, CustomAnswerService
和CustomAnswerRender
。 为什么? 因为这对我有意义。 如果你和一个团队合作,你应该把你的逻辑放在对团队有意义的地方。
使用服务与控制器/模型的优点是您不受单个控制器或单个模型的限制。 根据您的应用程序的devise和需求,您可以根据需要创build尽可能多的服务。 再加上在应用程序的任何一个类中调用服务的好处。
这很长,但我想告诉你我是如何构build我的应用程序:
app/ controllers/ MyCompany/ Composers/ Exceptions/ Models/ Observers/ Sanitizers/ ServiceProviders/ Services/ Validators/ views (...)
我使用每个文件夹的特定function。 例如, Validators
目录包含一个BaseValidator
类,它负责根据特定validation器(通常每个模型一个)的$rules
和$messages
来处理validation。 我可以很容易地把这个代码放在一个服务中,但是对于我来说这是有意义的,即使它只在服务中使用(现在)。
我build议你阅读下面的文章,因为他们可能会更好地向你解释一些事情:
Dayle Rees ( CodeBright的作者) 打破模具 :这是我把它放在一起,即使我改变了一些东西,以适应我的需求。
Chris Goosey使用Repositories和Services解耦Laravel中的代码 :这篇文章解释了什么是Service和Repository Pattern,以及它们如何组合在一起。
Laracasts也有简单和单一的责任 库 ,这是很好的资源和实例(即使你必须支付)。
我用来创build控制器和模型之间的逻辑是创build一个服务层 。 基本上,这是我的应用程序内的任何行动的stream程:
- 控制器获取用户请求的操作并发送参数并将所有内容委托给服务类。
- 服务类完成所有与操作有关的逻辑:inputvalidation,事件logging,数据库操作等等。
- 模型保存字段的信息,数据转换和属性validation的定义。
这是我如何做到的:
这是一个控制器创build一些东西的方法:
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
我想发表一个回应我自己的问题。 我可以谈论这个好几天,但是我会试着快速的把这个贴出来,以确保我能得到它。
我最终使用了Laravel提供的现有结构,这意味着我主要将我的文件保存为Model,View和Controller。 我也有一个库文件夹可重用的组件不是真正的模型。
我没有把我的模型包括在服务/图书馆里 。 所有提供的理由都不足以说服我使用服务的好处。 虽然我可能是错的,但据我所知,他们只是导致大量的几乎空文件,我需要创build和切换模型之间切换,也真的减less了使用口才的好处(特别是当涉及到检索模型例如使用分页,范围等)。
我把商业逻辑放在模型中 ,直接从我的控制器中获取口才。 我使用了许多方法来确保业务逻辑不被绕过:
- 访问者和变种者: Laravel拥有很多访问者和变种者。 如果我想执行一个行动,每当一个职位从草案移动到发布,我可以通过创build函数setIsPublishedAttribute和包括在那里的逻辑
- 重写创build/更新等:您可以始终覆盖模型中的Eloquent方法以包含自定义function。 这样你可以调用任何CRUD操作的function。 编辑:我觉得有一个重写创build更新的Laravel版本的bug(所以我使用现在在启动注册的事件)
- validation:我以同样的方式挂钩我的validation,例如,我将通过覆盖CRUD函数以及如果需要的访问器/修改器来运行validation。 请参阅Esensi或dwightwatson /validation以获取更多信息。
- 魔术方法:我使用我的模型的__get和__set方法在合适的地方挂钩到function
- 扩展雄辩:如果有一个行动,你想要采取所有更新/创build,你甚至可以扩展雄辩,并将其应用到多个模型。
- 事件:这是一个简单而且普遍认同的地方。 我认为事件的最大缺点是例外情况难以追查(可能不是Laravel新事件系统的新情况)。 我也喜欢按照他们所做的事情来组织我的事件,而不是在他们被调用的时候。例如,让一个MailSender订阅者监听发送邮件的事件。
- 添加枢轴/属于许多事件:我最长时间挣扎的事情之一是如何将行为附加到belongsToMany关系的修改。 例如,每当用户join一个组时执行一个动作。 我差不多已经完成了一个定制库。 我还没有发表,但它是function! 将尽快发布链接。 编辑我结束了所有我的枢轴进入正常模式,我的生活已经非常容易…
解决人们使用模型的顾虑:
- 组织:是的,如果你在模型中包含更多的逻辑,他们可以更长,但总的来说,我发现我的模型有75%仍然很小。 如果我select组织更大的那个,我可以使用traits(例如,根据需要为PostScopes,PostAccessors,PostValidation等其他文件创build一个文件夹)。 我知道这不一定是什么特性,但是这个系统没有问题。
附加说明:我觉得在服务中包装你的模型就像是有一把瑞士军刀,有很多工具,然后在它周围build立另一把刀,基本上是一样的东西? 是的,有些时候你可能想把刀片粘在一起,或者确保两个刀片一起使用,但是通常还有其他方法可以做到。
什么时候使用服务 :这篇文章阐述了什么时候使用服务的伟大例子( 提示:不是很经常 )。 他说,基本上当你的对象使用多个模型或模型在他们生命周期的奇怪部分是有道理的。 http://www.justinweiss.com/articles/where-do-you-put-your-code/
在我看来,Laravel已经有很多select来存储您的业务逻辑。
简短的回答:
- 使用laravel的
Request
对象自动validationinput,然后将数据保存在请求中(创build模型)。 由于所有的用户input都可以在请求中直接使用,我相信在这里执行此操作是有意义的。 - 使用laravel的
Job
对象来执行需要单个组件的任务,然后简单地调度它们。 我认为约翰的服务class包括在内。 他们执行任务,如业务逻辑。
长(呃)回答:
必要时使用Respositories:储存库必定会过度繁忙,并且大部分时间只是用作模型的accessor
。 我觉得他们肯定有一些用处,但除非你正在开发一个大规模的应用程序, 需要大量的灵活性,才能完全摆脱laravel,远离存储库。 稍后你会感谢你,你的代码将会更直接。
问问自己是否有可能会改变PHP框架或 laravel不支持的数据库types。
如果你的答案是“可能不是”,那么就不要实现存储库模式。
除了上面的内容,请不要在雄辩的ORM之上打一个模式。 你只是增加了不必要的复杂性,它根本不会使你受益。
谨慎使用服务:对我来说,服务类只是一个存储业务逻辑的地方,可以根据给定的依赖项执行特定的任务。 Laravel提供了这些开箱即用的工作,他们比自定义的Service类有更多的灵活性。
我觉得Laravel对于MVC
逻辑问题有一个全面的解决scheme。 这只是一个问题或组织。
例:
请求 :
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(); } }
在上面的例子中,请求input是自动validation的,我们所要做的就是调用persist方法并传入一个新的Post。 我认为可读性和可维护性应该总是胜过复杂和不需要的devise模式。
然后,您可以使用完全相同的持久方法更新post,因为我们可以检查post是否已经存在,并在需要时执行交替逻辑。