はじめに
前回は、クイックスタートを動かすことができたので、
今回はBEAR Serverの特徴の1つ、DIを試してみます。
DIにはたくさんの種類がありますが、今回は基本の「コンストラクタインジェクション」を試してみます。
ゴール
DIされたPersonManagerクラスにタロウさんとマイケルの二人の情報を登録し、
listメソッドで登録されている一覧をJSONで返すという単純な例です。
今回作る成果物をレンタルサーバーXServer上にアップロードし、
http://{あなたのドメイン}/foo/
ターゲットURLにアクセスすると
JSONレスポンス
{
"list": [
{
"name": "Taro",
"country": "JP"
},
{
"name": "Michale",
"country": "US"
}
],
"_links": {
"self": {
"href": "/index"
}
}
}
が表示されることがゴールになります。
登場クラス
- 人材データを保存するPersonクラス
- 人材管理用クラスのPersonManagerクラス
- DIで使用するためのPersonManagerIntarfaceインターフェース
公式 DIの説明
ローカル開発環境
前回と同様 Windowsにて開発
- Windows7 CLI環境
- PHP 7.2.5 (cli) (built: Apr 25 2018 02:20:05) ( ZTS MSVC15 (Visual C++ 2017) x64 )
- Composer version 1.6.4 2018-04-13 12:04:24
サーバー環境の準備 (xserverの場合)
- 必要なもの
- SSHでの接続
- FTPでの接続
参考: XServerの標準パス例
* [ドメインルート]
* URL
- http://{あなたのドメイン名}/
* サーバードメインルートパス:(ウェブ非公開フォルダ)
- /home/{ユーザー名}/{あなたのドメイン名}/
* ウェブ公開パス
- /home/{ユーザー名}/{あなたのドメイン名}/public_html/
BEAR.Sundayのプロジェクトの作成
cd C:\bear_di (作業フォルダ。環境によって変えてください。)
C:\bear_di>composer create-project -n bear/skeleton MyVendor.DiProject
数分でインストール完了します。
CLI(コマンドプロンプト)で動作確認
C:\bear_di\MyVendor.DiProject\> php bootstrap\web.php get /
以下のようなレスポンスが帰ってくればOKです
200 OK
content-type: application/hal+json
{
"greeting": "Hello BEAR.Sunday",
"_links": {
"self": {
"href": "/index"
}
}
}
作業の手順
-
Interfaceを作る
- PersonManagerInterface
-
classを作る
- PersonManager
- Person
-
AppModuleにDIを書く
-
ページリソースの Index.phpに実装する
+ 二人の情報をPersonManngerに登録して、一覧を帰り値とする -
ローカルでテストを行う
-
サーバーにアップロード
-
シンボリックリンクを貼る
-
ブラウザで表示
srcフォルダのファイルツリー
Interfaceを作る
ポイント
srcフォルダへのnamespageはデフォルトでは、MyVendor\MyProject になる
<?php
namespace MyVendor\MyProject;
interface PersonManagerInterface {
public function register(Person $p);
public function list();
}
classを作る
src\PersonManager.php
<?php
namespace MyVendor\MyProject;
class PersonManager implements PersonManagerInterface {
// 登録ユーザーの保存用
private $personList = [];
// ユーザーを登録する
public function register(Person $p) {
array_push($this->personList, $p);
}
// ユーザー一覧を返す
public function list() {
return $this->personList;
}
}
src\Person.php
<?php
namespace MyVendor\MyProject;
class Person {
public $name;
public $country;
static function create($name, $country) {
$p = new Person();
$p->name = $name;
$p->country = $country;
return $p;
}
}
依存を受け取る箇所をPageリソースのIndex.phpに記述する
src\Resource\Page\Index.php
<?php
namespace MyVendor\MyProject\Resource\Page;
use Ray\Di\Inject; // 追加した
use Ray\Di\Named; // 追加した
use BEAR\Resource\ResourceObject;
// 作成したPerson, PersonManagerを読み込む
use MyVendor\MyProject\Person;
use MyVendor\MyProject\PersonManager;
use MyVendor\MyProject\PersonManagerInterface;
class Index extends ResourceObject
{
private $pm; // PersonManagerInterface
/**
* コンストラクタインジェクション
*/
function __construct(PersonManagerInterface $pm) {
$this->pm = $pm;
}
public function onGet(string $name = 'BEAR.Sunday') : ResourceObject
{
if ($this->pm == null) {
$this->body = [
'error' => "error: pm is null. It is not DI inject."
];
return $this;
}
$taro = Person::create("Taro", "JP");
$michale = Person::create("Michale", "US");
// 二人追加する
$this->pm->register($taro);
$this->pm->register($michale);
// 一覧を取得
$list = $this->pm->list();
$this->body = [
'list' => $list
];
return $this;
}
}
AppModule.phpに束縛を追加する
今回は、リンク束縛 を使います。
書式は、
$this->bind($interface)->to($class);
公式には「 最も基本の束縛 です」と書かれていますが、
この基本とは「インターフェイスとクラスを束縛するので”基本” by Koriymさん」とのことです。
(※ ただ例えば文字列の場合はインターフェイスがないので使えません。)
束縛とは?
インジェクタの仕事はオブジェクトグラフを作成することです。
型を指定してインスタンスを要求し、依存関係を解決し、すべてを結びつけます。
依存関係の解決方法を指定するにはバインディングを設定します。
公式HPより引用
https://bearsunday.github.io/manuals/1.0/ja/di.html
それでは、AppModule.phpに束縛(bind部分)を追加します
src\Module\AppModule.php
<?php
namespace MyVendor\MyProject\Module;
use BEAR\Package\PackageModule;
use josegonzalez\Dotenv\Loader;
use Ray\Di\AbstractModule;
use Ray\Di\Di\Named;
use MyVendor\MyProject\PersonManagerInterface;
use MyVendor\MyProject\PersonManager;
class AppModule extends AbstractModule
{
/**
* {@inheritdoc}
*/
protected function configure()
{
// 追加した
$pm = new PersonManager();
// 追加した「リンク束縛」
$this->bind(\MyVendor\MyProject\PersonManagerInterface::class)
->to(PersonManager::class);
// 最初から記述されている部分
$appDir = dirname(__DIR__, 2);
(new Loader($appDir . '/.env'))->parse()->toEnv();
$this->install(new PackageModule);
}
}
AppModule.php内では、 リンク束縛 を
$this->bind(\MyVendor\MyProject\PersonManagerInterface::class)
->to(PersonManager::class);
のように記述しています。
以上で準備が完了です。
PHPUnitによるテストを行う
BEAR.Sundayのスケルトンでは ローカル上 でも PHPUnit が使えるようになっているので、
以下のようにコマンドを入力することでテストが行えます。
composer test
このままではエラーが出ますので、IndexTest.php に期待するテストコードを記述します。
tests\Resource\Page\IndexTest.php
<?php
namespace MyVendor\MyProject\Resource\Page;
use BEAR\Package\AppInjector;
use BEAR\Resource\ResourceInterface;
use BEAR\Resource\ResourceObject;
use PHPUnit\Framework\TestCase;
class IndexTest extends TestCase
{
/**
* @var ResourceInterface
*/
private $resource;
protected function setUp()
{
$this->resource = (new AppInjector('MyVendor\MyProject', 'app'))->getInstance(ResourceInterface::class);
}
public function testOnGet()
{
$ro = $this->resource->uri('page://self/index')(['name' => 'BEAR.Sunday']);
/* @var ResourceObject $ro */
$this->assertSame(200, $ro->code);
// リストを取り出す
$list = $ro->body['list'];
$person1 = $list[0];
$person2 = $list[1];
// テスト
$this->assertSame('Taro', $person1->name);
$this->assertSame('JP', $person1->country);
$this->assertSame('Michale', $person2->name);
$this->assertSame('US', $person2->country);
}
}
上記のように期待するテストを追加したあと、再度composer testを実行します。
composer test
このように OK がでれば完了です。
これでサーバーへアップロードする準備ができました。
MyVendor.DiProjectをzipファイルにまとめる(ローカル側)
好きなツールを使って、MyVendor.DiProjectフォルダごとzipに圧縮
MyVendor.DiProject.zipができる
サーバー側のアップロード先フォルダ「app_bear」の作成
[注意ポイント]
アップロードするコードなどは ウェブ非公開のフォルダ に保存すること
・ウェブ 非公開フォルダ のドメインルートパスに、「app_bear」フォルダを作成
$cd /home/{ユーザー名}/{あなたのドメイン名}/
$mkdir app_bear
作成したプロジェクトをサーバーのapp_bearフォルダへ転送
転送先 /home/{ユーザー名}/{あなたのドメイン名}/app_bear/
SCPやGit、FTPなどを使ってapp_bearフォルダへ転送します。
注意
XServerの場合、 GitやSCPは正式にサポートされていない ので :warning: 自己責任 :warning: で行ってください。
私自身はGITリポジトリの作成やSCP接続でのアップロードはネットを参考に使用することはできましたが、
:warning: 何か問題が発生してホームページが表示されない等のトラブルがあってもXServerのサポートを受けることができません。:warning:
XServerで利用する場合には、下記の「FTPで転送したい場合」を試すか、
GitやSCPを使いたい方は、素直にGitやSCPサポートを受けられるサーバーを利用することをお勧めします。
メモ
FTPで転送したい場合
FTPの場合、転送するファイル数が多いので、ローカル側でZIP圧縮したファイルを送り、サーバー側で展開すると効率的です。
- サーバーへSSH接続後、
cd /home/{ユーザー名}/{あなたのドメイン名}/app_bear
unzip MyVendor.DiProject.zip
展開すると MyVendor.DiProjectフォルダが作成され、
そのなかに展開されたファイルが
/home/{ユーザー名}/{あなたのドメイン名}/app_bear/MyVendor.DiProject
にファイルが設置されます。
web公開フォルダに、「foo」ディレクトリを作る
cd /home/{ユーザー名}/{あなたのドメイン名}/public_html/
mkdir foo
cd foo
XServer用のbootstrapを作成する
通常、web公開フォルダをドメインルートにした場合はこの作業は必要ありませんが、
http://{あなたのドメイン名}/foo/
のように サブディレクトリ内 で動かす場合には、
サーバーごとのPHPのREQUEST_URIに合わせて、bootstrapファイルを作ると便利です。
BEAR.Sundayには、api用のbootstrapである「api.php」と、webページ用の「web.php」が用意されています。
今回はページリソースのIndex.phpに実装しているので、web.phpをベースにして、xserver.phpを作成します。
cd /home/{ユーザー名}/{あなたのドメイン名}/app_bear/MyVendor.DiProject/bootstrap
cp web.php xserver.php
次に、このファイルを修正します。
XServerの場合は、
http://{あなたのドメイン名}/foo/
にアクセスすると
[REQUEST_URI] => /foo/
と設定されるので、xserver.php内で、以下のようにします。
<?php
$_SERVER["REQUEST_URI"] = "/";
$context = PHP_SAPI === 'cli' ? 'cli-hal-app' : 'hal-app';
require __DIR__ . '/bootstrap.php';
2行目のここがポイントです。
$_SERVER["REQUEST_URI"] = "/";
これを記述することによって、/fooディレクトリをサービスの起点に変更できます。
ユーザーによっては / ではいけない場合があるかもしれないので、その場合は調整してください。
BEAR.Sundayのpublicフォルダ内にある 「.htaccess」と「index.php」にシンボリックリンクを貼る
ln -s /home/{ユーザー名}/{あなたのドメイン名}/app_bear/MyVendor.DiProject/public/.htaccess .
ln -s /home/{ユーザー名}/{あなたのドメイン名}/app_bear/MyVendor.DiProject/bootstrap/xserver.php index.php
ブラウザでアクセス
http://{あなたのドメイン名}/foo/hello
表示が下記のようになれば成功です。
JSONレスポンス
{
"list": [
{
"name": "Taro",
"country": "JP"
},
{
"name": "Michale",
"country": "US"
}
],
"_links": {
"self": {
"href": "/index"
}
}
}
おつかれさまでした。
以上で終了です。
これでBEAR.SundayのDIの基本的な リンク束縛を試すことが出来ました。
DIにはこのほかにもセッターインジェクションなどたくさんの例がありますので
公式ページを参考にしてみてください。
公式HP DI
なぜDIを使うの?
なぜDIを使うのかというと、サービスクラスの 疎結合 を目的にしています。
サービスの実装をコード内で new してしまうと(密結合と呼ばれる)、使用するサービスを変更したいときには、
使用しているソースコードの変更が必要になります。
DIを使うことで、使用する実装を注入することができるので、結合度がゆるくなります。
これがDIの利点だと思います。
DIについては下記のリンクが参考になると思います。
「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した
https://qiita.com/mizunowanko/items/53eed059fc044c5aa5dc
最後に
BEAR.Sundayの開発者さまのみなさま、特に今回も直接サポートしていただいたKoriymさんに感謝を申し上げます。
XServerなどのレンタルサーバーでも、BEAR.Sundayがどんどん使われること願って。