0

0

Laravel 单行为控制器设计的魅力

Guanhui

Guanhui

发布时间:2020-05-22 11:24:40

|

3604人浏览过

|

来源于learnku

转载

Laravel 单行为控制器设计的魅力

昨天,Jeffrey Way 发布了一条推文,他问大家更愿意将其控制器命名为单数还是复数。 我回答我两种方案都不选,我使用单动作控制器。随后发生的是,有的人同意,有的不同意,有的甚至做出了最奇怪的事情。

由于十分强烈的反映,我想写一篇文章来解释为什么我爱单行为控制器、还有我为什么觉得它们很美妙。

首先在开始文章之前,我想要说这个东西并不是只有单一的真相。与往常一样,我想指出的是,一切都归结于你的个人喜好。我只能教、建议和指出一些事情,由你来决定是否同意、不同意、接受、学习和 / 或调整。或者都不是。从这篇博客中获得你想要的,随心所欲地做让自己感到舒适的事情吧。

对比 CRUD 和 Domain Modelling

开始前,我们先来想想我们倾向于写 resourceful 的 CRUD 控制器。我相信很多人会坚持使用这种做法,因为这是 Laravel 中的一个标准做法,文档中的大多数示例也是使用这种方法。另外,这或许也是你在各类博客或 app 代码中经常看到的。

但是,如果你停下来思考一下,这是编写它们的最佳方法吗?是软件行业的一般性做法吗?最近几年,我在 “领域驱动设计”(Domain Driven Design) 等领域投入了大量时间,并且思考软件如何应用于你工作的领域(Domian)以及它转化的过程。当您开始考虑模仿您领域中无处不在的语言的术语和措辞时,您会发现您的代码将变得更加清晰明了,更加戳到点子上。(最后这一句话仍值得斟酌、改进)

最后,我相信编写软件的本质是尽可能地应用 domain processes 来让你的代码更加易读和更加可维护。

Resourceful 控制器在这两个方面做得并不好。首先,它们不易读,因为您倾向于根据数据来构建它们,而不是根据领域来构建它们。这样的话,你就会丢失上下文对照。你表现了数据的处理方式,但却没有说明到底发生什么了,也没有说明你使用哪个过程进行处理。

第二,你没有针对可维护性进行优化。由于你是根据数据结构来构建的,因此你也会跟着耦合进去。实际上,您的领域模型在不断发展,数据结构也在不断发展。如果你的数据结构处理着多个过程或领域的多个部分,那你将很难进行调整。

一个实际的例子

因为理论很无聊,上代码更加容易解释,所以我们来看一个实际的例子。

假设您正在构建一个应用,它允许用户去组织事件。您想提供一种创建,更新和删除这些事件的方法。这是一种非常典型的例子,你会用 CRUD 的方式来考虑实现它。那么,让我们看看就这样一个 resourceful 控制器是如何被转换的。

首先我们来看看路由:

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

这个 EventController 处理所有的 CRUD 请求,展示事件列表,展示指定的事件,创建一个事件,更新一个现存的事件和删除一个事件。

来看看 index 方法的细节:

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

在这个方法中,我们检索出事件们,然后交给视图让它去展示到一个分页列表中。 到目前为止都还好。但是你现在想实现一个方法,用不同的页面去查看过去和即将来的事件。让我们看看如何在 index 方法中实现它:

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

呃啊!看起来好乱啊。尽管我们已经用 Eloquent scopes 来隐藏查询逻辑,但是还是有很丑的链式语句。我们来看看如何用单行为控制器来代替它。

每个单行为控制器只执行一件事情,仅仅一件事情。

首先,我们不使用查询参数去获得不同的事件列表,而是使用专用路由去实现它。

Magic AI Avatars
Magic AI Avatars

神奇的AI头像,获得200多个由AI制作的自定义头像。

下载
Route::get('events', ShowAllEventsController::class);
Route::get('events/past', ShowPastEventsController::class);
Route::get('events/upcoming', ShowUpcomingEventsController::class);

这个路由比之前的要长一些,但是这个比之前的要更有表达力。你可以一下子辨识出哪一个控制器处理哪一个特定的逻辑。如果你对比一下 URL,你会看到在可读性上改进了一些:

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

现在来看其中一个控制器。就看 ShowUpcomingEventsController 这个控制器:

<?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 语句没了, and has made way for the same readable three liner we had from our first CRUD controller example. But instead of having all of the other CRUD operations we now have a dedicated controller for a dedicated action.

简单,易读,便于维护。

你可能会问自己,这样做值么,毕竟之前的 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,再使用无处不在的语言、显式地命名,会带来更可维护的代码,甚至是控制器,不仅仅是领域对象。但是正如我开头所说,选择能帮助你的部分,好好分辨哪些适用于你,哪些不行。

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
laravel组件介绍
laravel组件介绍

laravel 提供了丰富的组件,包括身份验证、模板引擎、缓存、命令行工具、数据库交互、对象关系映射器、事件处理、文件操作、电子邮件发送、队列管理和数据验证。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

340

2024.04.09

laravel中间件介绍
laravel中间件介绍

laravel 中间件分为五种类型:全局、路由、组、终止和自定。想了解更多laravel中间件的相关内容,可以阅读本专题下面的文章。

293

2024.04.09

laravel使用的设计模式有哪些
laravel使用的设计模式有哪些

laravel使用的设计模式有:1、单例模式;2、工厂方法模式;3、建造者模式;4、适配器模式;5、装饰器模式;6、策略模式;7、观察者模式。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

773

2024.04.09

thinkphp和laravel哪个简单
thinkphp和laravel哪个简单

对于初学者来说,laravel 的入门门槛较低,更易上手,原因包括:1. 更简单的安装和配置;2. 丰富的文档和社区支持;3. 简洁易懂的语法和 api;4. 平缓的学习曲线。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

385

2024.04.10

laravel入门教程
laravel入门教程

本专题整合了laravel入门教程,想了解更多详细内容,请阅读专题下面的文章。

141

2025.08.05

laravel实战教程
laravel实战教程

本专题整合了laravel实战教程,阅读专题下面的文章了解更多详细内容。

85

2025.08.05

laravel面试题
laravel面试题

本专题整合了laravel面试题相关内容,阅读专题下面的文章了解更多详细内容。

80

2025.08.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

497

2026.03.04

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 13.4万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 11.3万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 1.0万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号