Laravel 4 – Конструктор родительских конструкторов дочерних конструкторов с инъекцией зависимости

Я создаю CMS, используя Laravel 4, и у меня есть базовый администратор для административных страниц, который выглядит примерно так:

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

Я использую контейнер IOC Laravel для ввода зависимостей классов в конструктор. Затем у меня есть разные классы контроллеров, которые управляют различными модулями, составляющими CMS, и каждый класс расширяет класс admin. Например:

 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 и более эффективной, возникает, когда я добавляю конструктор в класс 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 , больше недействительны, вызывая ошибки. Я могу вызвать функцию конструирования контроллера admin через 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() из дочернего элемента?

Я знаю, что это длинный вопрос для того, что действительно не проблема, но больше я просто буду о производительности, но я ценю любые идеи и / или решения.

Заранее спасибо!

Есть способ. Когда 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 для применения

Я знаю, что это очень старый вопрос, но я только что закончил работу над подобным вопросом в моем текущем проекте и пришел к пониманию проблемы.

Основной основной вопрос здесь:

Если я расширяю родительский класс, у которого есть конструктор. Этот конструктор вводит зависимости, и все его зависимости уже задокументированы в самом родителе. Почему я должен снова включать зависимости родителя в мой дочерний класс ?

Я столкнулся с этой проблемой.

Мой родительский класс требует 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; } 

Класс является абстрактным классом, поэтому я никогда не могу его создать самостоятельно. Когда я расширяю класс, мне все равно нужно включить все эти зависимости и их ссылки на использование в конструкторе child:

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

Включение операторов 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 и типа, намекающих от родителя, он больше не может гарантировать тип класса, передаваемого его конструктору, потому что он не знает. Вы можете построить из своего дочернего класса что угодно, и родитель согласился бы с ним.

Это похоже на двойной ввод кода, но на самом деле вы не строите с зависимостями, изложенными в родительском, вы проверяете, что ребенок отправляет правильные типы.

Не существует идеального решения, и важно понимать, что это не проблема с самим 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. Создайте экземпляры в родительской конструкции вместо передачи их в качестве параметров (поэтому вы не используете Injection Dependency):

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

Если вы хотите протестировать свои классы, третье решение будет сложнее тестировать. Я не уверен, что вы можете издеваться над классами, используя второе решение, но вы издеваетесь над ними, используя первое решение.

Вы должны передать зависимости родительскому конструктору, чтобы они были доступны в дочернем элементе. Невозможно вставить зависимости родительской конструкции, когда вы создаете экземпляр через дочерний элемент.

Я столкнулся с той же проблемой, когда расширял свой базовый контроллер.

Я выбрал другой подход, чем другие решения, показанные здесь. Вместо того, чтобы полагаться на инъекцию зависимостей, я использую app () -> make () в конструкторе parent.

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

Возможно, к этому более простому подходу могут быть недостатки – возможно, сделать код менее проверяемым.