LaravelでService層とRepository層を取り入れる

LaravelでService層とRepository層を取り入れる

こんにちは、原田です。

最近LaravelプロジェクトでServiceクラスとRepositoryクラスを取り入れた実装を行ったので、そのやり方を紹介したいと思います。

また、単体テスト時にクラスを差し替えられるように、それぞれDIの仕組みを使って実装しています。

ディレクトリ構造

完成形のディレクトリ構造はこのようになります。

app
├ Console
├ Exceptions
├ Http
│ ├ Controllers
│ │ └ SampleController.php
│ ├ Middleware	
│ └ Requests
├ Providers
├ Repositories
│ ├ SampleRepository.php
│ └ SampleRepositoryInterface.php
└ Services
  ├ SampleService.php
  └ SampleServiceInterface.php

ServiceとRepositoryを新規作成し、コントローラーを修正します。

Serviceクラス作成

LaravelにはServiceディレクトリが存在しないので、app配下に作成しましょう。

作成できたらそこにインターフェースと実装クラスを作成します。

app/Services/SampleServiceInterface

<?php

namespace App\Services;

interface SampleServiceInterface
{
    public function getSample($id);
}

app/Services/SampleService

<?php

namespace App\Services;

class SampleService implements SampleServiceInterface
{
    public function getSample($id);
    {
        // ここに処理を書く
    }
}

Repositoryクラス作成

Serviceと同様、app配下にRepositoriesディレクトリを作成し、インターフェースと実装クラスを作成します。

app/Repositories/SampleRepositoryInterface

<?php

namespace App\Repositories;

interface SampleRepositoryInterface
{
    public function getSampleById($id);
}

app/Repositories/SampleRepository

<?php

namespace App\Repositories;

use App\Models\Sample;

class SampleRepository implements SampleRepositoryInterface
{
    public function getSampleById($id)
    {
        return Sample::find($id);
    }
}

AppServiceProviderに登録

サービスプロバイダに設定します。
今回はAppServiceProviderに追記する形にしていますが、別でServiceProviderのファイルを新規作成した方が、クラスが増えた時に分かりやすいかと思います。

環境によって実装クラスを分けたい場合は、ここで環境変数を参照してインターフェースにbindするクラスを分けるようにしましょう。

    public function register()
    {
        $this->app->bind(
            \App\Repositories\SampleRepositoryInterface::class,
            \App\Repositories\SampleRepository::class,
        );

        $this->app->bind(
            \App\Services\SampleServiceInterface::class,
            function ($app) {
                return new \App\Services\SampleService(
                    $app->make(\App\Repositories\SampleRepositoryInterface::class)
                );
            },
        );
    }

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

最後にコンストラクタで依存対象のクラスを渡すよう処理を記述します。

ServiceクラスにはRepositoryのインターフェースを渡しましょう。
そしてgetSample()でrepositoryのgetSampleById()を呼ぶように処理を追加します。

app/Services/SampleService

class SampleService implements SampleServiceInterface
{
    private $sample_repository;

    public function __construct(
        SampleRepositoryInterface $sample_repository
    ) {
        $this->sample_repository = $sample_repository;
    }

    public function getSample($id);
    {
        return $this->sample_repository->getSampleById($id);
    }
}

同様にコントローラーにServiceのインターフェースを渡すよう追加します。

app/Http/Controllers/SampleController

<?php

namespace App\Http\Controllers;

use App\Services\SampleServiceInterface;

class SampleController extends Controller
{
    private SampleServiceInterface $sample_service;

    public function __construct(SampleServiceInterface $sample_service)
    {
        $this->sample_service = $sample_service;
    }

    public function get($id)
    {
        $sample = $this->sample_service->getSample($id);

        return view('sample.service', $sample);
    }
}

おわりに

これでServiceにビジネスロジックを、Repositoryにデータアクセス処理を分けることができるようになりました!

Laravelのオープンソースのプロジェクトを探しても中々しっくりくるものが無く。。
試行錯誤した結果この形にたどり着きました。笑

モックに差し替えて単体テストを行う方法は、またの機会に書きたいと思います。

エンジョイワークスでは一緒に働くエンジニアを募集しております!
採用情報はこちらWantedlyも覗いてみてください!

一覧へ戻る

最新記事