Laravel 4 - 子构造函数使用依赖项注入调用父构造函数


Laravel 4 - Child constructor call parent constructor with dependency injection

我正在使用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->user(不再有效,从而导致错误。我可以通过parent::__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()的方法?

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

提前感谢!

没有一个完美的解决方案,重要的是要明白这不是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();
    }
    

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

我知道

这是一个超级老的问题,但我刚刚完成了对当前项目的类似问题的磨练,并对手头的问题达成了谅解。

这里的基本问题是:

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

我遇到了同样的问题。

我的父类需要 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 个父依赖项的 use 语句似乎是重复的代码,因为它们已经在父构造函数中定义。我的想法是删除父语句use因为它们始终需要在扩展父语句的子类中定义。

我意识到的是,在父类中包含依赖项的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语句和类型提示,它就无法再保证传递给其构造函数的类的类型,因为它无法知道。你可以用任何东西从你的子类构造,父类会接受它。

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

有办法。当 BaseController 自动解析它的依赖项时。

use Illuminate'Routing'Controller;
use Illuminate'Foundation'Application;
// Dependencies
use Illuminate'Auth'AuthManager;
use Prologue'Alerts'AlertsMessageBag;
class BaseController extends Controller {
    protected $authManager;
    protected $alerts;
    public function __construct(
        // Required for resolving
        Application $app,
        // Dependencies
        AuthManager $authManager = null,
        AlertsMessageBag $alerts = null
    )
    {
        static $dependencies;
        // Get parameters
        if ($dependencies === null)
        {
            $reflector = new 'ReflectionClass(__CLASS__);
            $constructor = $reflector->getConstructor()
            $dependencies = $constructor->getParameters();
        }
        foreach ($dependencies as $dependency)
        {
            // Process only omitted optional parameters
            if (${$dependency->name} === null)
            {
                // Assign variable
                ${$dependency->name} = $app->make($dependency->getClass()->name);
            }
        }

        $this->authManager = $authManager;
        $this->alerts = $alerts;
        // Test it
        dd($authManager);
    }
}

因此,在子控制器中,您只传递应用程序实例:

class MyController extends BaseController {
    public function __construct(
        // Class dependencies resolved in BaseController
        //..
        // Application
        Application $app
    )
    {
        // Logic here
        //..

        // Invoke parent
        parent::__construct($app);
    }
}

当然,我们可以使用立面进行应用

必须将依赖项传递给父构造函数才能使它们在子构造函数中可用。 当您通过子构造实例化父构造时,无法在父构造上注入依赖项。

在扩展基本控制器时,我遇到了同样的问题。

我选择了与此处显示的其他解决方案不同的方法。我不依赖于依赖注入,而是在父构造函数中使用 app((->make((。

class Controller
{
    public function __construct()
    {
        $images = app()->make(Images::class);
    }
}

这种更简单的方法可能有缺点 - 可能会使代码的可测试性降低。

我也遇到了这个问题,并通过不在子类中调用构造函数并在函数参数中使用额外的所需依赖项来清除这个混乱。

它将与控制器一起使用,因为您不需要手动调用这些函数,并且可以在那里注入所有内容。因此,常见的依赖项归父级所有,而不需要的依赖项将添加到方法本身中。