Я создаю 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.
Чтобы справиться с этим, вы можете сделать одно из трех:
Передайте необходимые зависимости родителям (это была ваша проблема)
// 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(); }
Если вы хотите протестировать свои классы, третье решение будет сложнее тестировать. Я не уверен, что вы можете издеваться над классами, используя второе решение, но вы издеваетесь над ними, используя первое решение.
Вы должны передать зависимости родительскому конструктору, чтобы они были доступны в дочернем элементе. Невозможно вставить зависимости родительской конструкции, когда вы создаете экземпляр через дочерний элемент.
Я столкнулся с той же проблемой, когда расширял свой базовый контроллер.
Я выбрал другой подход, чем другие решения, показанные здесь. Вместо того, чтобы полагаться на инъекцию зависимостей, я использую app () -> make () в конструкторе parent.
class Controller { public function __construct() { $images = app()->make(Images::class); } }
Возможно, к этому более простому подходу могут быть недостатки – возможно, сделать код менее проверяемым.