1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel サービスコンテナ(インターフェイスと実装の結合)方法

Posted at

この投稿は、Laravel公式のサービスコンテナに関する内容です
https://readouble.com/laravel/8.x/ja/container.html

image.png

事の発端はService層やRepository層を設けて開発する場合、対象クラスのDI定義を都度定義するのは、設定漏らしやすいので『絶対にやりたくない!』です。

Providers/AppServiceProvider.php
 
  public function register()
  {
     $this->app->bind(Interface001::class, Imple001::class);
     $this->app->bind(Interface002::class, Imple002::class);
     $this->app->bind(Interface003::class, Imple003::class);
     $this->app->bind(Interface004::class, Imple004::class);
     ・・・
     気が狂いそう
  }

どうにかしてインターフェイスと実装クラスの紐づけを自動化できないか考えて、とりあえず解決に至ったので残しておこう。

今回の解決は特定のディレクトリ構成に基づいてサービスコンテナに登録仕組みとしています。

ディレクトリ構成

サービスコンテナに登録するインターフェイス、クラスの作成ルール

  • ディレクトリ直下にインターフェイスを定義
    必ずIから始める。(IXxxx.php)
  • Implディレクトリに実装クラスを定義
    必ずインターフェイスの先頭1文字を除外したクラス名にする。(Xxxx.php)

という構成にします。

ディレクトリ例 (Services, Repositories下が管理対象)

app
 Services
  IXxxService.php
  Impl
   XxxService.php
 Repositories
  IXxxRepository.php
  Impl
   XxxRepository.php

AppServiceProvider.php の実装

<?php

namespace App\Providers;

use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;

use Illuminate\Support\Facades\File;
use Symfony\Component\Finder\Finder;

/**
 * Class AppServiceProvider
 */
class AppServiceProvider extends ServiceProvider
{
    /**
     * {@inheritdoc}
     */
    public function register()
    {
        $dirs = [
            'Services',
            'Repositories'
        ];
        foreach ($dirs as $dir) {
            $this->bind($dir);
        }
    }
    /**
     * bindを行う
     *
     * @param string $dir 対象のディレクトリ
     * @return void
     */
    private function bind(string $dir)
    {
        $finder = new Finder();
        $iterator = $finder
            ->in(app_path().'/'.$dir)
            ->depth('< 2') // Implディレクトリが含まれないように
            ->name('I*.php')
            ->files();
        foreach ($iterator as $fileinfo) {
            $filepath = $fileinfo->getPathname();
            list($interface, $impl) = $this->getBinds($filepath);
            if (is_null($impl)) {
                // インターフェイスのみ定義している場合、bindしない
                continue;
            }

            $this->app->bind($interface, $impl);
        }
    }
    /**
     * バインドするインターフェイスと実装クラスのクラスパスを取得する
     *
     * @param string $filepath
     * @return array interface, impl
     */
    private function getBinds(string $filepath): array
    {
        $fileInfo = pathinfo($filepath);

        $interfaceDir      = $fileInfo['dirname'];
        $interfaceFileName = $fileInfo['filename'];
        $interfacePath     = "{$interfaceDir}/{$interfaceFileName}";
        $interfaceClass    = Str::replace(app_path(), 'App', $interfacePath);
        $interfaceClass    = Str::replace('/', '\\', $interfaceClass);

        $implDir      = "{$interfaceDir}/Impl";
        $implFileName = substr($interfaceFileName, 1);
        $implFilePath = "{$implDir}/{$implFileName}";
        $implFIlePathWithExtention = "{$implFilePath}.{$fileInfo['extension']}";
        $implClass    = Str::replace(app_path(), 'App', $implFilePath);
        $implClass    = Str::replace('/', '\\', $implClass);

        $implClass = File::exists($implFIlePathWithExtention) ? $implClass : null;

        return [$interfaceClass, $implClass];
    }
    ・・・
}

動作確認

tinkerでコンテナから取得できるか確認。OK!

php artisan tinker
>>> app()->make(App\Services\Auth\ILoginService::class);
=> App\Services\Auth\Impl\LoginService {#4733}

Controllerクラスで使用する場合

use App\Services\Auth\ILoginService;

class XxxController extends Controller {
  public function __constructor(ILoginService $loginService)
  {
    $this->loginService = $loginService;
  }
  public function login(Request $request)
  {
    // こんな感じで使える。
    $this->loginService->login($request->all());
  }
}

問題が出るまでこれでやってみる。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?