Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

LaravelでDIを使う

目次

Laravelの記事一覧は下記
PHPフレームワークLaravelの使い方

Laravelバージョン

動作確認はLaravel Framework 7.19.1で行っています

前提条件

eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

Laravelで環境(開発環境と本番環境等)ごとに異なる値を定義する
本記事は上記の内容を理解している前提で書かれています

DIって?

簡単に言うと、作成したクラスをnew演算子を使ってインスタンスを作成せずに、クラスに名前を付けて、付けた名前を使ってインスタンスを呼び出す方法です
そして、名前とクラスのマッピングは独立したファイル内で行います
そうすると、名前とクラスのマッピングファイルを変更するだけで帰ってくるインスタンスのクラスを変えることができます
それの何がいいかというと、ユーティリティクラスやデータベースアクセスクラスなどをテスト時だけダミーの張りぼてクラスに差し替えてテストを実行できるのです

サービスクラス作成

(1) /sample/app/Servicesフォルダ作成
(2) /sample/app/Services/Interfacesフォルダ作成
(3) /sample/app/Services/InterfacesフォルダにSampleService.php作成

SampleService.php
<?php
namespace App\Services\Interfaces;

interface SampleService
{
    public function __construct($sample1 = 0, $sample2 = 0);

    public function getSample1();
    public function setSample1($sample1);

    public function getSample2();
    public function setSample2($sample1);

}

(4) /sample/app/Services/Implフォルダ作成
(5) /sample/app/Services/ImplフォルダにSampleServiceImpl.php作成

App\Services\Impl\SampleServiceImpl.php
<?php
namespace App\Services\Impl;

use App\Services\Interfaces\SampleService;

class SampleServiceImpl implements SampleService
{
    private $sample1;
    private $sample2;

    public function __construct($sample1 = 0, $sample2 = 0)
    {
        $this->setSample1($sample1);
        $this->setSample2($sample2);
    }

    public function getSample1()
    {
        $this->sample1++;
        return $this->sample1;
    }

    public function setSample1($sample1)
    {
        $this->sample1 = $sample1;
    }

    public function getSample2()
    {
        $this->sample2++;
        $this->sample2++;
        return $this->sample2;
    }

    public function setSample2($sample2)
    {
        $this->sample2 = $sample2;
    }

}

(6) /sample/tests/Servicesフォルダ作成
(7) /sample/tests/Services/Implフォルダ作成
(8) /sample/tests/Services/ImplフォルダにSampleServiceImpl.php作成

Tests\Services\Impl\SampleServiceImpl.php
<?php
namespace Tests\Services\Impl;

use App\Services\Interfaces\SampleService;

class SampleServiceImpl implements SampleService
{
    public function __construct($sample1 = 0, $sample2 = 0)
    {
    }

    public function getSample1()
    {
        return 1;
    }

    public function setSample1($sample1)
    {
    }

    public function getSample2()
    {
        return 2;
    }

    public function setSample2($sample2)
    {
    }

}

DI登録

(1) /sample/app/Providers/DiServiceProvider.php作成

DiServiceProvider.php
<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\Interfaces\SampleService;

class DiServiceProvider extends ServiceProvider{

    public function register()
    {
        $env = config('app.env');

        // サービスクラスの登録
        $prefix = 'App\\Services\\Impl\\';
        if ($env === 'test') {
            $prefix = 'Tests\\Services\\Impl\\';
        }

        app()->singleton(SampleService::class, $prefix . 'SampleServiceImpl');

    }

    public function boot()
    {
    }

}

config('app.env')はconfig/app.php内に定義された配列のenv要素を取得しています
config('app.env')の値によって登録されるクラスが変わるようにしています
app()->singletonで先ほど作成したサービスクラスをDI登録しています
singletonというのは、クラスのインスタンスが1つしか生成されないようにすることです
要は、あるクラスに対してnew演算子を使うのは1回だけ。new演算子を使って作成されたインスタンスはフレームワーク内の変数に格納され、2回目以降の呼び出し時はその変数からインスタンスを取り出し返すというものになります
ただし、動作確認しているLaravel7.1.1では使い方によってはシングルトンにならない場合があります。それは後述します

(2) /sample/config/app.php修正
上記で作成したサービスプロバイダをLaravelに登録します
/sample/config/app.php内に定義されているproviders配列にApp\Providers\DiServiceProvider::class,を追記する

