Laravel での API 実装時、知識不足でエラーを出してしまったため、メモです。

エラーの内容

SPA アプリを実装中、API 側ソースを修正したところ、フロントエンドJS側でエラーが発生するようになった。

バージョン

  • PHP 7.0.x
  • Laravel 5.4

原因

Laravel の Collection を JSON 化してレスポンスする場合、ソートの有無で JSON 構造が変わってしまう。

Laravel のソートはキーを維持する

Laravel のコレクションをソートした場合、元々のキーを維持します。

全く認識していませんでしたが、ドキュメントを読み返すと明記されていました。。

The sorted collection keeps the original array keys

Laravel > Documentation > 5.4 > Collections > sort()
https://laravel.com/docs/5.4/collections#method-sort

この仕様は sortBy() でも sortByDesc() でも同じです。


実例を見てみましょう。

サンプル A

ORM からデータを取り出し、そのまま dd() します。

1$books = $this->book->findByTag($tagId);
2dd($books);
3

この場合は次のように出力されます。

1Collection {#362 ▼
2  #items: array:5 [▼
3    0 => Book {#363 ▶}
4    1 => Book {#364 ▶}
5    2 => Book {#365 ▶}
6    3 => Book {#366 ▶}
7    4 => Book {#367 ▶}
8  ]
9}

サンプル B

次にソートしたものを dd() してみます。

1$sortedBooks = $books->sortBy('price');
2dd($sortedBooks);
3

ソートすると次のように出力されます。

1Collection {#362 ▼
2  #items: array:5 [▼
3    1 => Book {#364 ▶}
4    3 => Book {#366 ▶}
5    0 => Book {#363 ▶}
6    2 => Book {#365 ▶}
7    4 => Book {#367 ▶}
8  ]
9}

サンプル A, B とで「配列の添え字と値の組み合わせが一致」していますね。

サンプル B ではソートされ出力順が変化しましたが、添字は維持しており「 添字の並びがチグハグ 」担っていることがわかると思います。

JSON のリストとオブジェクト

Laravel でコントローラーからコレクションを return すると、データを JSON 化してくれます。

1Bookontroller
2{
3    public function findByTag(Request $request, $tagId)
4    {
5        return $this->bookService->findByTag($tagId);
6    }
7}
8

JSON 化は、内部的に PHP 標準関数の json_encode を呼び出していています。
この json_encode は 「スカラー配列をリスト」 に、「連想配列をオブジェクト」に変換 します。

先のサンプル A, B のコレクションを JSON 化した場合どのように出力されるでしょうか。

サンンプル A のを JSON にした場合

1[
2  {"id": 1, "title": "リーダブルコード", "isbn": "1234..."},
3  {"id": 2, "title": "Team Geek", "isbn": "1234..."},
4  :
5]

サンプル B を JSON にした場合

1{
2  "1": {"id": 1, "title": "リーダブルコード", "isbn": "1234..."},
3  "3": {"id": 3, "title": "新装改訂 リファクタリング", "isbn": "1234..."},
4  :
5}

JSON の外側構造が、A はリスト、B はオブジェクトになっています。

わかりやすいように書き直して見ると、こういうことです。

1// リスト A の構造
2[ {...}, {...} ]
1// リスト B の構造
2{ 1: {...}, 3: {...} }

PHP はスカラー配列であっても内部的には全て連想配列です。
これに対し、他の多くの言語ではリストとハッシュが別実装であるため、このような手違いが起こるのですね。

解決方法

コレクションの values() を実行しキーを振り直すことで解消できます。

Laravel > Documentation > 5.4 > Collections > values()
https://laravel.com/docs/5.4/collections#method-values"

values メソッドは、キーをリセットし連続した整数にした新しいコレクションを返してくれます。

ソート呼び出し後にチェーンすればよく、手間ではありますがシンプルな実装は保たれますね。

1$sortedBooks = $books->sortBy('price')->values();
2

まとめ

PHP 側でちょっと複雑なソートをする場合はこんなことがありますよという内容でした。

通常「 SQL レベルで Order を済ましてしまう」実装がベターです。


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

竹澤 有貴,栗生 和明,新原 雅司,大村 創太郎
出版社:ソシム  発売日:2018-09-26

Amazonで詳細を見る

PHPフレームワーク Laravel入門

掌田津耶乃
出版社:秀和システム  発売日:2017-09-16

Amazonで詳細を見る

初めてのPHP

David Sklar
出版社:オライリージャパン  発売日:2017-03-18

Amazonで詳細を見る