tanikoのぶろぐ

Laravelが 201 Createdを返すってさ

Laravel 5.6での話です. 5.6から, 新たにモデルを作成した際に200 OKではなく, 201 Createdを返すようになったってだけの話です. (今更?)

どういうことか

LaravelをAPIとして利用していた時に, Eloquentモデルを返した際に, 200だったのが, 201に変更になりました.

// app/Http/Controllers/PostController.php
<?php

namespace App\Http\Controllers;

use App\Post;
use App\Http\Requests\Post\CreateRequest;

class PostController extends Controller
{
    public function create(CreateRequest $request)
    {
        return $request
            ->user()
            ->posts()
            ->save(Post::make($request->only(['text'])));
    }
}

こんな感じのコードがあった場合, 5.5までだと200 OKで新規に作成されたPostのデータがレスポンスとして, 返ってきていました. しかし, 5.6からは201 CreatedとPostのデータが返ってくるようになりました.
特に副作用がなさそうですが, テストやライブラリで200かを使っていれば思わぬ動作をしてしまいます. 皆さんは, axiosとかでasync/awaitとかthen/catchとか使っているので大丈夫ですよね. 私はPHPUnitでassertStatus(200)とかしていたのでテストが転けた.

地味な変更点ではあるかと思いますが, せっかく201 Createdという, ちゃんと意味があるものがあるので, 嬉しいですね.

ちなみに

この話自体はLaravel 5.5の時に出ていて, PRも出されていましたが, バグフィックスとかではなく, 破壊的変更であったため, 見送られ, 5.6でPRがマージされました. 仕組み自体はRouterでレスポンス(コントローラでreturnしたもの)がIlluminate\Database\Eloquent\Modelで,wasRecentlyCreatedtrueだったら201 Createdで返すって感じです. 詳しくはコードを.

Laravelのクエリビルダでifとかを使う

はじめに

Laravelで特定のクエリパラメータを使ってクエリを組み立てたいときってありませんか? 例えば, 検索なので, since_idがあれば, セットされている値以降で絞り込んで検索とかです.

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Asset;
use App\Http\Requests\Asset\SearchRequest;

class AssetController extends Controller
{
    public function search(SearchRequest $request)
    {
        $query = Asset::query();
        if ($request->has('since_id')) {
            $query = $query->where('id', '>=', $request->input('since_id'));
        }
        if ($request->has('until_id')) {
            $query = $query->where('id', '<=', $request->input('until_id'));
        }
        return $query->get();
    }
}

こんな感じで書けますよね.
ただ, これだと何個もif文を書いていくことになります. メソッドチェーンでやってみたくなったので, やってみましょう.

やり方

マクロの登録

まず, クエリビルダでifwhereIfを使えるように登録します. app/Providers/AppServiceProvider.phpに以下のコードを加えます.

<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Schema;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Builder::macro('if', function (bool $condition, callable $func) {
            return $condition ? $func($this) : $this;
        });
        Builder::macro('whereIf', function (bool $condition, $column, $operator = null, $value = null, $boolean = 'and') {
            return $condition ? $this->where($column, $operator, $value, $boolean) : $this;
        });
    }
}

まず1つ目は, コールバック関数を与える方法です. これは, 複雑なクエリを付け加えたい時に使用します. 単純なwhere句を加えたいだけなら, この方法はおすすめしません.
2つ目はwhere句のみを付属させるものです. where()とほぼ同じ使い方なので, こちらがおすすめです. where()との違いは, 第1引数がtrueならばwhereが加えられるという点です.

使い方

ユーザの投稿一覧の取得のサンプルコードです.

<?php
// 1つ目 if
$posts = $user->posts()
    ->if($request->has('since_id'), function ($query) use ($request) {
        return $query->where('id', '>=', $request->input('since_id'));
    })->get();

// 2つ目 whereIf
$posts = $user->posts()
    ->whereIf($request->has('since_id'), 'id', '>=', $request->input('since_id'));

こんな感じでメソッドチェーンをつなげていくことができます.
どちらの方が見やすいのか. ブロックで分けるべきか, 1行でまとめるべきか. あと, 型宣言や関数呼び出しの速度も.

リンク

Laravel API - https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Builder.html#method_where