Illuminate\Http\JsonResponseインスタンスからjsonを取り出す

レスポンスをjsonで返すapiを別のアクションで呼んでその結果に対して配列処理をかけたい場合。

参考

laravel.com

そのapiが下記のようにレスポンスを返す場合はIlluminate\Http\JsonResponseインスタンスが結果として返されている。

return response()->json( $result_data );

これはjsonではないのでjson_decodeをかけるとnullが帰る(json_decodeはjsonとして正しい形式でないデータを引数に渡すとnullを返す)。Illuminate\Http\JsonResponseインスタンスからjson形式のデータのみ抽出したい場合はIlluminate\Http\JsonResponseが持つcontent()メソッドを利用する。

$result       = $this->getUsers( $request, $some_attr );
$result_json  = $result->content()
$result_array = json_decode( $result_json, true );

上記のようにすれば結果を配列として処理することができる。

syncで生成されるSQL

参考

多対多

概要

Eloquentのメソッドにsyncというのがある。

これはbelongsToManyの定義されている多対多のリレーション下で使うことができる。

これによって要素同士の関連を定義することができる。つまり自動で中間テーブルを作ることができる。

$team->users()->sync( $user_ids, false );

第一引数は配列で渡す。第二引数にfalseを渡さない場合は第一引数で渡した配列以外の中間テーブルのレコードが削除されてしまうので注意。

またこれによってinsertされるレコードにはcreate_atとupdated_atが付与されない。必要な場合はモデルのリレーションを定義しているメソッドの返り値にwithTimestamps()をつける。

    public function users()
    {
        return $this->belongsToMany(User::class)->withTimestamps();
    }

利点

単に指定された要素同士のリレーションのレコードを作るだけではなく、既に中間テーブルに同じ要素同士のレコードがあり、リレーションがある場合には新たなレコードの作成を中止してくれる。

クエリ

生成されるクエリを確認してみる

$user_ids = [868, 911];
DB::enableQueryLog();
$team->users()->sync( $user_ids, false );
dd(DB::getQueryLog());

sql

