在哪里添加逻辑

比方说,每当我执行 CRUD 操作或以特定方式修改关系时,我还想执行其他操作。例如,每当有人发表了一篇文章,我也想保存一些东西到一个表格进行分析。也许不是最好的例子,但总的来说有很多这样的“分组”功能。

通常我看到这种类型的逻辑放入控制器。这一切都很好,直到你想在许多地方重现这一功能。当您开始使用局部数据集、创建 API 和生成虚拟内容时,保持数据的干燥就成了一个问题。

我看到的管理方法是事件、存储库、库和向模型中添加。以下是我对每个问题的理解:

服务: 大多数人可能会把这段代码放在这里。我对服务的主要看法是,有时候很难在服务中找到特定的功能,我觉得当人们专注于使用 Eloquent 时,他们就会被遗忘。当我只能调用 $post->is_published = 1时,我怎么知道我需要在库中调用方法 publishPost()呢?

我认为唯一可行的条件是,只使用服务(理想情况下,让所有控制器以某种方式都无法访问 Eloquent)。

最终,如果您的请求通常遵循您的模型结构,那么这似乎只会创建一堆额外的不必要的文件。

存储库: 据我所知,这基本上就像一个服务,但是有一个接口,所以你可以在 ORM 之间切换,这是我不需要的。

Events: 从某种意义上说,我认为这是最优雅的系统,因为您知道模型事件总是在 Eloquent 方法上调用,所以您可以像通常那样编写控制器。我可以看到这些变得混乱,但是,如果任何人有大型项目使用事件的关键耦合的例子,我希望看到它。

模型: 传统上,我拥有执行 CRUD 并处理关键耦合的类。这实际上使事情变得很简单,因为您知道 CRUD + 周围的所有功能都在那里。

很简单,但是在 MVC 架构中这不是我通常看到的。从某种意义上说,尽管我更喜欢这种服务,因为它更容易找到,而且要跟踪的文件更少。不过可能会有点杂乱无章。我想听听这种方法的缺点,以及为什么大多数人似乎不这样做。

每种方法的优缺点是什么? 我是否遗漏了什么?

69705 次浏览

我用来创建控制器和模型之间的逻辑的方法是创建一个 服务层。基本上,这是我的应用程序中的任何操作流程:

  1. 控制器获取用户请求的操作并发送参数并将所有内容委托给服务类。
  2. 服务类执行与操作相关的所有逻辑: 输入验证、事件日志记录、数据库操作等等。
  3. 模型包含字段、数据转换和属性验证定义的信息。

我是这么做的:

这是控制器创建某些东西的方法:

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

我认为,只要遵循 很可靠原则,您提出的所有模式/体系结构都非常有用。

对于 在哪里加入逻辑,我认为参考 单一责任原则很重要。另外,我的回答认为您正在从事一个中型/大型项目。如果它是一个 把什么东西扔到纸上项目,忘记这个答案,把它全部添加到控制器或模型。

简短的回答是: 你认为有意义的地方(服务)

长话短说:

控制器 : 控制器的责任是什么?当然,你可以把你所有的逻辑放在一个控制器中,但这是控制器的责任吗?我不这么认为。

对于我来说,控制器必须接收请求并返回数据,这不是放置验证、调用 db 方法等的地方。.

Model : 这是一个添加逻辑的好地方吗,比如当用户注册时发送一封欢迎邮件或者更新一篇文章的投票计数?如果您需要从代码中的其他位置发送相同的电子邮件,该怎么办?您创建了静态方法吗?如果电子邮件需要其他模型的信息呢?

我认为模型应该代表一个实体。对于 Laravel,我只使用模型类来添加类似于 fillableguardedtable和关系(这是因为我使用了 Repository Pattern,否则模型也会有 saveupdatefind等方法)。

存储库(存储库模式) : 一开始我对此感到非常困惑。和你一样,我想“好吧,我使用 MySQL,就是这样。”.

然而,我已经权衡了使用存储库模式的利弊,现在我使用它。我认为 现在,在这个非常时刻,我将只需要使用 MySQL。但是,如果三年后我需要更改为 MongoDB 之类的东西,那么大部分工作已经完成了。所有这些都以一个额外的接口和一个 $app->bind(«interface», «repository»)为代价。

