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

时间:2013-10-11 20:39:08

标签: php constructor dependency-injection laravel

我正在使用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()

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

提前致谢!

6 个答案:

答案 0 :(得分: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);
    }
}

因此在子控制器中只传递Application实例:

class MyController extends BaseController {

    public function __construct(
        // Class dependencies resolved in BaseController
        //..

        // Application
        Application $app
    )
    {
        // Logic here
        //..


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

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

答案 1 :(得分:3)

我知道这是一个非常古老的问题,但我刚刚完成了关于我当前项目的类似问题,并对手头的问题达成了谅解。

这里的基本问题是:

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

我遇到了同样的问题。

我的父类需要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语句并从父类进行提示,它就不能再保证传递给它的类的构造函数,因为它无法知道。你可以从你的子类构造任何东西,父母会接受它。

它看起来似乎是代码的双重输入,但实际上在您的帮助中,您没有使用父项中列出的依赖项构建,您需要验证孩子是否正在发送正确的类型。

答案 2 :(得分:2)

没有一个完美的解决方案,重要的是要理解这不是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. 所述,自动解析父构造中的依赖项
  4. 在父构造中创建实例,而不是将它们作为参数传递(因此您不使用依赖注入):

    // 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();
    }
    
  5. 如果您想测试您的课程,第三种解决方案将更难以测试。我不确定你是否可以使用第二个解决方案来模拟类,但是你使用第一个解决方案来模拟它们。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

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

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

这种更简单的方法可能存在缺点 - 可能使代码不易测试。

答案 5 :(得分:0)

我也遇到了这个问题,并通过不在子类上调用构造函数并在函数参数内使用其他需要的依赖项来解决了这一麻烦。

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