app.php
‥‥
    'providers' => [
‥‥
        /*
         * Application Service Providers...
         */
        App\Providers\DiServiceProvider::class,
‥‥
    ],
‥‥

ここに記載されたサービスプロバイダ(Illuminate\Support\ServiceProviderをextendsしたクラス)はLaravelが自動的にロードしてくれます

Controllerにメソッド追加

DI登録したクラスを使ってみましょう
(1) /sample/app/Http/Controllers/SampleController.phpにuse文を追記
use App\Services\Interfaces\SampleService;

(2) /sample/app/Http/Controllers/SampleController.phpにdi1メソッド、di2メソッドを追記

SampleController.php
    public function di1()
    {
        $data = [];

        $sampleService = app()->makeWith(SampleService::class, ['sample1' => 1, 'sample2' => 2]);
        $data['sample1_1'] = $sampleService->getSample1();
        $data['sample1_2'] = $sampleService->getSample2();

        $sampleService = app()->makeWith(SampleService::class);
        $data['sample2_1'] = $sampleService->getSample1();
        $data['sample2_2'] = $sampleService->getSample2();

        $sampleService = app()->makeWith(SampleService::class, ['sample1' => 100, 'sample2' => 200]);
        $data['sample3_1'] = $sampleService->getSample1();
        $data['sample3_2'] = $sampleService->getSample2();

        return view('sample.di', $data);
    }

    public function di2(SampleService $sampleService)
    {
        $data = [];

        $sampleService->setSample1(1);
        $sampleService->setSample2(2);
        $data['sample1_1'] = $sampleService->getSample1();
        $data['sample1_2'] = $sampleService->getSample2();

        $sampleService = app()->makeWith(SampleService::class);
        $data['sample2_1'] = $sampleService->getSample1();
        $data['sample2_2'] = $sampleService->getSample2();

        $sampleService = app()->makeWith(SampleService::class, ['sample1' => 100, 'sample2' => 200]);
        $data['sample3_1'] = $sampleService->getSample1();
        $data['sample3_2'] = $sampleService->getSample2();


        return view('sample.di', $data);
    }

上記を見て分かる通り、makeWithを実行してDI登録したクラスを呼び出すこともできますし、
メソッドの引数を型宣言(タイプヒンティング)することでも呼び出すことができます
makeWithでは第二引数を使ってDI登録したクラスのコンストラクタに引数を渡すことができます
先ほどDI登録したサービスプロバイダ作成のところで述べましたが、
動作確認しているLaravel7.1.1では使い方によってはシングルトンにならない場合があります
それはmakeWithの第二引数に渡した値が、phpのempty関数に与えた結果falseの場合です
makeWithの第二引数に['sample1' => xxx, 'sample2' => xxx]のように配列を渡しているとシングルトンにならずに新しいインスタンスが作成され返されます。コンストラクタの引数が与えられているからですね

(3) /sample/routes/web.phpに下記を追記
Route::get('sample/di1', 'SampleController@di1');
Route::get('sample/di2', 'SampleController@di2');

viewの作成

(1) /sample/resources/views/sample/di.blade.phpファイル作成

di.blade.php
<html>
    <head>
        <title>sample</title>
    </head>
    <body>
        <div>{{$sample1_1}}</div>
        <div>{{$sample1_2}}</div>
        <div>{{$sample2_1}}</div>
        <div>{{$sample2_2}}</div>
        <div>{{$sample3_1}}</div>
        <div>{{$sample3_2}}</div>
    </body>
</html>

動作確認

http://localhost/laravelSample/sample/di1

実行結果

2
4
1
2
101
202

Controllerにメソッド追加したところで説明したようにmakeWithに第二引数を与えているとシングルトンにならないことがわかります

http://localhost/laravelSample/sample/di2

実行結果

2
4
3
6
101
202

Controllerにメソッド追加したところで説明したようにmakeWithに第二引数を与えないとシングルトンになっていることがわかります

/sample/.envのAPP_ENV=localをAPP_ENV=testに変えて実行してみましょう

http://localhost/laravelSample/sample/di1

実行結果

1
2
1
2
1
2

http://localhost/laravelSample/sample/di2

実行結果

1
2
1
2
1
2

DI登録されたクラスが変わったことがわかります

/sample/.envのAPP_ENVをlocalに戻しておきましょう

toontoon
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away