Events (观察者模式) : Events 对于可以在任何给定时间在任何类中抛出的内容非常有用。例如,考虑向用户发送通知。 当需要时,可以激发事件以向应用程序的任何类发送通知。然后,可以使用类似 UserNotificationEvents的类来处理用户通知的所有已激发事件。

Services : 到目前为止,您可以选择向控制器或模型添加逻辑。对于我来说,在服务中添加逻辑是非常有意义的.面对现实吧,服务对于类来说是个花哨的名字。您可以在应用程序中使用任意多的类。

以这个例子为例: 不久前,我开发了类似于 Google Forms 的东西。我从 CustomFormService开始,最后是 CustomFormServiceCustomFormRenderCustomFieldServiceCustomFieldRenderCustomAnswerServiceCustomAnswerRender。为什么?因为我觉得有道理。如果你和一个团队一起工作,你应该把你的逻辑放在对团队有意义的地方。

使用服务与控制器/模型的优点是您不受单个控制器或单个模型的约束。您可以根据应用程序的设计和需求创建任意多个所需的服务。此外,还可以在应用程序的任何类中调用 Service。

这个过程很长,但我想向您展示我是如何构建我的应用程序的:

app/
controllers/
MyCompany/
Composers/
Exceptions/
Models/
Observers/
Sanitizers/
ServiceProviders/
Services/
Validators/
views
(...)

我使用每个文件夹的特定功能。例如,Validators目录包含一个 BaseValidator类,该类基于特定验证器的 $rules$messages(通常每个模型一个)负责处理验证。我可以很容易地将这段代码放到一个服务中,但是对我来说,为它设置一个特定的文件夹是有意义的,即使它只在服务中使用(目前)。

我建议你阅读下面的文章,因为它们可能会对你更好地解释一些事情:

作者: Dayle Rees (CodeBright 的作者) : 这是我把所有东西放在一起的地方,尽管我改变了一些东西来满足我的需要。

在 Laravel 使用存储库和服务解耦你的代码作者: chrisgoosey: 这篇文章很好地解释了什么是服务和存储库模式,以及它们是如何结合在一起的。

Laracast 也有 仓库简化版单一责任,这是很好的资源与实际例子(即使你必须支付)。

我想回复我自己的问题。我可以谈论这个好几天,但是我会尽快把这个发布出去,以确保我能够站起来。

我最终使用了 Laravel 提供的现有结构,这意味着我将文件主要保存为模型、视图和控制器。我还有一个用于可重用组件的 Library 文件夹,这些组件并非真正的模型。

我没有将我的模型包装在服务/库中。所有提供的理由并没有100% 地说服我使用服务的好处。虽然我可能是错的,但就我所知,它们只会导致我在处理模型时需要创建和切换的大量额外的几乎为空的文件,而且真的降低了使用雄辩的好处(特别是当涉及到检索模型时,例如,使用分页、作用域等)。

我把业务逻辑 在模特身上和访问雄辩直接从我的控制器。我使用了许多方法来确保业务逻辑不会被绕过:

  • 访问器和变异器: Laravel 有很好的访问器和变异器。如果我想在文章从草稿移动到发布时执行一个操作,我可以通过创建函数 setIsPublishedAttribute 并在其中包含逻辑来调用它
  • 覆盖 Create/Update etc: 您总是可以覆盖模型中的 Eloquent 方法以包含自定义功能。这样,您就可以调用任何 CRUD 操作的功能。编辑: 我认为在较新的 Laravel 版本中有一个重写 create 的 bug (所以我现在使用在启动中注册的事件)
  • 验证: 我用同样的方式钩住我的验证,例如,我将通过覆盖 CRUD 函数和必要的访问器/变异器来运行验证。更多信息请参见 Esensi 或 dwightwatson/validating。
  • Magic Method: 我使用模型的 _ _ get 和 _ _ set 方法在适当的地方连接到功能
  • 扩展雄辩: 如果有一个动作你想采取的所有更新/创建,你甚至可以扩展雄辩并应用到多个模型。
  • 事件: 这是一个直截了当的,普遍认同的地方。我认为事件最大的缺点是很难跟踪异常(Laravel 的新事件系统可能不会出现这种情况)。我还喜欢根据事件的功能而不是它们被调用的时间来对它们进行分组... ... 例如,有一个 MailSender 订阅服务器,该订阅服务器侦听发送邮件的事件。
  • 添加 Pivot/BelongsTomany Events: 我最纠结的事情之一就是如何将行为附加到归属关系的修改中。例如,当用户加入一个组时执行一个操作。我差不多完成了这个定制图书馆的润色工作。我还没有出版它,但它的功能!将尝试发布一个链接很快。我最终把我所有的支点变成了正常的模型,我的生活变得容易多了..。

利用模型解决人们的担忧:

  • 组织: 是的,如果你在模型中加入更多的逻辑,它们可以更长,但总的来说,我发现75% 的模型仍然相当小。如果我选择组织较大的文件,我可以使用 trait (例如,根据需要为模型创建一个包含更多文件的文件夹,比如 PostScope、 PostAccessor、 PostValization 等)。我知道这不一定是什么特质,但这个系统工作没有问题。

附加说明: 我觉得在服务中包装你的模型就像拥有一把瑞士军刀,里面有很多工具,然后在它周围制造另一把基本上做同样事情的刀?是的,有时候你可能想要把刀片粘起来或者确保两个刀片一起使用... 但是通常还有其他方法可以做到..。

什么时候使用服务 : 本文很好地阐述了什么时候使用服务(提示: 这并不常见)的很好的例子。他说,基本上,当你的对象使用 在其生命周期的奇怪部分的多个模型或模型它是有意义的。译自: 美国《 http://www.justinweiss.com/articles/where-do-you-put-your-code/》杂志网站(http://www.ustinweiss.com/article/where-do-you-put-your-code/”rel = “ norefrer”)

在我看来,Laravel 已经为您提供了许多存储业务逻辑的选项。

简短的回答:

  • 使用 Laravel 的 Request对象自动验证您的输入,然后在请求中保存数据(创建模型)。因为所有的用户输入都是可以直接使用 进去请求的,所以我相信在这里执行这个操作是有意义的。
  • 使用 Laravel 的 Job对象执行需要单个组件的任务,然后简单地分派它们。我认为 Job包括服务类。它们执行一项任务,例如业务逻辑。

长答案:

必要时使用仓库: 存储库注定会过度膨胀,而且大多数时候,它只是作为模型的 accessor使用。我觉得他们肯定有一些用途,但除非你正在开发一个大型应用程序,需要的灵活性,使您能够完全抛弃 Laravel,远离存储库。稍后您会感谢自己,您的代码将更加直接。

问问自己是否有可能将 PHP 框架 或者更改为 Laravel 不支持的数据库类型。

如果您的答案是“可能不会”,那么就不要实现存储库模式。

除此之外,请不要拍一个模式顶部的一个极好的 ORM 像雄辩。你只是增加了不必要的复杂性,这对你一点好处都没有。

节约使用服务: 对我来说,服务类只是一个存储业务逻辑的地方,用于执行具有给定依赖关系的特定任务。Laravel 开箱即用,称为“ Jobs”,它们比定制的 Service 类具有更大的灵活性。

我觉得 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
{
public function rules()
{
return [
'title'       => 'required',
'description' => 'required'
];
}


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;


$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 $post;
}
}

控制器 :

namespace App\Http\Controllers;


use App\Post;
use App\Http\Requests\PostRequest;


class PostController extends Controller
{
public function store(PostRequest $request)
{
$request->persist(new Post());


flash()->success('Successfully created new post!');
        

return redirect()->back();
}


public function update(PostRequest $request, Post $post)
{
$request->persist($post);


flash()->success('Successfully updated post!');
        

return redirect()->back();
}
}

在上面的示例中,将自动验证请求输入,我们所需要做的就是调用存储方法并传入一个新的 Post。我认为可读性和可维护性应该总是胜过复杂和不必要的设计模式。

然后,您可以使用完全相同的持久化方法来更新文章,因为我们可以检查文章是否已经存在,并在需要时执行交替逻辑。