假设每当我执行 CRUD 操作或以特定方式修改关系时,我还想做其他事情。例如,每当有人发布帖子时,我还想将某些内容保存到表格中以进行分析。也许不是最好的例子,但总的来说有很多这种“分组”功能。
通常我会看到将这种类型的逻辑放入控制器中。在您想在很多地方重现此功能之前,这一切都很好。当您开始研究部分内容、创建 API 并生成虚拟内容时,保持内容 DRY 成为一个问题。
我见过的管理它的方法是事件、存储库、库和添加到模型。以下是我对每一个的理解:
服务:这是大多数人可能会放置此代码的地方。我对服务的主要问题是,有时很难在其中找到特定的功能,我觉得当人们专注于使用 Eloquent 时,它们会被遗忘。当我可以执行 $post->is_published = 1
时,我怎么知道我需要调用库中的方法 publishPost()
?
我认为这很好用的唯一条件是你只使用服务(理想情况下,让 Eloquent 无法从控制器中以某种方式访问)。
最终,如果您的请求通常遵循您的模型结构,这似乎只会创建一堆额外的不必要的文件。
存储库:据我了解,这基本上就像一个服务,但有一个接口,因此您可以在 ORM 之间切换,我不需要。
事件:从某种意义上说,我认为这是最优雅的系统,因为您知道模型事件总是会在 Eloquent 方法上调用,因此您可以像往常一样编写控制器。我可以看到这些变得混乱,如果有人有使用事件进行关键耦合的大型项目的示例,我希望看到它。
模型:传统上我会拥有执行 CRUD 并处理关键耦合的类。这实际上使事情变得容易,因为您知道围绕 CRUD 的所有功能 + 在那里必须完成的任何事情。
很简单,但在 MVC 架构中,这通常不是我所看到的。从某种意义上说,虽然我更喜欢它而不是服务,因为它更容易找到,而且需要跟踪的文件更少。虽然它可能会有点杂乱无章。我想听听这种方法的失败以及为什么大多数人似乎不这样做。
每种方法的优点/缺点是什么?我错过了什么吗?
只要您遵循SOLID原则,我认为您展示的所有模式/架构都非常有用。
对于添加逻辑的位置,我认为参考 Single Responsibility Principle 很重要。另外,我的回答认为您正在从事一个中/大型项目。如果它是 throw-something-on-a-page 项目,请忘记此答案并将其全部添加到控制器或模型中。
简短的回答是:对您有意义的地方(通过服务)。
长答案:
控制器:控制器的职责是什么?当然,您可以将所有逻辑放在控制器中,但这是控制器的责任吗?我不这么认为。
对我来说,控制器必须接收请求并返回数据,这不是放置验证、调用数据库方法等的地方。
模型:这是一个添加逻辑的好地方,比如在用户注册或更新帖子的投票计数时发送欢迎电子邮件?如果您需要从代码中的其他位置发送相同的电子邮件怎么办?你创建一个静态方法吗?如果该电子邮件需要来自另一个模型的信息怎么办?
我认为模型应该代表一个实体。使用 Laravel,我只使用模型类添加 fillable
、guarded
、table
和关系(这是因为我使用存储库模式,否则模型也会有 save
、{5 }、find
等方法)。
存储库(Repository Pattern):一开始我对此很困惑。而且,像你一样,我想“好吧,我使用 MySQL 就是这样。”。
但是,我已经平衡了使用存储库模式的利弊,现在我使用它。我想现在,此时此刻,我只需要使用 MySQL。但是,如果三年后我需要改用 MongoDB 之类的东西,那么大部分工作都已经完成了。所有这些都以一个额外的接口和一个 $app->bind(«interface», «repository»)
为代价。
事件 (Observer Pattern): 事件对于可以在任何给定时间在任何类中抛出的东西很有用。例如,考虑向用户发送通知。当您需要时,您可以触发事件以在应用程序的任何类中发送通知。然后,您可以拥有像 UserNotificationEvents
这样的类来处理您为用户通知而触发的所有事件。
服务:到目前为止,您可以选择向控制器或模型添加逻辑。对我来说,在服务中添加逻辑是很有意义的。让我们面对现实吧,服务是类的一个花哨的名称。你可以在你的应用程序中拥有对你有意义的尽可能多的类。
举个例子:不久前,我开发了类似谷歌表单的东西。我从 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 的作者):这是我将所有内容放在一起的地方,尽管我更改了一些内容以满足我的需要。
Decoupling your code in Laravel using Repositories and Services by Chris Goosey:这篇文章很好地解释了什么是服务和存储库模式以及它们如何结合在一起。
Laracasts 也有 Repositories Simplified 和 Single Responsibility,它们是带有实际示例的好资源(即使您必须付费)。
我想对我自己的问题发表回应。我可以谈论这个几天,但我会尽量让这个发布快,以确保我得到它。
我最终利用了 Laravel 提供的现有结构,这意味着我主要将文件保存为模型、视图和控制器。我还有一个库文件夹,用于存放并非真正模型的可重用组件。
我没有将我的模型包装在服务/库中。提供的所有理由并没有 100% 让我相信使用服务的好处。虽然我可能是错的,但据我所见,它们只会导致我需要在使用模型时创建和切换大量几乎空的文件,并且还确实降低了使用 eloquent 的好处(尤其是在检索模型时,例如,使用分页、范围等)。
我将业务逻辑放在模型中,并直接从我的控制器访问 eloquent。我使用了多种方法来确保业务逻辑不会被绕过:
访问器和修改器:Laravel 有很好的访问器和修改器。如果我想在帖子从草稿移动到已发布时执行操作,我可以通过创建函数 setIsPublishedAttribute 并在其中包含逻辑来调用它
覆盖创建/更新等:您始终可以覆盖模型中的 Eloquent 方法以包含自定义功能。这样您就可以在任何 CRUD 操作上调用功能。编辑:我认为在较新的 Laravel 版本中覆盖创建存在一个错误(所以我使用现在在引导中注册的事件)
验证:我以同样的方式挂钩我的验证,例如,如果需要,我将通过覆盖 CRUD 函数和访问器/突变器来运行验证。有关更多信息,请参阅 Esensi 或 dwightwatson/validating。
魔术方法:我使用模型的 __get 和 __set 方法在适当的地方挂钩功能
扩展 Eloquent:如果您想对所有更新/创建执行操作,您甚至可以扩展 eloquent 并将其应用于多个模型。
事件:这是一个直截了当且普遍同意的地方。我认为事件的最大缺点是异常难以追踪(可能不是 Laravel 新事件系统的新案例)。我还喜欢按照事件的执行而不是调用的时间对事件进行分组……例如,有一个 MailSender 订阅者,它侦听发送邮件的事件。
添加 Pivot/BelongsToMany 事件:我最苦恼的一件事是如何将行为附加到 belongsToMany 关系的修改中。例如,每当用户加入群组时执行操作。我几乎已经为此完善了一个自定义库。我还没有发布它,但它是功能性的!将尝试尽快发布链接。编辑我最终把我所有的支点都变成了正常的模型,我的生活变得轻松多了......
解决人们对使用模型的担忧:
组织:是的,如果您在模型中包含更多逻辑,它们可能会更长,但总的来说,我发现 75% 的模型仍然很小。如果我选择组织较大的文件,我可以使用特征来完成(例如,根据需要为模型创建一个文件夹,其中包含更多文件,如 PostScopes、PostAccessors、PostValidation 等)。我知道这不一定是特征的用途,但这个系统可以正常工作。
附加说明:我觉得将您的模型包装在服务中就像拥有一把瑞士军刀,有很多工具,然后围绕它建造另一把基本上做同样事情的刀?是的,有时您可能想用胶带粘住刀片或确保两个刀片一起使用......但通常还有其他方法可以做到......
何时使用服务:这篇文章很好地阐述了何时使用服务的好例子(提示:不经常)。他说,基本上,当您的对象在其生命周期的奇怪部分使用多个模型或模型时,这是有道理的。 http://www.justinweiss.com/articles/where-do-you-put-your-code/
我用来创建控制器和模型之间的逻辑的方法是创建一个服务层。基本上,这是我在应用程序中执行任何操作的流程:
控制器获取用户请求的操作并发送参数并将所有内容委托给服务类。服务类执行与操作相关的所有逻辑:输入验证、事件记录、数据库操作等。模型保存字段信息、数据转换和属性验证定义。
我就是这样做的:
这是控制器创建某些东西的方法:
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
$congregation->save();
,那么您可能不需要存储库。但是,您可能会看到您的数据访问需求随着时间的推移而增加。您可能开始对 $congregation->destroyByUser()
或 $congregationUsers->findByName($arrayOfSelectedFields);
等有需求。为什么不将您的服务与数据访问需求分离。让您的应用程序的其余部分使用从存储库返回的对象/数组,并仅处理操作/格式化/等...您的存储库将增长(但将它们拆分为不同的文件,最终项目的复杂性必须驻留在某个地方)。
在我看来,Laravel 已经为你提供了很多存储业务逻辑的选项。
简短的回答:
使用 Laravel 的 Request 对象自动验证您的输入,然后将数据持久化在请求中(创建模型)。由于所有用户输入都直接在请求中可用,我相信在这里执行此操作是有意义的。
使用 Laravel 的 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
{
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();
}
}
在上面的例子中,请求输入是自动验证的,我们需要做的就是调用persist方法并传入一个新的Post。我认为可读性和可维护性应该总是胜过复杂和不需要的设计模式。
然后,您也可以使用完全相同的持久方法来更新帖子,因为我们可以检查帖子是否已经存在并在需要时执行交替逻辑。
ShouldQueue
上实现接口来指定。如果您想在命令或事件中编写业务逻辑,只需在这些事件/命令中触发作业。 Laravel 作业非常灵活,但最终它们只是简单的服务类。
dispatchSync()
方法也可以立即使用作业,该方法应忽略 ShouldQueue
。
不定期副业成功案例分享