こんにちは。よっしーです(^^)
今日は、PHP-DIを導入して実行するまでの方法についてご紹介します。
背景
PHP-DIに触れる機会がありましたので、PHP-DIを導入して実行するまでの方法を備忘として残しました。
作業ディレクトリの作成
下記のコマンドを実施します。
mkdir 22_learn-php-di
cd 22_learn-php-di
PHPのバージョンを指定
下記のコマンドを実施します。
asdf local php 8.2.7
asdfコマンドによるPHPの導入方法は、下記の記事をご覧ください。
composerコマンドの用意
下記のコマンドを実施します。
touch compose.yml
compose.ymlに下記の内容を保存します。
version: '3.8'
services:
composer-cmd:
image: composer
container_name: composer-cmd
volumes:
- .:/app
下記のコマンドを実施します。
docker compose run -it --rm composer-cmd composer --version
下記のような結果になっていれば成功です。
% docker compose run -it --rm composer-cmd composer --version
Composer version 2.5.8 2023-06-09 17:13:21
PHP-DIのインストール
下記のコマンドを実行します。
docker compose run -it --rm composer-cmd composer require php-di/php-di
実行ファイルの準備
下記のファイルを用意します。ファイルの内容は下記のセクションで記載しています。
new file: Mailer.php
new file: UserManager.php
new file: Sample01.php
new file: Sample02.php
Mailer.php
下記の内容でファイルを新規作成します。
<?php
class Mailer
{
public function mail($recipient, $content)
{
echo sprintf('%s : %s', $recipient, $content);
}
}
UserManager.php
下記の内容でファイルを新規作成します。
<?php
class UserManager
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function register($email, $password)
{
// The user just registered, we create his account
// ...
// We send him an email to say hello!
$this->mailer->mail($email, 'Hello and welcome!');
}
}
Sample01.php
下記の内容でファイルを新規作成します。
<?php
require_once "vendor/autoload.php";
$mailer = new Mailer();
$userManager = new UserManager($mailer);
$userManager->register('dummy@mail.com', 'password');
Sample02.php
下記の内容でファイルを新規作成します。
<?php
require_once "vendor/autoload.php";
$container = new DI\Container();
$userManager = $container->get('UserManager');
$userManager->register('dummy@mail.com', 'password');
composer.json
下記の緑色の箇所を追記します。
{
"autoload": {
"files": ["./Mailer.php", "./UserManager.php"]
},
"require": {
"php-di/php-di": "^7.0"
}
}
動作確認
PHP-DI がなければ、Sample01.phpのように依存関係を手動で「接続」しなければなりません。
実行結果は下記のとおりです。
% php Sample01.php
dummy@mail.com : Hello and welcome!
それに対して、PHP-DIを使用すると、Sample02.phpのように依存関係をPHP-DIに解決させることができます。
実行結果は下記のとおりです。
% php Sample02.php
dummy@mail.com : Hello and welcome!
それでは、どのようにして何を注入するか、どうやって知っているのでしょうか?
このコンテナは、自動接続(autowiring)と呼ばれる技術を使用しています。これはPHP-DIに特有のものではありませんが、それでも非常に優れています。コードをスキャンして、コンストラクタで必要なパラメーターを調べます。
この例では、UserManagerのコンストラクタはMailerオブジェクトを受け取ります。PHP-DIはこれを作成する必要があると知っています。非常に基本的な手法ですが、非常に効率的です。
でも、PHPコードをそのようにスキャンするのは奇妙でリスキーではないでしょうか?
心配しないでください、PHP-DIはPHPのReflectionクラスを使用しており、これはかなり標準的な方法です。LaravelやZend Frameworkなど、多くの他のコンテナも同じ方法を採用しています。パフォーマンス的には、そのような情報は一度読み取られてからキャッシュされるため、影響はありません。
依存関係の定義について
私たちは自動接続(autowiring)を見てきました。これは、PHP-DIがクラスが必要とする依存関係を自動的に解決する方法です。しかし、クラスに何をinjectionするかを定義する方法は3つあります。
- 自動接続を使用する方法
- 属性を使用する方法
- PHP定義を使用する方法
これらの方法はそれぞれ異なり、任意の選択肢です。以下は、PHP定義を使用する例です。ファイル内に以下のような内容が記述されています。
return [
'api.url' => 'http://api.example.com',
'Webservice' => function (Container $c) {
return new Webservice($c->get('api.url'));
},
'Controller' => DI\create()
->constructor(DI\get('Webservice')),
];
この定義方法については、後日ご紹介したいと思います。
フレームワークでの使用について
先ほどの例でわかるように、コンテナを使用してオブジェクトを取得できます。
$userManager = $container->get('UserManager');
ただし、アプリケーション内の至る所でコンテナを呼び出すのは避けたいです。なぜなら、それはコードをコンテナに依存させてしまうからです。これはサービスロケーターアンチパターンとして知られており、依存関係を取得する代わりにinjectionにするべきです。
Symfonyのドキュメントを引用すると:
「ある時点でコンテナから[オブジェクト]を取得する必要がありますが、これはアプリケーションのエントリーポイントでできるだけ少ない回数にすべきです。」
このため、PHP-DIはいくつかのフレームワークと統合しており、(コントローラーに依存関係が注入されるため)コンテナを呼び出す必要はありません:
- Symfony
- Slim
- Silex
- Zend Framework 2
- Zend Expressive
- Silly
別のフレームワークや独自のコードでPHP-DIを使用する場合は、ルートアプリケーションクラスまたはフロントコントローラー内で$container->get()
を使用することをおすすめします。このデモアプリケーションを見ると、PHP-DIを中心に構築された実践的な例が確認できます。
おわりに
今日は、PHP-DIを導入して実行するまでの方法についてご紹介しました。
本記事でご紹介したソースは、下記のリポジトリにあります。
https://github.com/Gate-Yossi/Musica/releases/tag/v2023.09.04
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント