昨日、Jeffrey Way 氏は、コントローラーの名前を単数にするか複数にするかを人々に尋ねるツイートを投稿しました。私はどちらのオプションも選択せず、シングルアクションコントローラーを使用すると答えました。次に何が起こるかというと、同意する人もいれば同意しない人もいて、最も奇妙なことをする人もいます。
あまりにも多くの反響があったので、なぜ私がシングルアクションコントローラーを愛し、なぜ素晴らしいと思うのかを説明する記事を書きたいと思いました。
まず、記事を始める前に、これは単一の真実ではないことを言っておきたいと思います。いつものように、すべてはあなたの個人的な好みによるということを指摘したいと思います。私は物事を教え、提案し、指摘することしかできません。同意するか反対するか、受け入れ、学習し、調整するかはあなた次第です。あるいは、どちらでもない。このブログから必要なことを取り入れて、自分が快適に感じることを何でもしてください。
CRUD とドメイン モデリングの比較
始める前に、まずリソース豊富な CRUD コントローラーを作成する傾向について考えてみましょう。これは Laravel の標準的な手法であり、ドキュメント内のほとんどの例でこのアプローチが使用されているため、多くの人がこのアプローチに固執すると確信しています。また、これはさまざまなブログやアプリのコードでもよく見かけるものかもしれません。
しかし、立ち止まって考えてみると、これが最適な書き方なのでしょうか?ソフトウェア業界ではこれが一般的なのでしょうか?近年、私はドメイン駆動設計などの分野に多くの時間を費やし、ソフトウェアが作業しているドメイン (Domian) にどのように適用され、どのように変換されるかを考えてきました。自分の分野のユビキタス言語を模倣した用語や表現について考え始めると、コードがより明確になり、より要点を絞ったものになることがわかります。 (この最後の文はまだ検討し、改善する価値があります)
最後に、ソフトウェアを書くことの本質は、コードをより読みやすく、保守しやすくするために可能な限りドメイン プロセスを使用することであると私は信じています。
リソースフル コントローラーは、これら 2 つの側面をうまく実行できません。まず、ドメインという観点ではなくデータという観点で構造化する傾向があるため、読みにくくなります。この場合、状況に応じた制御が失われます。データがどのように処理されるかを示していますが、正確に何が起こるのか、またはデータを処理するためにどのプロセスを使用するのかについては説明していません。
第二に、保守性を考慮して最適化を行っていません。データ構造の観点から構築しているので、データ構造とも結合しています。実際、ドメイン モデルは常に進化しており、データ構造も進化しています。データ構造が複数のプロセスやドメインの複数の部分を処理する場合、調整は困難になります。
#実践例
理論は退屈で、コードの方が説明しやすいので、実践的な例を見てみましょう。 ユーザーがイベントを開催できるアプリケーションを構築しているとします。これらのイベントを作成、更新、削除する方法を提供したいと考えています。これは、CRUD の観点から実装をどのように考えるかを示す非常に典型的な例です。それでは、このようなリソース豊富なコントローラーがどのように変換されるかを見てみましょう。 最初にルーティングを見てみましょう:Route::get('events', [EventController::class, 'index']); Route::get('events/create', [EventController::class, 'create']); Route::post('events', [EventController::class, 'store']); Route::get('event/{event}', [EventController::class, 'show']); Route::get('events/{event}/edit', [EventController::class, 'edit']); Route::put('events/{event}', [EventController::class, 'update']); Route::destroy('events/{event}', [EventController::class, 'destroy']);
<?php namespace App\Http\Controllers; use App\Models\Event; final class EventController { public function index() { // ... } public function create() { // ... } public function store() { // ... } public function show(Event $event) { // ... } public function edit(Event $event) { // ... } public function update(Event $event) { // ... } public function destroy(Event $event) { // ... } }
public function index() { $events = Event::paginate(10); return view('events.index', compact('events')); }
public function index(Request $request) { if ($request->boolean('past')) { $events = Event::past()->paginate(10); } elseif ($request->boolean('upcoming')) { $events = Event::upcoming()->paginate(10); } else { $events = Event::paginate(10); } return view('events.index', compact('events')); }
Route::get('events', ShowAllEventsController::class); Route::get('events/past', ShowPastEventsController::class); Route::get('events/upcoming', ShowUpcomingEventsController::class);
# Before /events /events?past=true /events?upcoming=true # After /events /events/past /events/upcoming
<?php namespace App\Http\Controllers; use App\Models\Event; final class ShowUpcomingEventsController { public function __invoke() { $events = Event::upcoming()->paginate(10); return view('events.index', compact('events')); } }
你可能会问自己,这样做值么,毕竟之前的 if 语句也没那么坏吧?但是我想向你展示的是你正在为未来的改进做优化,并改进维护性。下次你想要对这三个页面做任何指定改变的时候,你会知道在哪里改,并且不需要艰难地更新一个 if 语句。
当然,上面的例子很简单,我们来看一个更复杂一点的。我们试试重构 create 和 store 方法:
public function create() { return view('events.create'); } public function store(Request $request) { $data = $request->validate([ 'name' => 'required', 'start' => 'required', 'end' => 'required|after:start', ]) $event = Event::create($data); return redirect()->route('event.show', $event); }
我们要做的就是把这两个方法移到专用的控制器,这样更好地解释了这些方法做了啥。这些方法更好地服务于你,比起把它们放在一个叫做 ScheduleNewEventController 的控制器中。我们接着更新这个控制器的路由:
Route::get('events/schedule', [ScheduleNewEventController::class, 'showForm']); Route::post('events/schedule', [ScheduleNewEventController::class, 'schedule']);
我不会向你展示一个确切的控制器,因为它们有和上面的例子一样,有两个方法,只不过把 showForm 和 schedule 重新命名为更能表达它们干了啥的名字。即使这个不是单行为控制器,但是方法论是一样的:把你应用中的专用行为(方法)和它对应的控制器拆分到一起。
好了,现在你已经看了单行为控制器的例子了。你可能会想,这会导致越来越多的文件。但事实上,这个根本就不是问题。文件多又没啥。有更多、更小、更容易维护的文件比有更大、更难分析的要好。你可以打开一个单行为控制器的文件,然后快速扫描代码,马上就能知道这是干嘛的。
我经常把他们分组到不同的目录,这些目录负责领域的各个部分。这让你从文件结构的角度看控制器时,更加容易。
拆分控制器也让你跟容易找到特定的一个控制器。想象一下,你要寻找那个可以安排事件的控制器时。现在你只需要按照文件名搜索编辑器,而不是一个通用的 EventController。
其他情况
我也被问到是否要对所有控制器执行此操作。不总是。在命名控制器时,我倾向于严谨且简洁,但我也会像你一样适应各种情况。
当然,有时候你还是想用 resourceful 控制器。比如在你构建 RESTful API 时。这样做是很有意义,因为你经常直接与数据本身交互,而没有经常与领域或任何进程进行交互。CMS(内容管理系统)或 Laravel Nova 等应用程序就是最好的例子。
但是在需要的时候,您最好问问自己的方案是否更接近领域和处理过程。在需要根据领域执行操作的时候,比如 GraphQL 之类的或 API 之类的 RPC ,这样做可能更适合。
结论
我希望这有一点见地,你现在能更理解我为什么如此喜欢单行为控制器了吧。我相信,结合小的 classes,再使用无处不在的语言、显式地命名,会带来更可维护的代码,甚至是控制器,不仅仅是领域对象。但是正如我开头所说,选择能帮助你的部分,好好分辨哪些适用于你,哪些不行。
以上がLaravelのシングルビヘイビアコントローラー設計の魅力の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。