PHP MVC:控制器中的依赖项过多?

时间:2017-05-07 04:37:08

标签: php design-patterns model-view-controller dependency-injection constructor

我正在开展个人HMVC项目:

  • 没有服务定位器,没有全局状态(如staticglobal),没有单身人士。
  • 模型处理封装在服务中(服务=域对象+存储库+数据映射器)。
  • 所有控制器都扩展了一个抽象控制器。
  • 所有项目依赖项都是通过Auryn依赖注入容器注入的。

所有需要的依赖项都被注入抽象控制器的构造函数中。如果我想覆盖这个构造函数,那么我也必须在子控制器的构造函数中传递所有这些依赖项。

class UsersController extends AbstractController {

    private $authentication;

    public function __construct(
        Config $config
        , Request $request
        , Session $session
        , View $view
        , Response $response
        , Logger $logger
        , Authentication $authentication // Domain model service
    ) {
        parent::__construct(/* All dependencies except authentication service */);
        $this->authentication = $authentication;
    }

    // Id passed by routing.
    public function authenticateUser($id) {
        // Use the authentication service...
    }

}

依赖项列表会进一步增长。这需要改变。所以我在考虑:

  • 完全将视图与视图分开
    然后他们将共享服务层。视图不再属于控制器,Response将是视图的依赖。
  • 在控制器中使用 setter injection RequestSessionLogger等相似;
  • 在控制器操作中注入依赖关系
    仅在需要时。
    RequestSessionLogger等相似;
  • 使用装饰器模式。
    类似于在操作调用后记录。
  • 实施一些工厂
  • 构造函数仅在子控制器上注入所需的依赖项
    因此不再在AbstractController中。

我正在努力寻找一种优雅的方式来处理这项任务,我会感激任何建议。谢谢。

1 个答案:

答案 0 :(得分:4)

我会回答我自己的问题。当我编写它时,我已经很好地概述了许多有经验的开发人员推荐的关于MVC和MVC结构中依赖注入的内容。

  • 构造函数注入是正确的选项。但它似乎 我,按照这一行,我最终会得到太多 构造函数中的依赖项/参数。因此给控制器 责任太多(阅读要求的价值,改变 域对象的状态,日志记录操作,请求视图 加载模板和渲染数据等。)。
  • Setter注射也是一个需要考虑的解决方案。但是,在我的项目开发时间过程中,我意识到了 这个解决方案真的不适合(至少)适合的情况 我的控制器 - 视图关系。
  • 依赖项直接注入控制器 记住,行动让我很难(但很棒的时候) 我已经将url值作为动作参数注入了 我没有使用任何路由调度员。
  • 实现工厂也是一个好主意,以便能够 在每个控制器动作中拥有对象。 工厂是一个很好的工具,但只能从前提出发 需要运行时对象,而不仅仅是减少数量 构造函数中的依赖关系。
  • 装饰模式也是一个不错的选择。但是,例如,如果您想在控制器操作中记录某些内容,那么 这不是解决方案:您仍然必须将记录器作为依赖项传递 (在构造函数,设置器或操作中)。
  • 我考虑过只注入所需的依赖项 儿童控制器。但后来问题多了 相应控制器的职责保持不变。

因此,无论我做什么,这些解决方案似乎都不适合我的HMVC项目的结构。所以,我进一步挖掘,直到我意识到缺少的链接是什么。为此,我完全赞赏以下伟大文章的创建者 Tom Butler


他的作品基于对MVC概念的深入,充分论证的分析。它们不仅非常容易理解,而且还通过不言自明的例子来维持。总之:对MVC和开发者社区的贡献很棒。

我要进一步写下的内容只是用他自己的话语来表达他的原则,以某种方式完成它们,提供一个更紧凑的视角,并展示我所遵循的步骤。我在我的项目中实现了它们。所有关于这里描述的主题,想法,原则和工作流程的信用都归 Tom Butler 所有。

那么,我的HMVC项目缺少什么链接?它被命名为分离关注

为简单起见,我将尝试通过仅将一个控制器,一个控制器操作,一个视图,一个模型(域对象)和一个模板(文件)引用自己来解释这一点,并将它们引入{{1} } context。

最常在网络上描述的MVC概念 - 也是我研究过的一些流行框架实现的 - 主要围绕让控制器控制视图和模型的原则。为了在屏幕上显示某些内容,您必须告诉控制器 - 他还会通知视图加载和呈现模板。如果此显示过程也意味着使用某些模型数据,那么控制器也会操纵模型。

