依存関係の自動注入
webmanにおいて依存関係の自動注入はオプション機能であり、この機能はデフォルトで無効になっています。依存関係の自動注入が必要な場合は、php-diの使用をお勧めします。以下はwebmanとphp-di
の組み合わせの使い方です。
インストール
composer require psr/container ^1.1.1 php-di/php-di ^6.3 doctrine/annotations ^1.14
設定ファイルconfig/container.php
を変更し、最終的な内容は以下のようになります:
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();
config/container.php
では最終的にPSR-11
準拠のコンテナインスタンスが返されます。もしphp-di
を使用したくない場合は、ここで他のPSR-11
準拠のコンテナインスタンスを生成して返すことができます。
コンストラクタインジェクション
新しいapp/service/Mailer.php
を作成します(ディレクトリが存在しない場合は自分で作成してください)、内容は以下の通りです:
<?php
namespace app\service;
class Mailer
{
public function mail($email, $content)
{
// メール送信コードは省略
}
}
app/controller/UserController.php
の内容は以下の通りです:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
通常の場合、app\controller\UserController
をインスタンス化するには以下のコードが必要です:
$mailer = new Mailer;
$user = new UserController($mailer);
php-di
を使用すると、開発者はコントローラー内のMailer
を手動でインスタンス化する必要がなく、webmanが自動的に処理します。Mailer
のインスタンス化の過程で他のクラスへの依存がある場合、webmanは自動的にインスタンス化し、注入します。開発者は初期化作業を一切行う必要がありません。
注意
フレームワークまたはphp-di
が作成したインスタンスでなければ、依存関係の自動注入は完了しません。手動でnew
されたインスタンスでは依存関係の自動注入ができません。注入が必要な場合は、new
文をsupport\Container
インターフェイスに置き換える必要があります、例えば:
use app\service\UserService;
use app\service\LogService;
use support\Container;
// newキーワードで作成されたインスタンスは依存注入できません
$user_service = new UserService;
// newキーワードで作成されたインスタンスは依存注入できません
$log_service = new LogService($path, $name);
// Containerで作成されたインスタンスは依存注入できます
$user_service = Container::get(UserService::class);
// Containerで作成されたインスタンスは依存注入できます
$log_service = Container::make(LogService::class, [$path, $name]);
アノテーションインジェクション
コンストラクタの依存関係の自動注入に加えて、アノテーションによる注入も可能です。先の例を引き続き、app\controller\UserController
を以下のように変更します:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var Mailer
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
この例は@Inject
アノテーションを使用して注入され、@var
アノテーションによってオブジェクトの型が宣言されています。この例はコンストラクタインジェクションと同様の効果を持ちますが、コードがより簡潔です。
注意
webmanはバージョン1.4.6以前ではコントローラーのパラメータ注入をサポートしていません。例えば、以下のコードはwebmanが<=1.4.6の場合にはサポートされません。
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
// 1.4.6以前のバージョンではコントローラーのパラメータ注入はサポートされていません
public function register(Request $request, Mailer $mailer)
{
$mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
カスタムコンストラクタインジェクション
時にはコンストラクタに渡すパラメータがクラスのインスタンスではなく、文字列、数字、配列などのデータであることがあります。例えばMailerのコンストラクタにsmtpサーバーのIPアドレスとポートを渡す必要がある場合:
<?php
namespace app\service;
class Mailer
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// メール送信コードは省略
}
}
この場合、前述のコンストラクタ自動注入を直接使用することはできません。なぜならphp-di
は$smtp_host
と$smtp_port
の値を決定できないからです。この場合、カスタム注入を試みることができます。
config/dependence.php
(ファイルが存在しない場合は自分で作成してください)に以下のコードを追加します:
return [
// ... 他の設定は省略
app\service\Mailer::class => new app\service\Mailer('192.168.1.11', 25);
];
こうすることで、依存関係注入が必要とする場合、app\service\Mailer
インスタンスは自動的にこの設定で作成されたapp\service\Mailer
インスタンスが使用されます。
私たちは、config/dependence.php
内でMailer
クラスをnew
でインスタンス化していることに注意する必要があります。これは本例では問題ありませんが、もしMailer
クラスが他のクラスに依存している場合や、Mailer
クラス内部でアノテーション注入が使用されている場合、new
での初期化は自動注入をサポートしません。解決策はカスタムインターフェース注入を利用することです。Container::get(クラス名)
またはContainer::make(クラス名, [コンストラクタパラメータ])
メソッドを使用してクラスを初期化します。
カスタムインターフェース注入
実際のプロジェクトでは、具体的なクラスではなく、インターフェースを使ってプログラムすることが望ましいです。例えば、app\controller\UserController
にはapp\service\MailerInterface
をインポートすべきであり、app\service\Mailer
ではありません。
MailerInterface
インターフェースを定義します。
<?php
namespace app\service;
interface MailerInterface
{
public function mail($email, $content);
}
MailerInterface
インターフェースの実装を定義します。
<?php
namespace app\service;
class Mailer implements MailerInterface
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// メール送信コードは省略
}
}
具体的な実装ではなく、MailerInterface
インターフェースをインポートします。
<?php
namespace app\controller;
use support\Request;
use app\service\MailerInterface;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var MailerInterface
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
config/dependence.php
にMailerInterface
インターフェースの実装として以下のように定義します。
use Psr\Container\ContainerInterface;
return [
app\service\MailerInterface::class => function(ContainerInterface $container) {
return $container->make(app\service\Mailer::class, ['smtp_host' => '192.168.1.11', 'smtp_port' => 25]);
}
];
こうすることで、ビジネスでMailerInterface
インターフェースを使用する必要がある場合、自動的にMailer
の実装が使用されます。
インターフェース重視のプログラミングの利点は、特定のコンポーネントを変更する必要がある際、ビジネスコードを変更する必要がなく、
config/dependence.php
内の具体的な実装を変更するだけで済むことです。これは単体テストを実施する際にも非常に有用です。
その他のカスタム注入
config/dependence.php
では、クラスの依存関係だけでなく、文字列、数字、配列などの他の値も定義できます。
例えば、config/dependence.php
で以下のように定義します:
return [
'smtp_host' => '192.168.1.11',
'smtp_port' => 25
];
この場合、@Inject
を使用してsmtp_host
やsmtp_port
をクラスのプロパティに注入することができます。
<?php
namespace app\service;
use DI\Annotation\Inject;
class Mailer
{
/**
* @Inject("smtp_host")
*/
private $smtpHost;
/**
* @Inject("smtp_port")
*/
private $smtpPort;
public function mail($email, $content)
{
// メール送信コードは省略
echo "{$this->smtpHost}:{$this->smtpPort}\n"; // 192.168.1.11:25が出力されます
}
}
注意:
@Inject("key")
の中はダブルクオーテーションです
さらなる内容
詳しくはphp-diのマニュアルをご覧ください。