16

在过去几年中, PHP 开发环境发生了很大的变化。我们开始使用更多更好的设计模式,比如 DRYSOLID) 设计模式原则。但为什么我们仍然在使用控制器?

如果您以前曾经参与过大型项目的架构编写,那么您可能已经注意到迟早会出现控制器过多的这种现象。即使您将控制器逻辑分离到各种类库或服务类中,大量的依赖项和方法以及代码的行数还是会随着时间的推移不断增长。

我来介绍一下请求处理器。这个概念很简单,但很多 PHP 开发人员都不知道。请求处理器可以理解为仅包含单个动作(Action)的控制器,能够使请求到响应的流程更加清晰明确。这个概念与 Paul M. Jones 提出的 Action-Domain-Responder 设计模式有相似之处,后者是MVC模式的替代品。

file

一个好的方法去建立请求处理器就是使用调用类。可调用类是使用PHP中的魔术方法 __invoke ,把他们变成一个 Callable ,这将允许他们作为函数调用。这里有一个关于调用类的简单例子:

class Greeting
{
    public function __invoke($name)
    {
        echo 'Hello ' . $name;
    }
}

$welcome = new Greeting();
$welcome('John Doe'); //输出 Hello John Doe

看到这里你大概会想;“我为什么要这样做?”。我知道这是一个有点荒谬的例子。但是它与某些代码一起使用时例如可调用对象和依赖注入,它将变得很有意义。一个好的使用例子是路由的请求处理在Laravel和Slim框架中。

Route::get('/{name}', Greeting::class);

是否让你大吃一惊?没有?让我们把它和你通常写的比较一下:

Route::get('/{name}', 'SomeController@greeting');

还没有?除了代码好看之外,还有其他优点。让我们先去看看使用请求处理程序比控制器有那些优点。

单一模式

SOLID 的第一个原则是“单一模式”。在我看来,控制器中存在许多的方法,就打破了这个原则。请求处理程序提供了一个很好的解决方案,可以将这些操作分成它们自己的类,使它们更易于维护,重构和测试。

这是从 UsersController 中提取的2个请求处理程序的示例,它处理用户配置文件的编辑和保存:

class EditUserHandler
{
    public function __construct(
        UserRepository $repository,
        Twig $twig
    ) {
        ...
    }

    public function __invoke(Request $request, Response $response)
    {
        ...
    }
}

class UpdateUserHandler
{
    public function __construct(
        UserRepository $repository,
        UpdateUserValidator $validator,
        ImageManager $resizer,
        Filesystem $storage
    ) {
        ...
    }

    public function __invoke(Request $request, Response $response)
    {
        ...
    }
}

接下来让我们看下一个优势;

测试性能

你最近有没有为你的项目编写过单元测试?在编写单元测试的时候你可能编写了一些与测试无关的模拟依赖项。由于请求处理器将不同的控制器操作拆分为单独的类,因此您只需注入或绑定该动作所需要的依赖项即可。

这是 Jeffrey Way 的一些建议 Twitter

提示:让你的功能测试尽可能更加详细具体,使用测试用例来描述重要的规则和能力。

这基本不会让你的请求处理器都有一个测试文件。对于那些繁琐的控制器测试文件来说是一个非常好的改进。

重构

PhpStorm 和其他的编辑器都有强大的代码重构功能,但是如果你使用的是 Laravel 或者 Slim 框架默认的路由方法将控制器绑定到路由,那么你可能会遇到这种问题。

例如重命名:

Route::get('/{name}', Greeting::class);

比这简单得很多:

Route::get('/{name}', 'SomeController@greeting');

结论

请求处理器是控制器很好的替代品。控制器的动作(Actions)被分为多个独立的请求处理器类,分别负责响应单一的动作。这使整个项目的代码更易于维护、重构和测试。

您是否应当使用请求处理器替换所有控制器?可能不是。对于小型应用程序而言,为了简单,将动作组合成控制器或许更加合理。当我开始在 Teamleader 工作后,我才开始发掘请求处理器,我觉得近期没什么换回控制器的必要了。

如果有什么不清楚或有疑问,请在下面留下评论告诉我,我会更新这篇文章。

转自 PHP / Laravel 开发者社区 https://laravel-china.org/top...

你可能感兴趣的

Lelouchyang · 1月7日

虎头蛇尾 不知所云

+7 回复

灰色v碰触 · 1月7日

个人觉得,在单个复杂方法(比如多依赖,数以百计的代码行...)中可以应用请求处理器,方便维护,如果简单方法也每个写一个处理器类,反倒是麻烦了~

请求处理器实际上也是拆分代码方式的一种,比单纯把处理代码交给工具类等去处理,更直观明了....

+3 回复

LIONIDO · 1月15日

感谢楼主指路 https://jenssegers.com/85/goo... 原本英文看起来更清晰一些

回复

hsldymq · 1月17日

跟psr-15一个概念,你的方法是用魔术方法__invoke,psr-15用实现接口的方式,大同小异。
这种设计对小而美的项目来说还行,不过这种方式对大型项目来说可能是一个噩梦了。

首先会类爆炸,比如对一个rest api项目,一个资源的可能会实现get,post,delete,put等方法,原先一个类搞定的,现在要4个类,文件数量成倍增加,对于小型项目,比如100个资源,用request handler的方式也就多出几百个文件(类)。但是如果是一个1000个以上资源的项目,那么突然多出几千个文件(类)那可不是开玩笑的,程序员和架构师都未必愿意这么搞。

其次,对于传统方式,不同method之间的处理逻辑存在一定的逻辑关联,他们可能会共享一些方法,常量,他们虽然可以定义在另一个层次代码中,但是有时候只是一些小的方法或者它们仅限于当前资源使用,没有提取出来的必要,写在一个类中容易理解和维护。如果用了request handler,一个类变成几个类,这些复用性不高的小东西放在哪个类中?放在哪个类都会造成一定的理解负担!

为了这看上去很美的东西而搞这么一堆幺蛾子出来,我看未必好。

回复

载入中...