Laravel 4を使用してCMSを構築していて、次のような管理ページ用の基本管理コントローラーを持っています。
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;
}
}
LaravelのIOCコンテナーを使用して、クラスの依存関係をコンストラクターに注入します。次に、CMSを構成するさまざまなモジュールを制御するさまざまなコントローラークラスを用意し、各クラスが管理クラスを拡張します。次に例を示します。
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()
を呼び出さなくても親コンストラクタと子コンストラクタの両方を呼び出す方法をサポートします子供から?
これは何が問題ではないのかという長い質問であることは承知していますが、効率性の問題に悩んでいますが、アイデアや解決策はありがたいです。
前もって感謝します!
完璧な解決策はありません。これはLaravel自体の問題ではないことを理解することが重要です。
これを管理するには、次の3つのいずれかを行います。
必要な依存関係を親に渡します(これが問題でした)
// 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彼の答え で述べられているように、親構成の依存関係を自動解決します
インスタンスをパラメーターとして渡すのではなく、親コンストラクト内に作成します(したがって、依存性注入は使用しません)。
// 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();
}
クラスをテストする場合は、3番目のソリューションのテストが難しくなります。 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;
}
このクラスは抽象クラスなので、私はneverでインスタンス化できます。クラスを拡張するときも、それらの依存関係とそれらの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);
}
各クラスに3つの親の依存関係の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
ステートメントと親からの型ヒントがないと、コンストラクターに渡されるクラスのタイプを知ることができないため、コンストラクターに渡されることを保証できなくなります。子クラスから何でも作成でき、親はそれを受け入れます。
コードの二重入力のように見えますが、実際には、親でレイアウトされた依存関係を使用して構築していないのではなく、子が正しい型で送信していることを確認しています。
方法があります。 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);
}
}
したがって、子コントローラーでは、Applicationインスタンスのみを渡します。
class MyController extends BaseController {
public function __construct(
// Class dependencies resolved in BaseController
//..
// Application
Application $app
)
{
// Logic here
//..
// Invoke parent
parent::__construct($app);
}
}
もちろん、アプリケーションにFacadeを使用できます
ベースコントローラーを拡張するときに同じ問題に遭遇しました。
ここに示す他のソリューションとは異なるアプローチを選択しました。依存関係の注入に依存するのではなく、親のコンストラクターでapp()-> make()を使用しています。
class Controller
{
public function __construct()
{
$images = app()->make(Images::class);
}
}
この単純なアプローチにはマイナス面もあるかもしれません-おそらくコードをテストしにくくします。
依存関係を親コンストラクターに渡して、子で使用できるようにする必要があります。子を介してインスタンス化するときに、親コンストラクトに依存関係を注入する方法はありません。