Laravel 4 - 子构造函数调用具有依赖注入的父构造函数

我正在使用Laravel 4构建一个CMS,并且我有一个用于管理页面的基本管理控制器,如下所示:

class AdminController extends BaseController {

    public function __construct(UserAuthInterface $auth, MessagesInterface $message, ModuleManagerInterface $module)
    {
        $this->auth = $auth;
        $this->user = $this->auth->adminLoggedIn();
        $this->message = $message;
        $this->module = $module;
    }
}

我使用Laravel的IOC容器将类依赖项注入构造函数。然后,我有各种控制器类来控制组成CMS的不同模块,并且每个类都扩展了管理类。例如:

class UsersController extends AdminController {

    public function home()
    {
        if (!$this->user)
        {
            return Redirect::route('admin.login');
        }
        $messages = $this->message->getMessages();
        return View::make('users::home', compact('messages'));
    }
}

现在这很完美,但是当我向类中添加构造函数时,我的问题(不是问题,而是效率问题)就会发生。例如:UsersController

class UsersController extends AdminController {

    public function __construct(UsersManager $user)
    {
        $this->users = $users;
    }

    public function home()
    {
        if (!$this->user)
        {
        return Redirect::route('admin.login');
        }
        $messages = $this->message->getMessages();
        return View::make('users::home', compact('messages'));
    }
}

由于子类现在具有构造函数,这意味着父类的构造函数未被调用,因此子类所依赖的内容(例如不再有效)会导致错误。我可以通过调用管理控制器的构造函数,但是由于我需要向它传递类依赖项,因此我需要在子构造函数中设置这些依赖项,从而产生如下所示的内容:this->userparent::__construct()

class UsersController extends AdminController {

    public function __construct(UsersManager $user, UserAuthInterface $auth, MessagesInterface $message, ModuleManagerInterface $module)
    {
        parent::__construct($auth, $messages, $module);
        $this->users = $users;
    }

    // Same as before
}

现在,就其功能而言,这工作正常;但是,对我来说,必须在每个具有构造函数的子类中包含父级的依赖项似乎不是很有效。它看起来也很乱。Laravel是否提供了解决此问题的方法,或者PHP是否支持一种调用父构造函数和子构造函数而无需从子构造函数调用的方法?parent::__construct()

我知道这是一个很长的问题,实际上不是问题,但更多的我只是对效率的强迫症,但我欣赏任何想法和/或解决方案。

提前致谢!


答案 1

没有一个完美的解决方案,重要的是要明白这不是Laravel本身的问题。

要对此进行管理,您可以执行以下三项操作之一:

  1. 将必要的依赖项传递给父级(这是您的问题)

    // Parent
    public function __construct(UserAuthInterface $auth, MessagesInterface $message, ModuleManagerInterface $module)
    {
        $this->auth = $auth;
        $this->user = $this->auth->adminLoggedIn();
        $this->message = $message;
        $this->module = $module;
    }
    
    // Child
    public function __construct(UsersManager $user, UserAuthInterface $auth, MessagesInterface $message, ModuleManagerInterface $module)
    {
        $this->users = $users;
        parent::__construct($auth, $message, $module);
    }
    
  2. 自动解析父构造中的依赖项,如@piotr_cz在其答案中所述

  3. 在父构造中创建实例,而不是将它们作为参数传递(因此您不使用依赖关系注入):

    // Parent
    public function __construct()
    {
        $this->auth = App::make('UserAuthInterface');
        $this->user = $this->auth->adminLoggedIn();
        $this->message = App::make('MessagesInterface');
        $this->module = App::make('ModuleManagerInterface');
    }
    
    // Child
    public function __construct(UsersManager $user)
    {
        $this->users = $users;
        parent::__construct();
    }
    

如果你想测试你的类,第三个解决方案将更难测试。我不确定你是否可以使用第二个解决方案模拟类,但你使用第一个解决方案模拟它们。


答案 2

我知道这是一个超级老的问题,但我刚刚在我当前的项目中完成了一个类似的问题,并理解了手头的问题。

这里的基本问题是:

如果我正在扩展具有构造函数的父类。该构造函数已注入依赖项,并且其所有依赖项都已记录在父级本身中。为什么我必须在我的子类中再次包含父级的依赖项

我遇到了同样的问题。

我的父类需要 3 个不同的依赖项。它们通过构造函数注入:

<?php namespace CodeShare\Parser;

use CodeShare\Node\NodeRepositoryInterface as Node;
use CodeShare\Template\TemplateRepositoryInterface as Template;
use CodeShare\Placeholder\PlaceholderRepositoryInterface as Placeholder;

abstract class BaseParser {

    protected $node;
    protected $template;
    protected $placeholder;


    public function __construct(Node $node, Template $template, Placeholder $placeholder){
        $this->node           = $node;
        $this->template       = $template;
        $this->placeholder    = $placeholder;
    }

该类是一个抽象类,因此我永远无法自行实例化它。当我扩展类时,我仍然需要在子级的构造函数中包含所有这些依赖项及其引用:use

<?php namespace CodeShare\Parser;

// Using these so that I can pass them into the parent constructor
use CodeShare\Node\NodeRepositoryInterface as Node;
use CodeShare\Template\TemplateRepositoryInterface as Template;
use CodeShare\Placeholder\PlaceholderRepositoryInterface as Placeholder;
use CodeShare\Parser\BaseParser;

// child class dependencies
use CodeShare\Parser\PlaceholderExtractionService as Extractor;
use CodeShare\Parser\TemplateFillerService as TemplateFiller;


class ParserService extends BaseParser implements ParserServiceInterface {

    protected $extractor;
    protected $templateFiller;

    public function __construct(Node $node, Template $template, Placeholder $placeholder, Extractor $extractor, TemplateFiller $templateFiller){
        $this->extractor      = $extractor;
        $this->templateFiller = $templateFiller;
        parent::__construct($node, $template, $placeholder);
    }

在每个类中包含 3 个父依赖项的语句似乎是重复的代码,因为它们已经在父构造函数中定义。我的想法是删除父语句,因为它们总是需要在扩展父级的子类中定义。useuse

我意识到,在父类中包含 for 依赖项以及在父类的构造函数中包含类名,只有父类中的类型提示才需要。use

如果从父构造函数中删除语句,并从父构造函数中删除类型提示类名,则会得到:use

<?php namespace CodeShare\Parser;

// use statements removed

abstract class BaseParser {

    protected $node;
    protected $template;
    protected $placeholder;

    // type hinting removed for the node, template, and placeholder classes
    public function __construct($node, $template, $placeholder){
        $this->node           = $node;
        $this->template       = $template;
        $this->placeholder    = $placeholder;
    }

如果没有来自父级的语句和类型提示,它就无法再保证传递给其构造函数的类的类型,因为它无法知道。你可以用任何东西从你的子类中构造,父母会接受它。use

这看起来确实像是代码的复式输入,但实际上,在你的paren中,你不是在用父级中列出的依赖项进行构造,而是在验证子级是否发送了正确的类型。


推荐