以传统方式,控制器创建和动作调用过程包括两个步骤:

  • 创建控制器 - 将所有依赖项传递给它,包含视图;
  • 调用控制器操作。

代码:

User

这意味着,控制器负责一切。因此,难怪为什么必须为控制器注入如此多的依赖项。

但是,控制器是否应该参与或负责在屏幕上有效地显示任何类型的信息?不,这应该是观点的责任。为了实现这一目标,我们开始将视图与控制器分开 - 从不需要任何模型的前提出发。涉及的步骤是:

  • 定义一个$controller = new UserController(/* Controller dependencies */); $controller->{action}(/* Action dependencies */); 方法,用于在屏幕上显示信息 图。
  • 创建控制器 - 将所有依赖项传递给它,除了 视图及其相关依赖项(响应,模板对象等)。
  • 创建视图 - 将相应的依赖项传递给它 (响应,模板对象等)。
  • 调用控制器操作。
  • 调用视图的output方法:

代码:

output

通过完成五个上面的步骤,我们设法将控制器与视图完全分离。

但是,有一个方面,我们先前假设:我们没有使用任何模型。那么控制器在这个星座中的作用是什么呢?答案是:没有。控制器应仅作为某个存储位置(数据库,文件系统等)与视图之间的中间存在。否则,例如,只有在屏幕上以某种格式输出一些信息,视图的class UserView { //.... // Display information on screen. public function output () { return $this ->load('<template-name>') ->render(array(<data-to-display>)) ; } //.... } $controller = new UserController(/* (less) controller dependencies */); $view = new UserView(/* View dependencies */); $controller->{action}(/* Action dependencies */); echo $view->output(); 方法才足够。

如果一个模型出现在现场,情况会发生变化。但是应该在哪里注射?在控制器或视图中?同时。它们共享相同的模型实例。在这一刻,控制器凭借自己的权利获得了中间人 - 存储和视图之间的角色。理论形式是:

output

这样,控制器可以更改模型的状态并确保它已保存在存储系统中。视图读取并显示相同的模型实例及其状态。控制器通过模型将显示逻辑信息传递给视图。问题是,这些信息不属于业务逻辑,模型应该是唯一拥有的。它们只是显示逻辑参与者。

为了避免向模型赋予显示逻辑职责,我们必须在图片中引入一个新组件:视图模型。控制器和视图将共享视图模型实例,而不是共享模型对象。只有这一个将接收模型作为依赖。实施:

$model = new UserModel;
$controller = new UserController($model, /* Other controller dependencies */);
$view = new UserView($model, /* Other view dependencies */);

$controller->{action}(/* Action dependencies */);
echo $view->output();

工作流程可以这样描述:

  • 请求值由浏览器(&#34;用户&#34;)发送给 控制器。
  • 控制器将它们作为属性存储在视图模型实例中 (数据成员),因此改变了显示逻辑状态 图的模型。
  • $model = new UserModel; $viewModel = new UserViewModel($model, /* Other view-model dependencies */); $controller = new UserController($viewModel /* Other controller dependencies */); $view = new UserView($viewModel, /* Other view dependencies */); $controller->{action}(/* Action dependencies */); echo $view->output(); 方法中,视图会从中读取值 视图模型并请求模型查询其存储 基础。
  • 模型运行相应的查询并将结果传回 观点。
  • 视图会读取并将其传递给相应的模板。
  • 模板渲染后,结果显示在屏幕上。

视图模型不属于域模型,所有域对象都驻留在该域模型中,并且发生了真正的业务逻辑。它也不属于服务层,它操纵域对象,存储库和数据映射器。它属于应用程序模型,例如应用程序逻辑发生的位置。视图模型自行负责从控制器获取显示逻辑状态并将其传送到控制器。

可以看出,只有视图模型&#34;触及&#34;该模型。控制器和视图两者不仅完全脱离,而且还与模型完全分离。这种方法最重要的方面是,所涉及的所有组件中的每一个都只能获得它应该获得的责任。

通过利用这种组件分离和依赖注入容器,控制器中依赖性太多的问题消失了。并且可以以非常灵活的方式应用我的问题中提出的所有选项的组合。没有考虑到其中一个组件(模型,视图或控制器)获得了太多的责任。