Blogブログ

laravel

2023.07.11

Laravelのapiの作り方とserviceとrepositoryの使い方とか5分で学べる。

これを知りたいという人多いですよね。

これまでのブログで
laravelの簡単な扱い方として、

routing
assetのcssやjsの読み込み
bladeを使ったlayoutやその継承
seed
migration
1:1 1:n n:nの呼び出し
helperの使い方
画像のアップロード
なんかはできるようになってますよね。

ではそれをapiにする方法をやっています。

重要なのはroutingとresponse()->json([])のみ。

まず、routeフォルダに行ってみてください。
api.phpがありますよね。
そこに普通にapiのroutingを作ります。

ためしに、
apiフォルダの下にadminフォルダを作りそこにいろいろapiを作る場合を想定して、resouceでroutingを書いてみます。
accountsテーブルから値を取得する系のapiを想定してみました。

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;


Route::group(['prefix' => 'admin', 'namespace' => 'Api\Admin', 'as' => 'api.admin.'], function($router) {
    $router->resource('accounts', 'AccountsController');

});

では、controllerで取得系のapi作ってみましょう。

/app/Http/Controllers/Api/Admin
useは省略しますが、
アクションはこんな感じ

class AccountsController extends AdminBaseController
{
    protected $viewPathPrefix = 'admin.accounts';

    public function index()
    {
        $list = Admin::all();
▸-return response()->json(['admin'=>$list]);
    }
}

response()->json();に配列で渡せばいいということがわかりますね。

これで、あとは127.0.0.1/api/admin/accounts/とかでアクセスすれば取得できます。

apiはこれでコツを掴めたのではないかと思います。

次、laravelのseviceについて

serviceを使わない人はlaravel開発を本当にやったことはない人だと思います。
コードをきれいにするためにもserviceの利用は必須と考えてください。

昔から、
DALやBLLを分けて開発をするというデザインパターンはありましたよね。
service / logic
dal / bll
なんかを知ってる人はそれと同じで、serviceとはbllに当たります。

コントローラーになんでもかんでも書くのは保守性を考えると良くないです。
オフショアなんかをやっている人は、
海外勢によくこの辺りを教えてあげてください。

では、serviceを作っていきます。

これまで、controllerの中にmodelのAdmin::All()とかを書いてきましたが、基本的に本当の開発ではcontrollerにこのようなモデルの呼び出しは書きません。もう2度と書かないと思ってもらっても大丈夫です。controllerにmodelの記述を書いている開発者はlaravel開発の発展のために明日からやめましょう。

まず/App/Services/Adminのフォルダをつくり、
そこにAccountService.phpを作り、

<?php
namespace App\Service\Admin;

use App\Models\Admin;

class AccountsService
{
    public function getAdmins()
    {
        $accounts = Admin::all();
        return $accounts;
    }
}

これで、serviceは終わりです。
model部分を切り出しただけだというのがわかります。
(modelに本来serviceに書く記述があったとしても、最終的にmodel自体は1:1やn:nなどを定義する記述がくるのみとなり、serviceに記述を移していきます。)

このサービスをいつcontrollerの中でインスタンス化するか

ということろで使い方が変わってきますが、
コントローラーのコンストラクタでインスタンス化することが多いので、
それをやってみます。

<?php
namespace App\Http\Controllers\Admin;

use App\Http\Request\Admin\Account\CreateEditRequest;
use App\Models\Admin;
use Illuminate\Http\Request;
use App\Service\Admin\AccountService;


class AccountsController extends AdminBaseController
{
    protected $viewPathPrefix = 'admin.accounts';
    private $accountService;

    public function __construct(accountService $accountService){
        $this->accountService = $accountService;
    } 
    public function index()
    {
        $list = $this->accountService->getAccounts();
        return view('admin.accounts.index', compact('list'));
    }
}

比較的大きなプロジェクトになってくると、
一つだけコンストラクタでインジェクションするなんてことはないので、

    public function __construct(
        accountService $accountService,
        sampleService $sampleService
        ){
        $this->accountService = $accountService;
    } 

のように、複数のサービスをインジェクションしていくことになります。
それってけっこう読みにくくなったりしますよね。
そこで..

service providerの存在を知っておくと便利です。

毎回毎回 useしてコンストラクタに書いてそれをアクションでインジェクションして呼び出すこともちょっと大変です。

laravelにはApp配下にProviderというフォルダがあり、
そこにあらかじめインスタンス化するserviceを登録しておくことで、
たくさんのインジェクションをする必要がなくなります。

/app/Providers/AppServiceProvider.php

を開いてみてください。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('adminAccountService', \App\Services\Admin\AccountService::class);
        // コントローラーから app()->make('adminAccountService');
        // で呼び出せるようになる。
    }

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

registerのところにこのようにして登録します。
あえてadminAccountServiceという名前でバインド(ひもづけ)を行っています。
このようにしてサービスプロバイダーにバインドしておくと、
なんとコントローラーやモデル等全ての場所でインジェクションしなくても使えるようになります。かなり便利ですね。
なので、先ほどのコントローラーもこのように変わります。

<?php
namespace App\Http\Controllers\Admin;

