Heim  >  Artikel  >  PHP-Framework  >  Der Charme des Single-Behavior-Controller-Designs von Laravel

Der Charme des Single-Behavior-Controller-Designs von Laravel

Guanhui
Guanhuinach vorne
2020-05-22 11:24:403440Durchsuche

Der Charme des Single-Behavior-Controller-Designs von Laravel

Gestern hat Jeffrey Way einen Tweet gepostet, in dem er die Leute fragte, ob sie ihre Controller lieber im Singular oder im Plural nennen würden. Ich antwortete, dass ich keine der beiden Optionen wähle, sondern einen Single-Action-Controller verwende. Was als nächstes passiert, ist, dass einige Leute zustimmen, andere nicht und wieder andere die seltsamsten Dinge tun.

Aufgrund der überwältigenden Resonanz wollte ich einen Beitrag schreiben, in dem ich erkläre, warum ich Single-Behaviour-Controller liebe und warum ich sie wunderbar finde.

Bevor ich mit dem Artikel beginne, möchte ich zunächst sagen, dass es zu dieser Sache nicht nur eine Wahrheit gibt. Wie immer möchte ich darauf hinweisen, dass alles von Ihren persönlichen Vorlieben abhängt. Ich kann nur Dinge lehren, vorschlagen und aufzeigen, und es liegt an Ihnen, zuzustimmen, nicht zuzustimmen, zu akzeptieren, zu lernen und/oder sich anzupassen. Oder auch nicht. Nehmen Sie sich aus diesem Blog heraus, was Sie wollen, und tun Sie einfach das, womit Sie sich wohl fühlen.

Vergleich von CRUD und Domänenmodellierung

Bevor wir beginnen, denken wir zunächst über unsere Tendenz nach, einfallsreiche CRUD-Controller zu schreiben. Ich bin mir sicher, dass viele Leute bei diesem Ansatz bleiben werden, da er in Laravel eine Standardpraxis ist und die meisten Beispiele in der Dokumentation diesen Ansatz verwenden. Darüber hinaus ist dies möglicherweise auch etwas, das Sie häufig in verschiedenen Blogs oder im App-Code sehen.

Aber wenn Sie innehalten und darüber nachdenken, ist das die beste Art, sie zu schreiben? Ist das eine gängige Praxis in der Softwarebranche? In den letzten Jahren habe ich viel Zeit in Bereichen wie Domain Driven Design verbracht und darüber nachgedacht, wie Software auf die Domäne (Domian), in der Sie arbeiten, anwendbar ist und wie sie übersetzt wird. Wenn Sie anfangen, über Terminologie und Formulierungen nachzudenken, die die in Ihrem Fachgebiet allgegenwärtige Sprache nachahmen, werden Sie feststellen, dass Ihr Code klarer und auf den Punkt gebracht wird. (Dieser letzte Satz ist immer noch eine Überlegung und Verbesserung wert)

Abschließend glaube ich, dass das Wesentliche beim Schreiben von Software darin besteht, Domänenprozesse so weit wie möglich anzuwenden, um Ihren Code lesbarer und wartbarer zu machen.

Der einfallsreiche Controller beherrscht diese beiden Dinge nicht sehr gut. Erstens sind sie weniger lesbar, weil man sie eher nach den Daten als nach der Domäne strukturiert. In diesem Fall verlieren Sie die kontextuelle Kontrolle. Sie zeigen, wie die Daten verarbeitet werden, erklären aber nicht, was genau passiert oder mit welchem ​​Verfahren Sie sie verarbeiten.

Zweitens optimieren Sie nicht die Wartbarkeit. Da Sie auf der Grundlage von Datenstrukturen aufbauen, koppeln Sie sich auch an diese an. Tatsächlich entwickelt sich Ihr Domänenmodell ständig weiter, ebenso wie Ihre Datenstrukturen. Wenn Ihre Datenstruktur mehrere Prozesse oder mehrere Teile der Domäne abwickelt, wird es schwierig sein, sie anzupassen.

Ein praktisches Beispiel

Da Theorie langweilig ist und sich mit Code leichter erklären lässt, schauen wir uns ein praktisches Beispiel an.

Angenommen, Sie erstellen eine Anwendung, die es Benutzern ermöglicht, Veranstaltungen zu organisieren. Sie möchten eine Möglichkeit bieten, diese Ereignisse zu erstellen, zu aktualisieren und zu löschen. Dies ist ein sehr typisches Beispiel dafür, wie Sie über die Implementierung in CRUD-Begriffen nachdenken würden. Schauen wir uns also an, wie sich ein so einfallsreicher Controller verwandelt.

Werfen wir zunächst einen Blick auf das Routing:

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']);

Jetzt der entsprechende Controller:

<?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)
    {
        // ...
    }
}

Dieser EventController verarbeitet alle CRUD-Anfragen, zeigt die Ereignisliste an und zeigt die angegebenen an Ereignis erstellen, ein vorhandenes Ereignis aktualisieren und ein Ereignis löschen.

Werfen wir einen Blick auf die Details der Indexmethode:

