カテゴリー: BackEnd

LaravelのDI

はじめに

今回の記事では、PHPのフレームワークであるLaravelのDIに関する機能と実装方法を紹介します。Laravelの基礎知識に関してはこちらをご覧ください。

DIに関する機能

DIとは

大雑把に説明すると、DI(Dependency Injection)とはあるコンポーネントで利用する別のコンポーネントを外部から注入できるようにするデザインパターンです。コンポーネント間を疎結合にするために、コンポーネント間の関係には具体的なコンポーネントではなくインタフェースを用います。DIに関してはこちらが参考になります。

サービスコンテナ

サービスコンテナはLaravelアプリケーションにおいてインスタンスの管理を行う機能です。サービスコンテナの役割は大きく分けて二つあります。一つはインスタンスの生成方法の管理、もう一つは生成したインスタンスの管理です。

サービスプロバイダ

サービスプロバイダはインスタンスの生成方法をサービスコンテナに登録する役割を担います。

サービスコンテナ

ここでは、サービスコンテナにインスタンスの生成方法を登録する方法(バインド)とサービスコンテナを利用したインスタンスの生成方法(解決)について説明します。

バインド

バインドには下記の四つの方法が用意されています。

  • bindメソッド
  • bindIfメソッド
  • singletonメソッド
  • instanceメソッド

bind

まず始めに一般的なbindメソッドの使用方法を説明します。下記のサンプルコードの通り、 app()->bind() メソッドの第一引数に文字列(登録名)、第二引数にクロージャ(生成方法)を指定します。

<?

class Message
{
    private $name;
    
    public function __construct($name='Taro')
    {
        $this->name = $name;
    }
    
    public function sayHello()
    {
        return 'Hello '.$this->name.'!';
    }
}

// 引数なしの場合
app()->bind(Message::class, function () {
    return new Message();
});

// 引数ありの場合
app()->bind(Message::class, function ($app, array $parameters) {
    return new Message($parameters[0]);
});

bindIf

bindIfメソッドは引数に指定した文字列に対応したバインドが存在しない場合にバインドを行います。

<?

class Message
{
    private $name;

    public function __construct($name='Taro')
    {
        $this->name = $name;
    }
    
    public function sayHello()
    {
        return 'Hello '.$this->name.'!';
    }
}

// Hanakoで登録
app()->bindIf(Message::class, function () {
    return new Message('Hanako');
});

// Taroで登録
app()->bindIf(Message::class, function () {
    return new Message('Taro');
});

$message = app()->make(Message::class)->sayHello();
// $message = 'Hello Hanako!'

singletonメソッド

singletonメソッドでバインドした場合、生成したインスタンスはサービスコンテナにキャッシュされます。

<?

class Message
{
    private $name;

    public function __construct($name='Taro')
    {
        $this->name = $name;
    }
    
    public function sayHello()
    {
        return 'Hello '.$this->name.'!';
    }
}

app()->singleton(Message::class, function ($app, array $parameters) {
    return new Message($parameters[0]);
});

$message1 = app()->make(Message::class, ['Hanako'])->sayHello();
$message2 = app()->make(Message::class, ['Taro'])->sayHello();
// $message1 = 'Hello Hanako!'
// $message2 = 'Hello Hanako!'

instanceメソッド

instanceメソッドは生成したインスタンスをサービスコンテナにバインドします。

<?

class Message
{
    private $name;

    public function __construct($name='Taro')
    {
        $this->name = $name;
    }
    
    public function sayHello()
    {
        return 'Hello '.$this->name.'!';
    }
}

$message = new Message();
app()->instance(Message::class, $message);

$sayHello = app()->make(Message::class)->sayHello();
// $sayHello = 'Hello Hanako!'

解決

サービスコンテナを介したインスタンスの生成には app()->make() メソッドか app() ヘルパ関数で行います。

<?

class Message
{
    private $name;
    
    public function __construct($name='Taro')
    {
        $this->name = $name;
    }
    
    public function sayHello()
    {
        return 'Hello '.$this->name.'!';
    }
}

app()->bind(Message::class, function () {
    return new Message();
});

$message1 = app()->make('Message::class')->sayHello();
$message2 = app('Message::class')->sayHello();
// $message1 = 'Hello Taro!'
// $message2 = 'Hello Taro!'

サービスプロバイダ

サービスコンテナへのインスタンス生成方法のバインドは AppServiceProvider クラスや独自のサービスプロバイダクラスに定義します。独自のサービスプロバイダクラスを作成した場合は app.php に定義を追加します。

'providers' => [
    // 省略
    App\Providers\MyServiceProvider::class, // 追加
],

サービスプロバイダクラスにはregisterメソッドとbootメソッドがあります。バインド処理は基本的にregisterメソッドに記述します。ただし、インスタンス生成時に他のクラスを利用する必要がある場合はbootメソッドに記述します。

DIを利用する

ここではLaravelでのDIの利用方法を紹介します。まず、下記の用にインタフェースとクラスを定義し、バインドします。

<?

interface NoticeInterface
{
    public function sayHello(string $name);
}

class Notice implements NoticeInterface
{
    public function sayHello(string $name)
    {
        return 'Hello '.$name.'!'
    }
}

// インタフェースのバインド
app()->bind(NoticeInterface::class, Notice::class);

コンストラクタインジェクション

上記のインタフェースをコンスタラクタインジェクションで利用する場合は下記のようになります。

<?php

class Message
{
    private $notice;
    private $name;

    public function __construct(NoticeInterface $notice, $name='Taro')
    {
        $this->notice = $notice;
        $this->name = $name;
    }
    
    public function sayHello()
    {
        return $this->notice()->sayHello($this->name);
    }
}

app()->bind(Message::class, function () {
    return new Message();
});

$sayHello = app(Message::class)->sayHello();
// $sayHello = 'Hello Taro!'

メソッドインジェクション

メソッドインジェクションで利用する場合は下記のようになります。

<?php

class Message
{
    private $notice;
    private $name;

    public function __construct($name='Taro')
    {
        $this->notice = $notice;
        $this->name = $name;
    }
    
    public function sayHello(NoticeInterface $notice)
    {
        return $this->notice()->sayHello($this->name);
    }
}

app()->bind(Message::class, function () {
    return new Message();
});

$message = app(Message::class);
$sayHello = app()->call([$message, 'sayHello']);
// $sayHello = 'Hello Taro!'

さいごに

LaravelでのDIの利用にあたり重要な機能であるサービスコンテナとサービスプロバイダについて説明しました。

おすすめ書籍

    

Hiroki Ono

シェア
執筆者:
Hiroki Ono
タグ: phplaravel

最近の投稿

Goの抽象構文木でコードを解析する

はじめに Goでアプリケーショ…

5日 前

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

1か月 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前