Подтвердить что ты не робот

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() из дочернего элемента?

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

Спасибо заранее!

4b9b3361

Ответ 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);
    }
}

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