在PHP中使用依赖项注入组合控制器类

在PHP中使用依赖项注入组合控制器类,php,model-view-controller,dependency-injection,instantiation,Php,Model View Controller,Dependency Injection,Instantiation,如何解决用PHP编写控制器类的问题,应该是: 通过使用依赖项注入,可以轻松地进行测试 为终端程序员提供共享对象 提供加载新用户库的方法 向下看,对于依赖项注入框架的控制器实例化 问题是,派生控制器可能会使用程序员想要的任何资源(例如框架提供的资源)。如何创建对共享资源(数据库、用户、存储、缓存、助手)、用户定义类或其他库的统一访问 优雅的解决方案? 对我的问题有几种可能的解决办法,但没有一种看起来是优雅的 是否尝试通过构造函数传递所有共享对象?(即使有10个占位符也可以创建构造函数) 创

如何解决用PHP编写控制器类的问题,应该是:

  • 通过使用依赖项注入,可以轻松地进行测试
  • 为终端程序员提供共享对象
  • 提供加载新用户库的方法
向下看,对于依赖项注入框架的控制器实例化


问题是,派生控制器可能会使用程序员想要的任何资源(例如框架提供的资源)。如何创建对共享资源(数据库、用户、存储、缓存、助手)、用户定义类或其他库的统一访问

优雅的解决方案? 对我的问题有几种可能的解决办法,但没有一种看起来是优雅的

  • 是否尝试通过构造函数传递所有共享对象?(即使有10个占位符也可以创建构造函数)
  • 创建getter、setter?(膨胀的代码)
    $controller->setApplication($app)
  • 在共享资源上应用单例<代码>用户::getInstance()或
    数据库::getInstance()
  • 是否将依赖项注入容器用作控制器内对象共享的单例
  • 是否提供一个全局应用程序单例作为工厂?(这一个看起来在php框架中非常常用,但它强烈违反了DI原则和德米特定律)
我知道,创建强耦合的类是不被鼓励的,因为:),但是我不知道这个范例如何应用于其他程序员的起点(控制器类),在这个起点中,他们应该能够访问提供给MVC体系结构的共享资源。我相信,将控制器类拆分成更小的类会破坏MVC的实际意义


依赖注入框架 DI框架看起来是一个可行的选择。然而,这个问题仍然存在。类控制器不位于应用程序层,而是位于RequestHandler/Response层

该层应如何实例化控制器?

  • 将去离子注入器送入该层
  • 作为一个单体的DI框架
  • 将隔离DI框架配置仅用于此层,并创建单独的DI注入器实例

当依赖注入是可行的时,单例是不受欢迎的(我还没有找到一个需要单例的案例)

更可能的情况是,您可以控制控制器的实例化,因此您可以使用前面提到的
$controller->setApplication($application)
,但如果需要,您可以使用静态方法和变量(与单例相比,静态方法和变量对应用程序正交性的危害要小得多);即
Controller::setApplication()
,并通过实例方法访问静态变量

例如:

//在控制器中定义应用程序——很可能是在引导中 $application=新应用程序(); 控制器::设置应用程序($application); //控制器类定义中的某个地方 公共函数setContentType($contentType) { self::$application->setContentType($contentType); }
我养成了分离静态和实例属性和方法的习惯(必要时,仍然在类定义的顶部对属性进行分组)。我觉得这比使用单例要简单,因为类仍然非常紧凑。

重构如何


虽然这不是您的选项之一,但是您声明代码是一个很大程度上耦合的类。为什么不花点时间和精力把它重构成更模块化、可测试的组件呢?

你自己在开发一个框架吗?如果没有,您的问题不适用,因为您必须从现有的解决方案中进行选择。在这种情况下,您的问题必须重新表述为“如何在框架X中进行单元测试/依赖项注入”

如果您自己开发一个框架,您应该首先检查现有框架是如何处理这个问题的。您还必须详细说明您自己的需求,然后使用最简单的解决方案。如果没有要求,你的问题纯粹是审美和辩论

Imho最简单的解决方案是将公共属性初始化为框架提供的默认值,否则您可以在此处注入模拟。(这相当于您的getter/setter解决方案,但没有提到的膨胀。您并不总是需要getter和setter。)或者,如果您确实需要它,您可以提供一个构造函数来在一次调用中初始化它们(如您所建议的)

单例是一个优雅的解决方案,但您必须再次问自己,它是否适用于您的情况?如果你的应用程序中必须有同一类型对象的不同实例,你就不能使用它(例如,如果你希望只在你的应用程序的一半中模拟一个类)

当然,拥有所有的选择真的很棒。您可以使用getter/setter、构造函数,当省略初始化时,默认值取自单例工厂。但是在不需要的时候有太多的选项并不可怕,这是令人不安的,因为程序员必须弄清楚使用哪种约定、选项和模式。我绝对不想仅仅为了运行一个简单的CRUD而做出几十个设计决策

如果你看看其他框架,你会发现没有银弹。通常,单个框架根据上下文使用不同的技术。在控制器中,DI是一件非常简单的事情,看看CakePHP的$helpers,$components变量,它指示将适当的变量注入控制器类。对于应用程序本身来说,单例仍然是一件好事,因为总是只有一个单例 // defining the Application within the controller -- more than likely in the bootstrap $application = new Application(); Controller::setApplication($application); // somewhere within the Controller class definition public function setContentType($contentType) { self::$application->setContentType($contentType); }
class ControllerFactory {
    public function __construct(EvenDispatcher $dispatcher,
                                Request $request,
                                ResponseFactory $responseFactory, 
                                ModelFactory $modelFactory, 
                                FormFactory $formFactory) {
        ...
    }

    public function createController($name = 'Default') {
        switch ($name) {
            case 'User':
              return new UserController($dispatcher, 
                                        $request, 
                                        $responseFactory->createResponse('Html'), 
                                        $modelFactory->createModel('User'),
                                        $formFactory->createForm('User'),...);

              break;
            case 'Ajax':
              return new AjaxController($dispatcher, 
                                        $request, 
                                        $responseFactory->createResponse('Json'), 
                                        $modelFactory->createModel('User'));
              break;
            default:
                 return new DefaultController($dispatcher, $request, $responseFactory->createResponse('Html'));
        }
    }

} 
class App {
    public function __construct(Router $router,Request $request, ControllerFactory $cf, ... ) {
      ...
    }

    public function execute() {
        $controllerName = $this->router->getMatchedController();
        $actionName $this->router->getMatchedAction();

        $controller = $cf->createController($controllerName);

        if(is_callable($controller, $actionName)) {
            $response = $controller->$action(request);
            $response->send();     
        }
    }
}