【Laravel】#03 RESTful API の実装(CRUD)

前回はLaravelで開発環境の構築(文字コードやロケール/DB周り)を実施しました。
今回は基本となる、前回投入したデータを登録・更新・削除・参照するAPIを作成していきます!
作成する機能は以下の通りです。

  1. つぶやきの一覧が取得できる
  2. つぶやきを投稿できる
  3. つぶやきを編集できる
  4. つぶやきを削除できる

具体的には、リクエストを受けた後に処理するためのコントローラを作成し、それぞれのリクエストメソッドに応じて上記処理を実行します。リクエストの振り分けは、Laravelのルーティング機能を使用します。

初期データの投入は前回の記事を参照してください。

目次

つぶやき一覧取得APIを作成

Controllerの雛形作成

リクエストを処理するためにはまず、コントローラを作成します。
Laravelがリクエストを受け付けた後にルータがコントローラに処理を振り分けます。
make:controller Artisanコマンドを使用することで、ResourceControllerの雛形を作成できます。

sail artisan make:controller TweetController --resource

問題なく作成されればINFO Controller created successfully.が表示されます。
コントローラはapp/Http/Controllers配下に作成されます。
TweetControllerTweet/IndexControllerとすることもできるので、作成しようとしているアプリの規模に応じていい感じにディレクトリ構成を考えるといいでしょう。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TweetController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

indexメソッドを作成

続いて、つぶやきの一覧を取得するエンドポイントを作成するために、コントローラ内にIndexメソッドを追加します。
内容としては、先程作成したテーブルのレコードを取得してレスポンスするというシンプルなものです。

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;  // 追加

class TweetController extends Controller
{
    /**
     * @return Collection
     */
    public function index()
    {
        return DB::table('tweets')
            ->selectRaw('id')
            ->selectRaw('title')
            ->selectRaw('content')
            ->selectRaw('user_id')
            ->selectRaw('created_at')
            ->selectRaw('updated_at')
            ->get();
    }
}

これだけだと、どのURLを叩いたらこのコントローラが実行されるかわからないので、ルーティングを設定します。
routes/api.phpに下記を記載します。

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::apiResource('tweets', 'App\\Http\\Controllers\\TweetController');

Laravelでは、Route::apiResourceを使用することで、RESTに対応したルーティングを自動で設定してくれます。

HTTPメソッドURIアクション役割
GET/コントローラ名index一覧表示
POST/コントローラ名store新規作成
GET/コントローラ名/{id}show読み込み
PUT/PATCH/コントローラ名/{id}edit更新
DELETE/コントローラ名/{id}destroy削除

実際に作成したAPIの動作を確認するため、APIを叩いてみます。
ブラウザにhttp://localhost/api/tweetsを入力し、json形式でレスポンスが返ってきていることを確認します。
人によってDBのデータが違うため、返却内容は異なりますが、以下のような文字がブラウザに表示されているはずです。

整形すると以下の通りで、DBに挿入されたデータと同じであることがわかります。

[
    {
        "id": 1,
        "title": "の不思議ふしぎそう。",
        "content": "祖せんでした。誰だれとも鷺さぎの方へ急いそらを見ました。けれどもすきとおもしながれてあのはいいました。「いると空中につけた、このごろぼろぼくもわかに流ながめて向むこうもろこしまいました。青年は一生け。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 2,
        "title": "さめたので、百二十。",
        "content": "売しょうめん、ぼくは学校で見たままでできてるんだ。こんなは、中に高い三角標さんまりました。ジョバンニは叫さけびましたがねを頭につい硝子ガラスよりはりんごくよねえ、氷山ひょうしろそうに、赤い帽子ぼうし。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 3,
        "title": "いつかれがついてそ。",
        "content": "向むこうのほんとう」カムパネルラはにわかにまるでパイをたべるに要いるか、ジョバンニは、この人たちは、青宝玉サファイアと黄と青じろくぼんや貝殻から」鳥捕とりは虫よ。それはなれそうでとまわって、わから霧。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 4,
        "title": "ゅらしいと思ってい。",
        "content": "りが非常ひじょうもろこしかしげました。ああ、なに問というよ。行こうのお宮みやで二尺も孔あなを一人ひとにほうせんでも着つくこんごをもって出て来るらしいの」ジョバンニもカムパネルラもぼんやり白くけむりは。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 5,
        "title": "い巾きれぎれの考え。",
        "content": "もなく、その羽根はね、お父さんその右手の崖がけの上に小さな嘆息たんだ人でした。もとの間原稿げんのさい」「お父さんに丘おかしやだ。そしても、ジョバンニはわけでしょうてに赤旗あかりゅうやのもやって行きま。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 6,
        "title": "えるきれぎれる北の。",
        "content": "陽たい箱はこんどんの石を腕うでのぞいて行ってやっぱな人たちのたってけむっていま新しい波なみを、あすこかで見た。ジョバンニは言いいねえ」カムパネルラが、くるくなってその神かみさまの平たいていて行って行。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 7,
        "title": "札口から、みんなに。",
        "content": "「蠍さそりの女の子がいたのあかいにある大きくしゃの窓まどから私の義務ぎむだとは、すっとさっき聞こえジョバンニはだん数を増ました。カムパネルラが、どこかぐらいどがありがまるでこんどは自分のお菓子かしき。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 8,
        "title": "ってね」ジョバンニ。",
        "content": "「そうな気もちが軽かるくなり、いっているんじを示しめし、第一だいぶんでいる、その天の川の水が深いほかの花のにおいのです。私は大学士だいろがりたいてきます。そのうぎょうどさそりのように見えるとこへ顔を。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 9,
        "title": "イアは向むこうの方。",
        "content": "かんを二人ふたりした。「もう腸はらをまっていたのですよ。今日は角砂糖かくひょうばいものを見みませんな悪わるい実験じっと行く。どうもなかった地図を指さしました。ジョバンニの見ず知らずには空じゅくしくな。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    },
    {
        "id": 10,
        "title": "って、さっきりんご。",
        "content": "こ、つやつや楢ならんと光っているのです。くじょジョバンニには、そらのようにそこらえているかぐあいさつしました。そのときなどは向むこうかと訊きこんだからぼくたちのからもなんから、ほんとうの柵さく遠いと。",
        "user_id": "1",
        "created_at": "2022-09-28 15:44:24",
        "updated_at": "2022-09-28 15:44:24"
    }
]

登録APIを作成

Requestクラスを作成

同じように他機能も作成していきます。
その際、リクエスト内容を格納するクラスが必要になるため、リクエストクラスを作成します。
リクエストクラスもコントローラと同様、Artisanコマンドから作成できます。

sail artisan make:request TweetRequest

成功すればapp/Requests/TweetRequest.phpが作成されています。
雛形からauthorize関数のreturnをtrueに変更します。
こちはbool型で認証の可否を判断するための関数です。falseを返した場合認証に問題があるとして、403が返却されますので、trueに変更します。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class TweetRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;  // ココを変更する
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

Modelクラスを作成

リクエストクラスを作成したらモデルクラスを作成します。
リクエスト内容に応じてDB登録する際に利用します。
app/Models/Tweet.phpに作成し以下を記載します。$filllabelにDBカラム、$castsに型を記載します。記載しないとVARCHAR扱いになります。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;

class Tweet extends BaseModel
{
    use HasFactory;
    use SerializeDate;

    protected $fillable = [
        'title', 'content', 'user_id', 'created_at', 'updated_at'
    ];

    protected $casts = [
        'created_at' => 'datetime:Y-m-d H:i:s',
        'updated_at' => 'datetime:Y-m-d H:i:s'
    ];
}

これだけだとcreated_atやupdated_atが更新されないのでBaseModelクラスを作成します。
Tweetモデルクラスを継承したBaseModelクラスを作成し、createメソッドとinsertメソッドをオーバライドします。
その後、Tweetモデルクラスの親クラスをBaseModelクラスに変更します。

<?php

namespace App\Models;

use DateTime;
use Illuminate\Database\Eloquent\Model;

abstract class BaseModel extends Model
{
    /**
     * @param $attributes
     * @return mixed
     */
    public static function create($attributes)
    {
        $now = new DateTime('now');
        $formedNow = $now->format('Y-m-d H:i:s');
        $attributes['created_at'] = $formedNow;
        $attributes['updated_at'] = $formedNow;
        return (new static)->forwardCallTo((new static)->newQuery(), 'create', [$attributes]);
    }
}

もう一つポイントがあります。
この後、登録処理をするのですが、登録処理後、created_atカラムを参照すると、タイムゾーンが9時間ずれた状態で取得されます。色々調べたのですが、どうやらLaravel8以降Date Serializationというものの仕様が変わったらしく、オーバライドして型を指定する必要があります。
app/Models配下に以下トレイトを作成し、Tweetモデルクラスではuseするようにした後、$catsで型を指定すれば、OKです。(そこそこハマりました。。)

<?php

namespace App\Models;

use DateTimeInterface;

trait SerializeDate
{
    /**
     * Prepare a date for array / JSON serialization.
     *
     * @param DateTimeInterface $date
     * @return string
     */
    protected function serializeDate(DateTimeInterface $date)
    {
        return $date->format('Y-m-d H:i:s');
    }
}

storeメソッドを作成

次にControllerを修正していきます。
登録APIを作成するため、storeメソッドを以下のように変更します。
$request->all()を使用して、受信リクエストのすべてのデータをarrayとして取得できます。
そのデータをTweetモデルのcreateメソッドにわたすことでInsert文が発行され、登録処理が行われます。
その後は登録処理に成功している場合は201、失敗した場合は500応答するようにreturn処理を記述します。

    /**
     * @param TweetRequest $request
     * @return JsonResponse
     * @throws Exception
     */
    public function store(TweetRequest $request)
    {
        $tweet = Tweet::create($request->all());
        return $tweet
            ? response()->json($tweet, 201)
            : response()->json([], 500);
    }

Postmanをインストールし、実際にAPIを叩いてみます。
Postmanのインストール方法はこちら。

Postmanで下記リクエストを送り、HTTPステータス201が返却されることを確認します。

{
	"title": "テストつぶやき",
	"content": "テストテストテスト",
	"user_id": "1"
}

更新・削除APIを作成

登録APIと同じ要領で更新・削除APIを作成していきます。

updateメソッドを作成

titleとcontentが設定された場合、値を更新する。何も設定されなければ、更新しない。

    /**
     * @param TweetRequest $request
     * @param Tweet $tweet
     * @return JsonResponse
     */
    public function update(TweetRequest $request, Tweet $tweet)
    {
        $tweet->title = $request->title ?? $tweet->title;
        $tweet->content = $request->content ?? $tweet->content;

        return $tweet->update()
            ? response()->json($tweet)
            : response()->json([], 500);
    }

Destroyメソッドを作成

URLパラメータに設定されたリソースを削除します。
実行後、DBを確認すれば対象のリソースがDELETEされていることがわかります。

    /**
     * @param Tweet $tweet
     * @return JsonResponse
     */
    public function destroy(Tweet $tweet)
    {
        return $tweet->delete()
            ? response()->json($tweet)
            : response()->json([], 500);
    }
よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

目次