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;
}
}
Im, используя Laravel IOC-контейнер для ввода зависимостей классов в конструктор. Затем у меня есть разные классы контроллеров, которые управляют различными модулями, составляющими 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
. Например:
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()
из дочернего элемента?
Я знаю, что это длинный вопрос для того, что действительно не проблема, но больше я просто хочу узнать об эффективности, но я ценю любые идеи и/или решения.
Спасибо заранее!
Ответы
Ответ 1
Там путь.
Когда 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 для приложения
Ответ 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);
}
Включение операторов use
для трех родительских зависимостей в каждом классе выглядело как дублированный код, поскольку они уже определены в родительском конструкторе. Моя мысль заключалась в том, чтобы удалить родительские инструкции use
, поскольку они всегда должны быть определены в дочернем классе, который расширяет родительский элемент.
Что я понял, так это то, что включение use
для зависимостей в родительском классе и включение имен классов в родительский конструктор ТОЛЬКО необходимо для ввода типа в родительском.
Если вы удаляете инструкции use
из родительского элемента и имя типа намеченного класса из конструктора parent, вы получаете:
<?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
Нет идеального решения, и важно понять, что это не проблема с самим Laravel.
Чтобы справиться с этим, вы можете сделать одну из трех вещей:
-
Передайте необходимые зависимости родителям (это была ваша проблема)
// 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);
}
-
Автоматически разрешать зависимости в родительской конструкции, как указано @piotr_cz в его ответе
-
Создайте экземпляры в родительской конструкции вместо передачи их в качестве параметров (поэтому вы не используете 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();
}
Если вы хотите протестировать свои классы, третье решение будет сложнее тестировать. Я не уверен, что вы можете издеваться над классами, используя второе решение, но вы издеваетесь над ними, используя первое решение.
Ответ 4
Вы должны передать зависимости родительскому конструктору, чтобы они были доступны в дочернем элементе. Невозможно вставить зависимости родительской конструкции, когда вы создаете экземпляр через дочерний элемент.
Ответ 5
Я столкнулся с той же проблемой при расширении моего базового контроллера.
Я выбрал другой подход, чем другие решения, показанные здесь. Вместо того, чтобы полагаться на инъекцию зависимостей, я использую app() → make() в конструкторе родителей.
class Controller
{
public function __construct()
{
$images = app()->make(Images::class);
}
}
В этом более простом подходе могут быть недостатки - возможно, что код менее подвержен тестированию.