Blogブログ

laravel

2022.03.26

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;
    }
}

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

余談ですが、
フルーツわらびもちのお店を北海道でやります。
砂糖をはちみつにしたり、黒糖にしたり、甜菜糖にしたり、
糖分のg数とわらび粉のg数、水の量を変えたりして、
全パターンを分岐させてベストな味を追求しています。
ここからどう変わっていくのかの記録も兼ねて初期の画像を添付しておきます。