33

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!

3
  • 1
    Constructors are not inherited. So you have already answered your own question. If you want the same actions to happen in the constructor of a child class you need to manually call the parent's constructor. Commented Dec 6, 2013 at 2:17
  • 1
    What do you mean by "not very efficient"? Commented Dec 31, 2013 at 22:45
  • i'll add to this question a bit, as I was looking to do the same thing. specifically, I want the base controller to receive the Request object on every call, rather than redoing it in each controller. this allows me to set things in the base controller (like user) without resorting to facades. is it POSSIBLE with PHP? is it something that could be implemented in laravel through the use of reflection? i don't know enough about reflection yet to answer this. thanks. Commented Jul 8, 2015 at 22:28

6 Answers 6

12

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.

Sign up to request clarification or add additional context in comments.

Comments

5

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.

Comments

3

There's a way. When BaseController autoresolves it's dependecies.

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

So in child controller you pass only Application instance:

class MyController extends BaseController {

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

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


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

Of course, we could use Facade for application

Comments

0

You must pass the dependencies to the parent constructor to have them available in the child. There is no way to inject the dependencies on the parent construct when you instantiate it via the child.

1 Comment

So does that mean that the TS solution is the solution? Coz really, I find it messy too to pass the same parameters all over the child classes until it reaches the parent. Having that, the classes are tightly coupled to each other.
0

I came across the same issue, when extending my base Controller.

I opted for a different approach than the other solutions shown here. Rather than rely on dependency injection, I'm using app()->make() in the parents constructor.

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

There may be downsides to this simpler approach - possibly making the code less testable.

Comments

0

I've also ended up with this problem and cleared this mess by not calling constructor at child class and use extra needed dependencies inside the function parameters.

It will work with controllers because you won't need to call these functions manually and you can inject everything there. So common dependencies goes to the parent and less needed will be added to the methods itself.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.