Laravel は PHP のフレームワークでよく web アプリケーションを作るために用いられます。Laravel の中で日時を使う時、それは Illuminate\Support\Carbon というクラスのインスタンスでよく扱われます。この Illuminate\Support\Carbon は次リンクの Carbon というライブラリのラッパークラスです。
Carbon – A simple PHP API extension for DateTime.
この Illuminate\Support\Carbon インスタンスですが Laravel の中で JSON として変換する時に二種類のフォーマットが混在しやすいです。これは Eloquent という Laravel のデータベースのテーブル密接な関係にあるクラスの中でのフォーマット指定と Illuminate\Support\Carbon そのものフォーマット指定が異なるためです。どういうことか説明すると、まず Eloquent では次の様に Carbon を指定する機能が用意されており(実装は複雑なので割愛。 jsonSerialize, toArray, attributesToArray と追っていくと各カラムの値の型を変換する処理にたどり着けます)、
タイムスタンプのフォーマットをカスタマイズする必要があるなら、モデルの
$dateFormatプロパティを設定してください。このプロパティはデータベースに保存される日付属性のフォーマットを決めるために使用されると同時に、配列やJSONへシリアライズする時にも使われます。<?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model { /** * モデルの日付カラムの保存フォーマット * * @var string */ protected $dateFormat = 'U'; }
このフォーマットのデフォルトが基本的に Y-m-d H:i:s となっています(SqlServer につながっている時だけ Y-m-d H:i:s.v)。
そして Carbon はそれ自身にも JSON 化する時の変換機能があり、基本的にインスタンス単位でしか定められず、デフォルトでは ISO 8601 形式に変換されます。
Carbon – A simple PHP API extension for DateTime.
この二つのフォーマットの違いにより JSON 中に Y-m-d H:i:s 形式の日時と ISO 8601 形式の日時が混在しやすくなります。最も多い混在パターンは同じ API サーバなのにレスポンスによって日時表記がぶれて奇妙、というパターンでしょう。
この混在を解決する方法の一つとして次のコードがあります。
// protected static なプロパティである serializer をリフレクションクラスで読み取ります
$originalSerializer = (new \ReflectionClass(Carbon::class))->getStaticPropertyValue('serializer');
// json_encode した時に呼ばれる jsonSerializer メソッドのフォーマットを変更します
Carbon::serializeUsing('Y-m-d H:i:s');
// Carbon インスタンスを json_encode
dump(json_encode(['time' => new Carbon('now')], JSON_PRETTY_PRINT));
/*
{
"time": "2021-03-04 11:20:26"
}
*/
// 他の Carbon を使う処理に影響を与えないために serializer プロパティの値を元に戻します
Carbon::serializeUsing($originalSerializer);
リフレクションを用いていますが、プリミティブな値の読み取りのみであるため比較的安全です(IDE や PHP でコードの流れを追うのこそ難しくなりますが)。serializeUsing メソッドは deprecated(非推奨)なメソッドですが、これは Carbon のソースコード中の次のコメントの通りの理由で非推奨となっているため、例の様に Serializer を静的セッターメソッド使用前に復元すれば問題は起きにくくなります。
To avoid conflict between different third-party libraries, static setters should not be used. You should rather transform Carbon object before the serialization. 異なるサードパーティライブラリ間の競合を避けるために、静的セッターは使用しないでください。Carbonオブジェクトは、シリアル化の前に変換しておくべきです。
上記コードの様に JSON 化する前後でシリアライザーの変更と復元を行うことで Carbon を JSON 化した時のデフォルトのフォーマットを変更できます。具体的な例として Laravel の JSON レスポンス用のスニペットは次の様になります。
/** Laravel の用意したデフォルトコントローラクラスを拡張 */
class BaseController extends Controller
{
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
public function makeJsonResponse($data, int $code = 200): \Illuminate\Http\JsonResponse
{
if (is_array($data) || $data instanceof JsonSerializable) {
$originalSerializer = (new \ReflectionClass(Carbon::class))->getStaticPropertyValue('serializer');
\Illuminate\Support\Carbon::serializeUsing('Y-m-d H:i:s');
$data = json_decode(json_encode($data, JSON_THROW_ON_ERROR), true, 4096, JSON_THROW_ON_ERROR));
\Illuminate\Support\Carbon::serializeUsing($originalSerializer);
}
return \Response::json($data, $code);
}
}
// 使用例
class TimeController extends BaseController
{
public function index(): JsonResponse
{
return $this->makeJsonResponse([
'time' => now()
]);
}
}