LoginSignup
3
0

More than 5 years have passed since last update.

BEAR.SundayでDIをレンタルサーバーで試してみる

Last updated at Posted at 2018-06-04

はじめに

前回は、クイックスタートを動かすことができたので、
今回は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での接続

:information_source: 参考: 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フォルダのファイルツリー

tree.png

Interfaceを作る

:information_source: ポイント

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
<?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さん」とのことです。
(※ ただ例えば文字列の場合はインターフェイスがないので使えません。)

:information_source: 束縛とは?

   インジェクタの仕事はオブジェクトグラフを作成することです。
   型を指定してインスタンスを要求し、依存関係を解決し、すべてを結びつけます。
   依存関係の解決方法を指定するにはバインディングを設定します。

   公式HPより引用
   https://bearsunday.github.io/manuals/1.0/ja/di.html

それでは、AppModule.phpに束縛(bind部分)を追加します

src\Module\AppModule.php

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
<?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

test_ok.png

このように OK がでれば完了です。

これでサーバーへアップロードする準備ができました。

MyVendor.DiProjectをzipファイルにまとめる(ローカル側)

  好きなツールを使って、MyVendor.DiProjectフォルダごとzipに圧縮
  MyVendor.DiProject.zipができる

サーバー側のアップロード先フォルダ「app_bear」の作成

:warning: [注意ポイント]

  アップロードするコードなどは ウェブ非公開のフォルダ に保存すること

・ウェブ 非公開フォルダ のドメインルートパスに、「app_bear」フォルダを作成

  $cd /home/{ユーザー名}/{あなたのドメイン名}/
  $mkdir app_bear

作成したプロジェクトをサーバーのapp_bearフォルダへ転送

  転送先 /home/{ユーザー名}/{あなたのドメイン名}/app_bear/

SCPやGit、FTPなどを使ってapp_bearフォルダへ転送します。

:warning: 注意

   XServerの場合、 GitやSCPは正式にサポートされていない ので :warning: 自己責任 :warning: で行ってください。

   私自身はGITリポジトリの作成やSCP接続でのアップロードはネットを参考に使用することはできましたが、
   :warning: 何か問題が発生してホームページが表示されない等のトラブルがあってもXServerのサポートを受けることができません。:warning:

   XServerで利用する場合には、下記の「FTPで転送したい場合」を試すか、
   GitやSCPを使いたい方は、素直にGitやSCPサポートを受けられるサーバーを利用することをお勧めします。

:information_source: メモ

   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
<?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がどんどん使われること願って。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0