use App\Http\Request\Admin\Account\CreateEditRequest;
use App\Models\Admin;
use Illuminate\Http\Request;


class AccountsController extends AdminBaseController
{
    protected $viewPathPrefix = 'admin.accounts';

    public function index()
    {
        $accountService = app()->make('adminAccountService');
        $list = $accountService->getAccounts();
        return view('admin.accounts.index', compact('list'));
    }
}

かなりシンプルになるのがわかると思います。
このようにlaravelとは構造上重いフレームワークになりがちです(railsもか^^)。

次、repositoryの話をします。

repositoryとはgitの話ではないですよ。
laravelのリポジトリって何ですか?というと
laravelの開発においてドメインとデータマッピングレイヤー間の抽象化レイヤーとして定義できる場所のことです。」というエンジニアは解説になっていないので国に帰って欲しいですよね。

リポジトリとは、
なくても開発はできるのですが、
コードをより読みやすくするために
modelのデータにアクセスする部分だけをrepositoryというフォルダ配下に切り出して書くことにした
という感じです。
Serviceが複雑になってきたときに、
repositoryはmodelにアクセスするクエリを発行するだけの場所にして、
serviceでそのデータを整形してコントローラーで扱いやすくしてあげる
という感じです。
なので必然的に大きなプロジェクトのときに使う手法となります。

laravelのrepositoryの使い方

まず、app配下にRepositoriesとInterfacesいうフォルダを作ります。

先にInterfaceを作っていきます。

その中にAdminというフォルダをつくり、
そこにAccountRepositoryInterfase.phpというファイルを作りましょう。


<?php
namespace App\Interfaces\Admin;
interface AccountRepositoryInterface
{
/**
    public function getAllOrders();
    public function getOrderById($orderId);
    public function deleteOrder($orderId);
    public function createOrder(array $orderDetails);
    public function updateOrder($orderId, array $newDetails);
    public function getFulfilledOrders();
**/
    public function getAccounts();
} 

このように今後、repositoryで使う関数を先にinterfaceに書いておきます。
今回はgetAccountsしか使わないですが、例としていろいろ書いておきました。
interfaceで関数を登録しておく意味は、必ずしもmodel系ではない場合
例えばjsonとかのconfigを何処かに書いておいてそれを呼び出すようなものも
ここに書いておくことで、使う側のserviceのロジックを変えなくてもいいようにするためのものです。

あとはテストコードを書くときにmockのデータを用意してテストのためだけの関数を作っておき、そこをテスト側で使うためにも便利です。

では、repositoryに移動します。そこにadminというフォルダをつくり

repository/admin/のフォルダに移動します。

そこにAccountRepository.phpを作ります。

<?php

namespace App\Repositories\Admin;

use App\Interfaces\Admin\AccountRepositoryInterface;
use App\Models\Admin;

class AccountRepository implements AccountRepositoryInterface
{
    public function getAccounts()
    {
        return Admin::all();
    }
}    

interfaceはuseでパスを指定して、
必要なmodelをuseした上で、

interfaceのクラスはimplementsで継承します。

interfaceの中に、repogitoryの関数群を登録しておき、
あとでまとめてserviceで呼び出すというイメージです。

repositoryを書いたらserviceを分離します。

<?php
namespace App\Service\Admin;
use App\Interfaces\Admin\AccountInterface;
use App\Models\Admin;

class AccountsService
{
    private $accountRepository;
    public function __construct(
        AccountRepository $accountRepository
    ){
        $this->accountRepository = $accountRepository;
    }
    public function getAccounts()
    {
        $accounts = $this->accountRepository->getAccounts();
        return $accounts;
    }
}

これでリポジトリの使い方は分かったかと思います。

service repogitoryデザインパターンについて書かれている他のブログを紹介します。

このブログは最小単位で理解してもらえるように書いたので、
このブログを理解してから他のlaravel service repogitoryブログを見るとかなりわかりやすいかと思います。

例えば実際のエンジニアのみんなだとひよこさんのブログのようなメモから始まるのではないでしょうか。
そしてこのブログのようにservice-repogitory laravelのパターンを理解していくという感じかと思います。

このブログ以外でも例えばキータのこの記事「LaravelでService層、Repository層を取り入れる方法とその必要性を簡潔に語る」も結構わかりやすいです。
かなりシンプルに解説してくれています。

このブログではrepogitoryパターンの解説をしています。
あとはこのブログもrepogitoryパターンです。
文脈としてはservice repogitoryのパターンの前に、
repogitoryだけのデザインパターンから入る開発も多くあります。
こちらのブログはservice-repogitoryのひとつ前のrepogitoryのパターンとして理解するととても良いブログだとおもいます。
本ブログでserviceも含めたlaravelの開発を理解した後に見るとより理解が深まるのではないかと思います。

laravel service repogitoryの開発の話は以上です。

わかりましたでしょうか。
fat controllerを避けて快適なコードを書いて後に誰が見ても混乱することがないようにしたいですね。

ちなみに弊社はservice repogitoryパターンを無視すると一発で席が消えます。
では。

慣れてきたところで、
こちらのブログで実践的なlaravel10の環境構築とservice repositoryを使ったブログの構築を解説しています。