Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 4 - Child constructor call parent constructor with dependency injection

I'm building a CMS using Laravel 4 and I have a base admin controller for the admin pages that looks something like this:

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;     } } 

Im using Laravel's IOC container to inject the class dependencies into the constructor. I then have various controller classes that control the different modules that make up the CMS and each class extends the admin class. For example:

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'));     } } 

Now this works perfectly however my problem, which is less of a problem and more of a efficiency issue, occurs when I add a constructor to the UsersController class. For example:

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'));     } } 

Since the child class now has a constructor it means the parent's constructor isn't getting called and thus things the child class is dependant on, such as this->user are no longer valid, causing errors. I can call the admin controller's construct function via parent::__construct() however since I need to pass it the class dependencies I need to set these dependencies in the child constructor resulting in something that looks like this:

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 } 

Now this works fine in terms of its functionality; however it doesn't seem very efficient to me to have to include the parent's dependencies in every child class that has a constructor. It also looks quite messy. Does Laravel provide a way around this, or does PHP support a way of calling both the parent and child constructor without having to call parent::__construct() from the child?

I know this is a long question for what is effectively not a problem but more me just be ocd about efficiency, but I appreciate any ideas and/or solutions.

Thanks in advance!

like image 602
ArranJacques Avatar asked Oct 11 '13 20:10

ArranJacques


2 Answers

There isn't a perfect solution and it's important to understand that this isn't an issue with Laravel itself.

To manage this, you can do one of three things:

  1. Pass the necessary dependencies to the parent (which was your issue)

    // 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. Auto resolve the dependencies in the parent construct as stated by @piotr_cz in his answer

  3. Create the instances in the parent construct instead of passing them as parameters (so you don't use Dependency Injection):

    // 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(); } 

If you want to test your classes, the third solution will be more difficult to test. I'm unsure if you can mock the classes using the second solution, but you mock them using the first solution.

like image 119
Luís Cruz Avatar answered Sep 28 '22 05:09

Luís Cruz


I know this is a super old question, but I just finished grinding on a similar question on my current project and came to an understanding with the issue at hand.

The basic underlying question here is:

If I'm extending a parent class that has a constructor. That constructor has injected dependancies and all of it's dependencies are already documented in the parent itself. Why do I have to include the parent's dependencies again in my child class?

I ran into this same issue.

My parent class requires 3 different dependencies. They're injected via the constructor:

<?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;     } 

The class is an abstract class so I can never instantiate it on it's own. When I extend the class, I still need to include all of those dependencies and their use references in the child's constructor:

<?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);     } 

Including the use statements for the 3 parent dependencies in each class seemed like duplicate code since they're already defined in the parent constructor. My thought was to remove the parent use statements as they'll always need to be defined in the child class that extends the parent.

What I realized is that including the use for the dependencies in the parent class and including the class names in the parent's constructor are ONLY needed for type hinting in the parent.

If you remove the use statements from the parent and the type hinted class name from the parents constructor, you get:

<?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;     } 

Without the use statements and type hinting from the parent, it can no longer guarantee the type of class being passed to it's constructor because it has no way of knowing. You could construct from your child class with anything and the parent would accept it.

It does seem like double entry of code, but really in your paren't you're not constructing with the dependencies laid out in the parent, you're verifying that the child is sending in the correct types.

like image 45
Chris Schmitz Avatar answered Sep 28 '22 04:09

Chris Schmitz