public function index()
{
    $events = Event::paginate(10);
    return view(&#39;events.index&#39;, compact(&#39;events&#39;));
}

Bei dieser Methode rufen wir die Ereignisse ab und übergeben sie dann an die Ansicht, um sie in einer Paging-Liste anzuzeigen. So weit, ist es gut. Jetzt möchten Sie jedoch eine Methode implementieren, um auf verschiedenen Seiten vergangene und bevorstehende Ereignisse anzuzeigen. Mal sehen, wie man es in der Indexmethode implementiert:

public function index(Request $request)
{
    if ($request->boolean(&#39;past&#39;)) {
        $events = Event::past()->paginate(10);
    } elseif ($request->boolean(&#39;upcoming&#39;)) {
        $events = Event::upcoming()->paginate(10);
    } else {
        $events = Event::paginate(10);
    }
    return view(&#39;events.index&#39;, compact(&#39;events&#39;));
}

Ugh! Es sieht so chaotisch aus. Obwohl wir Eloquent-Bereiche verwendet haben, um die Abfragelogik zu verbergen, haben wir immer noch hässliche verkettete Anweisungen. Sehen wir uns an, wie wir dies durch einen einzelnen Verhaltenscontroller ersetzen können.

Jeder Einzelverhaltenscontroller macht eine Sache, und zwar nur eine.

Anstatt Abfrageparameter zu verwenden, um unterschiedliche Ereignislisten zu erhalten, verwenden wir zunächst dedizierte Routen, um dies zu erreichen.

Route::get(&#39;events&#39;, ShowAllEventsController::class);
Route::get(&#39;events/past&#39;, ShowPastEventsController::class);
Route::get(&#39;events/upcoming&#39;, ShowUpcomingEventsController::class);

Diese Route ist etwas länger als die vorherige, aber diese ist ausdrucksvoller als die vorherige. Sie können auf einen Blick erkennen, welcher Controller welche spezifische Logik verwaltet. Wenn Sie die URLs vergleichen, werden Sie eine gewisse Verbesserung der Lesbarkeit feststellen:

# Before
/events
/events?past=true
/events?upcoming=true
# After
/events
/events/past
/events/upcoming

Schauen Sie sich nun einen der Controller an. Schauen Sie sich einfach den ShowUpcomingEventsController-Controller an:

<?php
namespace App\Http\Controllers;
use App\Models\Event;
final class ShowUpcomingEventsController
{
    public function __invoke()
    {
        $events = Event::upcoming()->paginate(10);
        return view(&#39;events.index&#39;, compact(&#39;events&#39;));
    }
}

Die hässliche if-Anweisung ist weg und hat den gleichen lesbaren drei Zeilen Platz gemacht, die wir aus unserem ersten CRUD-Controller-Beispiel hatten, aber anstelle aller anderen CRUD-Operationen Wir haben jetzt einen dedizierten Controller für eine dedizierte Aktion.

Einfach, leicht zu lesen und leicht zu warten.

你可能会问自己,这样做值么,毕竟之前的 if 语句也没那么坏吧?但是我想向你展示的是你正在为未来的改进做优化,并改进维护性。下次你想要对这三个页面做任何指定改变的时候,你会知道在哪里改,并且不需要艰难地更新一个 if 语句。

当然,上面的例子很简单,我们来看一个更复杂一点的。我们试试重构 create 和 store 方法:

public function create()
{
    return view(&#39;events.create&#39;);
}
public function store(Request $request)
{
    $data = $request->validate([
        &#39;name&#39; => &#39;required&#39;,
        &#39;start&#39; => &#39;required&#39;,
        &#39;end&#39; => &#39;required|after:start&#39;,
    ])
    $event = Event::create($data);
    return redirect()->route(&#39;event.show&#39;, $event);
}

我们要做的就是把这两个方法移到专用的控制器,这样更好地解释了这些方法做了啥。这些方法更好地服务于你,比起把它们放在一个叫做 ScheduleNewEventController 的控制器中。我们接着更新这个控制器的路由:

Route::get(&#39;events/schedule&#39;, [ScheduleNewEventController::class, &#39;showForm&#39;]);
Route::post(&#39;events/schedule&#39;, [ScheduleNewEventController::class, &#39;schedule&#39;]);

我不会向你展示一个确切的控制器,因为它们有和上面的例子一样,有两个方法,只不过把 showForm 和 schedule 重新命名为更能表达它们干了啥的名字。即使这个不是单行为控制器,但是方法论是一样的:把你应用中的专用行为(方法)和它对应的控制器拆分到一起。

好了,现在你已经看了单行为控制器的例子了。你可能会想,这会导致越来越多的文件。但事实上,这个根本就不是问题。文件多又没啥。有更多、更小、更容易维护的文件比有更大、更难分析的要好。你可以打开一个单行为控制器的文件,然后快速扫描代码,马上就能知道这是干嘛的。

我经常把他们分组到不同的目录,这些目录负责领域的各个部分。这让你从文件结构的角度看控制器时,更加容易。

拆分控制器也让你跟容易找到特定的一个控制器。想象一下,你要寻找那个可以安排事件的控制器时。现在你只需要按照文件名搜索编辑器,而不是一个通用的 EventController。

其他情况

我也被问到是否要对所有控制器执行此操作。不总是。在命名控制器时,我倾向于严谨且简洁,但我也会像你一样适应各种情况。

当然,有时候你还是想用 resourceful 控制器。比如在你构建 RESTful API 时。这样做是很有意义,因为你经常直接与数据本身交互,而没有经常与领域或任何进程进行交互。CMS(内容管理系统)或 Laravel Nova 等应用程序就是最好的例子。

但是在需要的时候,您最好问问自己的方案是否更接近领域和处理过程。在需要根据领域执行操作的时候,比如 GraphQL 之类的或 API 之类的 RPC ,这样做可能更适合。

结论

我希望这有一点见地,你现在能更理解我为什么如此喜欢单行为控制器了吧。我相信,结合小的 classes,再使用无处不在的语言、显式地命名,会带来更可维护的代码,甚至是控制器,不仅仅是领域对象。但是正如我开头所说,选择能帮助你的部分,好好分辨哪些适用于你,哪些不行。

推荐教程:《PHP教程》《Laravel教程

Das obige ist der detaillierte Inhalt vonDer Charme des Single-Behavior-Controller-Designs von Laravel. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen