Maison  >  Article  >  cadre php  >  Le charme de la conception du contrôleur Laravel à comportement unique

Le charme de la conception du contrôleur Laravel à comportement unique

Guanhui
Guanhuiavant
2020-05-22 11:24:403369parcourir

Le charme de la conception du contrôleur Laravel à comportement unique

Hier, Jeffrey Way a publié un tweet demandant aux gens s'ils préféraient nommer leurs contrôleurs au singulier ou au pluriel. J'ai répondu que je n'avais choisi aucune des deux options, j'utilisais un contrôleur à action unique. Ce qui se passe ensuite, c'est que certaines personnes sont d'accord, d'autres non, et certaines font les choses les plus étranges.

En raison de la réponse massive, j'ai voulu écrire un article expliquant pourquoi j'aime les contrôleurs de comportement uniques et pourquoi je les trouve merveilleux.

Tout d’abord, avant de commencer l’article, je tiens à dire qu’il n’y a pas qu’une seule vérité à propos de cette chose. Comme toujours, je tiens à souligner que tout dépend de vos préférences personnelles. Je ne peux qu'enseigner, suggérer et souligner des choses et c'est à vous d'être d'accord, pas d'accord, d'accepter, d'apprendre et/ou de vous ajuster. Ou ni l’un ni l’autre. Prenez ce que vous voulez de ce blog et faites tout ce qui vous met à l’aise.

Comparaison du CRUD et de la modélisation de domaine

Avant de commencer, réfléchissons d'abord à notre tendance à écrire des contrôleurs CRUD ingénieux. Je suis sûr que beaucoup de gens s'en tiendront à cette approche car c'est une pratique standard dans Laravel et la plupart des exemples de la documentation utilisent cette approche. De plus, cela peut aussi être quelque chose que vous voyez souvent dans divers blogs ou dans le code d’application.

Mais si vous y réfléchissez, est-ce la meilleure façon de les écrire ? Est-ce une pratique courante dans l’industrie du logiciel ? Ces dernières années, j'ai passé beaucoup de temps dans des domaines tels que la conception pilotée par domaine et j'ai réfléchi à la manière dont les logiciels s'appliquent au domaine (Domian) dans lequel vous travaillez et à la manière dont ils se traduisent. Lorsque vous commencerez à réfléchir à une terminologie et à une formulation qui imite le langage omniprésent dans votre domaine, vous constaterez que votre code deviendra plus clair et plus pertinent. (Cette dernière phrase mérite toujours d'être considérée et améliorée)

Enfin, je crois que l'essence de l'écriture de logiciels est d'appliquer autant que possible les processus de domaine pour rendre votre code plus lisible et maintenable.

Un contrôleur ingénieux ne fait pas très bien ces deux choses. Premièrement, ils sont moins lisibles car vous avez tendance à les structurer en termes de données plutôt qu’en termes de domaine. Dans ce cas, vous perdez le contrôle contextuel. Vous montrez comment les données sont traitées, mais vous n'expliquez pas ce qui se passe exactement ni quel processus vous utilisez pour les traiter.

Deuxièmement, vous n'optimisez pas la maintenabilité. Puisque vous construisez en termes de structures de données, vous vous y associez également. En fait, votre modèle de domaine évolue constamment, tout comme vos structures de données. Si votre structure de données gère plusieurs processus ou plusieurs parties du domaine, elle sera difficile à ajuster.

Un exemple pratique

Parce que la théorie est ennuyeuse et qu'elle est plus facile à expliquer avec du code, regardons donc un exemple pratique.

Supposons que vous construisiez une application qui permet aux utilisateurs d'organiser des événements. Vous souhaitez fournir un moyen de créer, mettre à jour et supprimer ces événements. Il s'agit d'un exemple très typique de la façon dont vous envisageriez de l'implémenter en termes CRUD. Voyons donc comment un contrôleur aussi ingénieux se transforme.

Jetons d'abord un coup d'œil au routage :

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

Maintenant, le contrôleur correspondant :

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

Cet EventController gère toutes les requêtes CRUD, affiche la liste des événements et affiche le spécifié événement. Créez un événement, mettez à jour un événement existant et supprimez un événement.

Jetons un coup d'œil aux détails de la méthode d'indexation :

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

Dans cette méthode, nous récupérons les événements puis les transmettons à la vue pour les afficher dans une liste de pagination. Jusqu'ici, tout va bien. Mais maintenant, vous souhaitez implémenter une méthode permettant d'utiliser différentes pages pour afficher les événements passés et à venir. Voyons comment l'implémenter dans la méthode index :

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 ! Ça a l'air tellement brouillon. Bien que nous ayons utilisé des étendues Eloquent pour masquer la logique de requête, nous avons toujours des instructions enchaînées laides. Voyons comment remplacer cela par un seul contrôleur de comportement.

Chaque contrôleur à comportement unique fait une chose, et une seule chose.

Premièrement, au lieu d'utiliser des paramètres de requête pour obtenir différentes listes d'événements, nous utilisons des routes dédiées pour y parvenir.

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

Ce parcours est un peu plus long que le précédent, mais celui-ci est plus expressif que le précédent. Vous pouvez identifier en un coup d’œil quel contrôleur gère quelle logique spécifique. Si vous comparez les URL, vous constaterez une certaine amélioration de la lisibilité :

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

Regardez maintenant l'un des contrôleurs. Il suffit de regarder le contrôleur ShowUpcomingEventsController :

<?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;));
    }
}

La moche instruction if a disparu et a fait place aux mêmes trois lignes lisibles que nous avions dans notre premier exemple de contrôleur CRUD, mais au lieu d'avoir toutes les autres opérations CRUD. nous disposons désormais d'un contrôleur dédié pour une action dédiée.

Simple, facile à lire et facile à entretenir.

你可能会问自己,这样做值么,毕竟之前的 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教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer