"BOKU"のITな日常

62歳・文系システムエンジニアの”BOKU”は日々勉強を楽しんでます

はじめてLaravel6.0:オリジナルサービス作成とDI(依存性注入)/Windows10

今回はサービスクラスを作成します。

サービスプロバイダを介して、コントローラクラス内でサービスクラスをDI(依存性注入)して利用するやり方(Laravelのお作法)を確認します。

f:id:arakan_no_boku:20191003220222p:plain

 

 はじめに

 

今回は、コントローラクラスの中で使うサービスクラスの作成をします。

Laravelに限らず、MVCモデルのコントローラクラスの中にロジックを全部書くなんてことは基本しません。

ja.wikipedia.org

ロジック部分をサービスクラスに切り出して、それをコントローラクラスで使います。

今回は、そんなサービスクラスを作成するLaravelの作法を確認します。

なお。

今回の目的は、Laravelのお作法の確認中心にしたいので、DBとかは使いません。

あまり、あれこれやると、ポイントがブレますからね。

 

実装手順の確認から

 

Laravel6.0の作法にそったサービスを作る手順は。

  1. app\Servicesフォルダに、サービスクラスを作成する。
  2. php artisan make:provider <privider name>コマンドでサービスプロバイダを生成する
  3. app/Providersフォルダに生成されたサービスプロバイダを編集する。
  4. config/app.phpのproviders=>に作成したサービスプロバイダを登録する。

です。

今回は手順確認だけが目的のダミーサービスなので名称も。

  • DummyService.php
  • DummyServiceProvider.php

にします。

Viewとコントローラは、前回使ったものと同じにします。

  • home.blade.php
  • HomeController.php

ですね。

さて、やってみます。

 

app\Servicesフォルダに、サービスクラスを作成する。

 

簡単にするために、前回「HomeController」内に直書きしていたロジック部分を外だしするだけのサービスクラスを作ります。

前回やっていたのは。

  • Carbonを使って今日の日付を取得する。
  • Carbonを使って10日後の日助を取得する。
  • リスト1からリスト4の文字列の配列を作る

です。

ちなみに。

CarbonはPHPのDateTimeのBetter版といった感じの日付処理ライブラリです。

carbon.nesbot.com

ちょっと検索すれば、サンプルはいくらでもでてくる位有名です。

さて。

このうち上の2つは「今日=0日後の日付」とも考えられるので、ひとつのメソッドにまとめることにしました。

結果、こんな感じになります。

app\Services\DummyService.php

<?php
declare(strict_types=1);

namespace App\Services;

use Carbon\Carbon;

class DummyService
{
    public function getDate(int $later): string
    {
        return Carbon::now()->addDay($later)->format('Y年m月d日');
    }
    
    public function getArray(): array
    {
        $tests = [
            1 => 'リスト1',
            2 => 'リスト2',
            3 => 'リスト3',
            4 => 'リスト4'];
        return $tests;
    }
}

 

さて、今度はこれをDI(依存性注入)できるようにしていきます。 

なんて・・。

さらっと書きましたが、このDI(依存性注入)って言葉はわかりづらいです。

なので、少し寄り道して、DIについてちょっと書きます。

 

DI(依存性注入)とは?

 

Laravelのドキュメントの説明を引用します。

そこには「コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入す。」ことだと書いてあります。

依存している・・。

言葉的に難しそうに見えます。

でも、実際のところ「クラスAを使用するクラスに対して、クラスAのオブジェクトを外部から暗黙的に渡す」だけなんですよね。

例えば。

Databaseとやりとりするアダプタの指定は、DIを使わないならこんな感じです。

$this->adapter = new MySqlAdapter;

でも。

これだと「MySqlAdapter;」クラスと、使っているクラスの結びつきは強いです。

DatabaseをMySqlAdapter以外でアクセスする必要があるときに、使っている箇所を探して、ゴリゴリ書き換えたりしないといけなくなります。

それでは保守性もおちるし、テストもしづらい。

そこで。

$this->adapter = $adapter; 

 みたいにしてやります。

そして、この「$adapter」に対して、外部からクラスオブジェクト(例だとMySqlAdapter)を外部から注入するようにしてやります。

そうすると、疎結合になるので、テスト時にMock等で代替するのもしやすくなるし、長期的な保守性もあがります。

このやり方を「DI(依存性注入)」とよんでいます。

そして、この$adapterに対してクラスオブジェクトを外部から渡す方法に、コンストラクタを使う方法とメソッドの引数を使う方法があるということです。

 

サービスコンテナという仕組 

 

さて。

Laravelで上記の「DI(依存性注入)」を実装する「お作法」を確認していきます。

Laravelではサービスコンテナという仕組を使います。

Laravelのドキュメントでは、サービスコンテナとは、「クラス間の依存を管理する強力な管理ツール」だとされています。

実際には。

サービスコンテナクラスみたいなソースを書いたりするわけではありません。

サービスプロバイダクラスを作成して、その中で「$this->app->bind()」メソッドを使って、サービスコンテナにクラスを登録します。

つまり、「$this->app」部分が、サービスコンテナを指しているというわけです。

仕組上。

このように「サービスコンテナ」に、DIしたいクラスを登録してすれば「DI(依存性注入)」できるようになるわけです。

お作法としては、こんな感じです。

 

php artisan make:providerコマンドでサービスプロバイダを生成

 

まずは、サービスプロバイダクラスを生成します。

プロジェクトフォルダをカレントにして。

php artisan make:provider DummyServiceProvider

を実行します。

成功すると以下のメッセージを表示します。

Provider created successfully.

よしよし。 

 

app/Providersフォルダに生成されたサービスプロバイダを編集

 

上記で生成されたソースはこんな感じです。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class DummyServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

registerとbootというメソッドが、上記ソースにはあります。 

それぞれの役割は、下記リンクのLaravelのドキュメントに詳しく書かれていますが、簡単に要約してみるとこんな感じですかね

  • register:サービスコンテナに何かを結合することだけを行なう。
  • boot:view composerをサービスプロバイダで登録する。

readouble.com

今回作成するのは「サービスクラス」です。

なので「サービスコンテナ」を使います。

ということで・・。

registerメソッドが対象となります。

 

サービスプロバイダはこんな感じでやってみました

 

登録する部分を追加したサービスプロバイダクラスはこんな感じになります。

app\Providers\DummyServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

use App\Services\DummyService;

class DummyServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('DummyService', DummyService::class);
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

DummyServiceをuseして、「$this->app->bind()」という、文字通りのことを「register」の中でやっているだけなのがよくわかります。

 

config/app.phpのproviders=>に作成したサービスプロバイダを登録

 

作成したDummyServiceProviderを、config\app.phpに登録します。

app.phpの中に「'providers' => []」という配列があって、そこにデフォルトで沢山のプロバイダがすでに記入されてます。

そこに、以下のような感じで追加します。

 /*
* User Service Providers...
*/
App\Providers\DummyServiceProvider::class, 

 これで保存しておけば準備OKです。

 

コントローラクラスでサービスをDIして利用する

 

最後に、「HomeController.php」を修正して、DummyServiceクラスをDIして利用するようにします。

app\Http\Controllers\HomeController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Services\DummyService;

class HomeController extends Controller
{
 
    protected $dummy;
    
    public function __construct(DummyService $dummyService)
    {
        $this->middleware('auth');
        $this->dummy = $dummyService;
    }

    public function index()
    {
        $today = $this->dummy->getDate(0);
        $thatday = $this->dummy->getDate(10);
        $tests = $this->dummy->getArray();
        return view('home', compact(
            'today',
            'thatday',
            'tests'
        ));
    }
}

やっていることは。 

  • use App\Services\DummyService;する。
  • protected $dummy;でクラスオブジェクトを注入する変数を定義する。
  • コンストラクタに$this->dummyへ引数で渡したDummyServiceを代入する
  • クラスメソッドを$this->dummy経由で呼び出して使う

ということだけです。

でも、こうしておくとですね。

例えばDummyServiceクラスの実装がまだFIXしていなくても、モックを使ってコントローラのテストもやれますし、仮に、サービスの中で利用しているクラス(今回だとCarbon)が何かの事情で使えなくなった時にも、変更はサービスの内部だけに局所化できて保守性もあがります。

 

さて実行してみます。

 

実行の仕方は前回と一緒で、MySQL動かして、「php artisan serve」でビルトインサーバーを動かして、localhostにアクセスするだけなので割愛します。

実行した結果がこちら。

f:id:arakan_no_boku:20191013164321p:plain

正しく動いてます。

よしよし。

今回はこんなところで。

ではでは。