【问题标题】:Where to load controller dependency classes?在哪里加载控制器依赖类?
【发布时间】:2014-07-19 15:00:07
【问题描述】:

我正在使用 PHP 制作自己的原始 MVC 框架,我想知道我应该在哪里加载/实例化相应的控制器依赖项?

在每个控制器的构造函数中(紧耦合)还是注入它们(松耦合)?

我不太确定的后者的唯一部分是在注入之前在 MVC 范式之外的引导级别上实例化依赖项。除了默认的父级之外,并非每个控制器都使用完全相同的依赖项。我必须将它们全部实例化,这也会产生大量开销。

我已经看到一些现有的框架在构造函数中像 $this->load->model('model'); // CodeIgniter 那样做,但我不知道他们为什么这样做。

【问题讨论】:

    标签: php dependency-injection controller


    【解决方案1】:

    我建议您注入依赖项,这样您的控制器与您的框架的耦合度就会降低。这将使切换到另一个框架更容易。

    关于实例化依赖:我建议你使用(或实现)dependency injection 容器。此容器应包含可以实例化服务的factories

    在理想情况下,您的控制器也是服务(意味着它们在依赖注入容器中也有工厂)。

    这样只会实例化特定请求所需的控制器,因此只会实例化其依赖项。
    在构建您自己的框架时,这意味着在路由阶段之后(当知道正确的控制器时),框架应该从容器中获取该控制器。容器本身将确保提供所需的所有依赖项。

    查看Pimple 以获得简单的依赖注入容器的示例。

    PS:来自 CodeIgniter 的那一行看起来很像 service locator pattern。此模式类似于dependency injection,但不提供完整 inversion of control

    【讨论】:

    • 这意味着在引导级别将它们注入控制器,对吧?
    • 并非如此:您将在引导阶段设置依赖注入容器(但这只是创建工厂)。服务的实例化(将它们注入到其他服务中)是在您从依赖注入容器请求服务时由这些工厂完成的。因此服务的实例化是按需完成的,并且完全由依赖注入容器管理。
    • @JasperN.Brouwer 抱歉,我标记了错误的人。我的意思是任何使用控制器作为服务和依赖注入容器来实例化控制器的流行框架。任何将其控制器实现为服务的框架。我已经构建了一个 MVC 框架,我想通过更好的设计来改进它。我喜欢你的回答,因此我很好奇是否有一些轻型框架(如果可能)或任何可以做到这一点的框架?
    • @Shikhar Subedi Silex(一个微框架)可以做到这一点,看看他们的ServiceControllerServiceProvider
    【解决方案2】:

    问:我应该在哪里加载/实例化相应的控制器依赖项?

    有多种方法。 加载和实例化的概念基本上是“前/外”和“后/内”。

    之前和外部意味着,在加载控制器之前加载包含类(您想要实例化并传递给控制器​​)​​的文件。 但是在加载控制器之前,你怎么知道控制器需要什么?呃..

    • 依赖描述文件

    描述文件开始发挥作用,描述控制器及其依赖项之间的连接。换句话说,您可以通过查看控制器的依赖描述文件来查看控制器的依赖关系。依赖注入工具经常使用这个概念,它分析对象并自动提取依赖项名称。也可以手动维护这样的布线配置文件。但这很乏味。

    • 服务定位器

    服务定位器是依赖项的实例化助手。 基本上,它包含与依赖描述文件相同的信息,但这次是以registry 的形式。应用程序各部分之间的链接变成了这个注册表。

    这两种策略都会引入开销。这是一个权衡。当您改变视角并从可能包含 500 多个类的应用程序中看待事物时,您就会意识到依赖注入工具有时是值得的。

    • 手动注入

    通过构造函数注入。

    After and inside 意味着,您加载包含控制器的文件,然后开始关心依赖关系。

    此时该类还没有被实例化,但是自动加载器可能会在幕后做它的肮脏行为。他评估了控制器文件顶部的use 语句。 use 语句声明命名空间类,自动加载器将其解析为实际文件并加载它们。然后,您可能会开始将这些类用作控制器中的依赖项。 这可能是解决您的问题的最简单方法,我强烈建议您研究使用命名空间和使用语句自动加载的主题。

    当类被实例化时,您有以下可能性: use 可能使用 Setter Injection 或 Reference Injection 来设置对象的依赖关系。这要求您的构造函数依赖关系已经解决或者您的构造函数为空。 可以将这些策略结合起来。

    问:$this->load->model('model'); // CodeIgniter 有什么作用?

    CodeIgniter 是一个遗留应用程序框架。它是在命名空间自动加载不可用时创建的。 $this->load 是一个基本的类加载助手。这与“自动”加载器相反,它会自动加载东西。

    CodeIgniters 加载器类用于加载各种其他类,例如来自视图、帮助器、模型或用户定义的东西的库或文件。这又是注册表的概念。在这里,注册表只知道事物在您的应用程序布局中的位置并解决它们。所以$this->load->model('model'); 意味着model函数必须有一些信息,关于模型文件在你的应用程序中的位置。 您提供模型名称,文件的路径由model 构建。 这正是它的作用(除了一点开销):https://github.com/EllisLab/CodeIgniter/blob/develop/system/core/Loader.php#L223

    【讨论】:

      【解决方案3】:

      由于我是一名 Symfony 开发人员,我只能给你一个 Symfony 的参考。 我认为你应该像他们在 Symfony 中所做的那样,思考你在每个方面需要什么 控制器对象。

      至少,你需要:

      • 一个请求对象

      • 还有一个模型加载器对象,可为您提供所需的每个模型。

      创建一个实现这几个功能的 BaseController,然后使用自定义控制器对其进行扩展。 你也可以看看 Silex:http://silex.sensiolabs.org/a Micro Framework

      希望对你有帮助。

      【讨论】:

        【解决方案4】:

        什么时候说“在构造函数中”是指传入 conatiner 并从中提取依赖项(在构造函数中)?

        <?php
        
        class SomeController
        {
          public function __construct($container)
          {
            $this->service1 = $contanier->get('service1);
          }
          //...
        }
        

        我建议不要这样做,虽然更简单,但您可以将控制器耦合到 容器,从而使用 ServiceLocator 而不是真正的控制反转。

        如果您希望您的控制器易于进行单元测试,您应该使用控制反转:

        class SomeController
        {
          public function __construct($service1)
          {
            $this->service1 = $service1;
          }
          //...
        }
        

        您甚至可以将控制器创建为容器内的服务:

        // this uses Pimple notation, I hope you get the point
        $container['controller'] = function($c) {
          return SomeController($c['service1']);
        }
        

        使用代理服务来延迟加载它们

        此外,如果您的控制器需要的不仅仅是某些服务,而且您不会使用所有这些服务,您可以:

        1) 使用代理服务以便仅在真正需要时才延迟加载服务

        <?php
        
        class ProxyService
        {
          /**
           * @var Service1Type
           */
          private $actualService;
        
          public function __construct()
          {
            $this->actualService = null;
          }
        
          private function initialize()
          {
            $this->actualService = new Service1(); // This operation may take some time thus we deferred as long as possible
          }
        
          private function isInitialized()
          {
            return $this->actualService === null;
          }
        
          public function someActionOnThisService()
          {
            if (!$this->isInitalized()) {
              $this->initalize();
            }
        
            $this->actualService->someActionOnThisService();
          }
        

        你有一个带有延迟加载的简单代理对象。如果你想走那条路,你可能想看看很棒的Proxy Manager Library

        2) 拆分你的控制器

        如果您的控制器有太多依赖项,您可能需要拆分它。

        事实上,您可能想阅读Paul M. Jones(Aura Framework 的首席开发人员)关于MVC-Refinement 的提案,恕我直言,尽管您可能不完全同意,但它是一本不错的读物。

        即使您拆分控制器以减少依赖项,延迟加载您的依赖项也是一个好主意(显然,如果它在您的上下文中可行,您必须检查天气:更多工作以获得更快的速度)。

        【讨论】:

          【解决方案5】:

          在尝试加载尚未加载的类之前,您可能需要定义__autoload() 函数。喜欢:

          function __autoload($className) {
              require "/path/to/the/class/file/$className.php";
          }
          

          我的示例非常简单,可以自动要求类定义所在的文件。 您还可以在该函数中使用if-else 语句或switch 语句来巧妙地适应您自己的情况。

          __autoload() 函数在 PHP 找不到类定义时调用,适用于 newclass_exists()call_user_method() 等,绝对适用于您的依赖/父类。如果调用__autoload()后仍然没有类定义,PHP会报错。

          或者你可以更优雅地使用spl_autoload_register()函数代替__autoload()

          有关详细信息,您可能希望查看: http://php.net/manual/en/function.autoload.php http://php.net/manual/en/function.spl-autoload-register.php

          【讨论】:

            猜你喜欢
            • 2013-06-30
            • 1970-01-01
            • 2014-07-16
            • 2012-09-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-04-26
            • 1970-01-01
            相关资源
            最近更新 更多