array:3 [
  0 => array:3 [
    "query" => "select `user_id` from `team_user` where `team_id` = ?"
    "bindings" => array:1 [
      0 => 15
    ]
    "time" => 1.0
  ]
  1 => array:3 [
    "query" => "insert into `team_user` (`created_at`, `team_id`, `updated_at`, `user_id`) values (?, ?, ?, ?)"
    "bindings" => array:4 [
      0 => Carbon {#898
        +"date": "2019-03-24 12:10:02.000000"
        +"timezone_type": 3
        +"timezone": "Asia/Tokyo"
      }
      1 => 15
      2 => Carbon {#898}
      3 => 868
    ]
    "time" => 1.02
  ]
  2 => array:3 [
    "query" => "insert into `team_user` (`created_at`, `team_id`, `updated_at`, `user_id`) values (?, ?, ?, ?)"
    "bindings" => array:4 [
      0 => Carbon {#899
        +"date": "2019-03-24 12:10:02.000000"
        +"timezone_type": 3
        +"timezone": "Asia/Tokyo"
      }
      1 => 15
      2 => Carbon {#899}
      3 => 911
    ]
    "time" => 0.9
  ]
]

まず、中間テーブルの既存のレコードでダブりのチェックをするためのsqlが走る。

そのあと、配列で渡す要素分の本数のinsertクエリが走ることになる。

注意点

配列で渡す要素の数がわからない場合や、ここで言う$teamが複数あってforeachなどで繰り返し処理を行いたい場合などはクエリが予期しない本数走る場合があるので避けるべき

条件分岐の処理を書くときに気をつけること

if文の処理を下のように3ステップで考えていく。

  1. if文によって処理自体を条件分岐させる。これで意図した動きにはなっている。
$response['name'] = 'default' . $extention;
if( !is_null( $request->file_name ) ) {
    $response['file_name'] = $request->file_name . $extention;
}
  1. if文によって変数の場合分けをする。行数は増えている。
$file_name = 'default';
if( !is_null( $request->file_name ) ) {
    $file_name = $request->file_name;
}
$response['file_name'] = $file_name . $extention;
  1. 三項演算子を使う。
$file_name = is_null( $request->file_name ) ? 'default': $request->file_name;
$response['file_name'] = $file_name . $extention;

それぞれの変更理由

  • 1から2に修正する理由
    • 1だとif文の条件が真の場合に文字列結合の処理が二回実行されることになる。
    • 次の処理に渡すための最も重要な変数が$response['file_name']なのでその変数を操作する処理は最後に一回だけ行うようにしてif文によって処理が分散するのを防ぎたい。
  • 2から3から修正する理由
    • 三項演算子を使うことで行数が減っている
    • 三項演算子を使うことで変数の定義し忘れがなくなる。
    • 条件式がシンプルになった。

特に重要だと思うこと

1から2の修正がポイントだと思っている。

今回は条件分岐も一つだけでかつ変数に値を格納するだけの処理なので大した違いにはならず行数が増えた分返って冗長になっているような気もする。

ただし条件分岐のパターンが多く複雑になる場合にはこのように最後に処理を一度だけ行うようにした方がわかりやすくなる。

またもしsaveの対象を条件分岐したい場合などはこのような考え方がより重要になる。saveは最後に一度だけ走らせるようにするべきで直接条件分岐のなかに入れるべきではない。

ソフトデリート

参考

Laravel 5.3 Eloquent:利用の開始

概要

laravelのEloquentではモデルのソフトデリートを行うことができる。

ソフトデリートとはいわゆる論理削除のことで物理削除のように実際にデータを削除するわけではないが該当のデータを削除したものをして取り扱うことをいう。

準備

具体的にはそのモデルのレコードのdeleted_at属性にdate型の値をセットすることでこれを行う。前提としてモデルのテーブルのカラムにdeleted_atがあり、またモデルクラスでIlluminate\Database\Eloquent\SoftDeletesトレイトをuseしておく必要がある。

migrationでdeleted_atを追加するためのメソッドも用意されている。

Schema::table('users', function ($table) {
    $table->softDeletes();
});

使い方

ソフトデリートのために準備されたモデルにdeleteメソッド使うとdeleted_atにその時点の時刻が入り論理削除済みとなる。deleted_atがnullでないレコードはクエリの対象とならない。インタンスに対してtrashed()メソッドを実行するとソフトデリート済みかどうかの結果がブーリアンで返ってくる。

ソフトデリートは常にクエリの対象とならないがあえて含めたい場合にはstatic functionのwithTrashedを使ってクエリを作ればそれができる。これはリレーションのチェーンメソッドとしても使うことができる。

さらにonlyTrashedメソッドを使えば論理削除済みのアイテムのみを選ぶこともできる。

論理削除からインスタンスを復活させるにはそのインスタンスに対してrestore()メソッドをかける。これもリレーションに対して使用できる。

反対に完全削除をかける時にはforceDelete()をインスタンスにかける。

外部キー制約を付けるときのマイグレーションで気をつけること

要約

外部キー制約を設定するときは外部キーにunsigned制約を付ける。

外部キーと対象のプライマリーキーが完全に同じ型である必要があって、プライマリーキーには自動的にunsignedがかかっているから。

エラー内容

Illuminate\Database\QueryException : SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint

constraintは制約の意味。sql的に一般的にエラーだよと言っている

migration file

例えば下記のようにしておく。

$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

修正が面倒なのでもう間違えたくない。

外部キー制約の付与に失敗しているがテーブルは作られている。

なので修正してmigrateすると「もうテーブルあるよ」のエラーがでる。

resetとかしてもこのrollbackだけ効かなかったりして面倒だが直接テーブルを削除することでやり直した。

トレイト

参考

トレイト

概要

  • コードをまとめて再利用するための仕組み。
  • メソッドをまとめて書いておき、使用したいクラスで呼び出すことでそのクラスでメソッドを定義したかのように使用することができる。
  • クラスに似ているがトレイト自身のインスタンスを作ることはできない。

使い方

  • 下記のように定義できる
trait someMethods {
    function getSomething() 
    function getSomethingElse() 
}
  • 下記のように呼び出せる
class somethingController{
    use someMethods;
}

使われ方

ソースコードを読んでいてそのクラスのなかに呼び出されているはずのメソッドが見当たらない時はuseで参照されているトレイトにある可能性がある。

クエリビルダ

参考

PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応

概要

クエリビルダはsqlを作って実行するための仕組み。

Eloquentも内部的にクエリビルダのインスタンスを持っている。

メソッド

sqlの関数に即したメソッドを持っている。

select, where, leftJoin, orderBy などのメソッドはクエリビルダのインスタンス(Illuminate\Database\Query\Builder)を返り値として返す。

またこの時点ではsqlは実行されない。

sqlが実行されるのはget()やfirst()が実行されるとき。

この結果はEloquentモデルのインスタンスとして返ってくる。

構文

  1. クエリビルダオブジェクトの取得
  2. メソッドチェーンでSQLを作成
  3. 実行してEloquentモデルのインスタンスを取得
クエリビルダをDBファサードから取得する方法
 $query = DB::table('users');
メソッド覚書

where系のメソッドは連続して繋げるとand条件になる。or条件を使いたい場合はwhere()にorWhereをつなげる。

join系のメソッドを使うことで条件に当てはまらないレコードはこの時点で弾くことができる。

クエリの実行・データの取得
  • get()

    • 全てのデータを取得する

    • object(Illuminate\Database\Eloquent\Collection)

  • first()

    • 最初の一行を取得する

    • object(App\User)など具体的なモデルインスタンス