【问题标题】:Laravel 4 - Child constructor call parent constructor with dependency injectionLaravel 4 - 使用依赖注入的子构造函数调用父构造函数
【发布时间】:2013-10-11 20:39:08
【问题描述】:

我正在使用 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() 的方法?

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

提前致谢!

【问题讨论】:

  • 构造函数不会被继承。所以你已经回答了你自己的问题。如果您希望在子类的构造函数中发生相同的操作,则需要手动调用父类的构造函数。
  • “效率不高”是什么意思?
  • 我会稍微补充一下这个问题,因为我想做同样的事情。具体来说,我希望基本控制器在每次调用时都接收Request 对象,而不是在每个控制器中重做它。这允许我在基本控制器(如用户)中设置内容,而无需借助外观。 PHP可以吗?它可以通过使用反射在 laravel 中实现吗?我对反射的了解还不够,无法回答这个问题。谢谢。

标签: php constructor dependency-injection laravel


【解决方案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 in his answer 所述自动解析父构造中的依赖关系

  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 个父依赖项的 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 语句和来自父级的类型提示,它不能再保证传递给它的构造函数的类的类型,因为它无法知道。您可以使用任何东西从您的子类构建,并且父母会接受它。

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

    【讨论】:

      【解决方案3】:

      有办法。 当 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);
          }
      }
      

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 那么这是否意味着TS解决方案就是解决方案?因为真的,我发现在所有子类中传递相同的参数直到它到达父类也很麻烦。这样,这些类就彼此紧密耦合了。
        【解决方案5】:

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

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

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

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-02-02
            • 1970-01-01
            • 2018-01-31
            • 2018-06-17
            • 1970-01-01
            相关资源
            最近更新 更多