[Laravel]モデルの標準日付フォーマットを指定する

2019/08/07

課題

日付のフォーマットを YYYY-MM-DD から YYYY/MM/DD に変更したい。

結論

モデルのDateフォーマットを↓こんな風に設定しても、うまくいかずエラーになってしまう。(MySQL ・ PostgreSQL ・ SQLite で試してみたけれど変わらず)

protected $dateFormat = 'Y/m/d H:i:s';

画面上の出力形式を変更するには、Eloquentのアクセサ機能を利用する。

function getEndDateAttribute($value)
{
    return $value ? Carbon::parse($value)->format('Y年m月d日') : null;
}

詳細

ほぼ Laravel インストール直後の状態からスタートして、インストール時に生成される users テーブルの CRUD 画面を作ってみます。

(参考:Laravel5.7: usersのCRUD機能を実装する – Qiita

php artisan migrate

で users テーブルが生成されます。

次にリソースコントローラーを生成。

php artisan make:controller UserController --resource

これで app\Http\Controllers\UserController.php が生成されて、index() や create() なんかのメソッド定義だけが自動的に用意されます。

ルーティングは web.php に

Route::resource('users', 'UserController');

と書けば、単純なCRUDについてはOK。

データが空なので、先に簡単な登録画面を作成してしまいましょう。

まずは登録画面を表示させるために、 app\Http\Controllers\UserController.php にて

    public function create()
    {
        return view('users.create');
    }

としておきます。

ここで指定した “users.create" の 実体(htmlを出力するBladeファイル)として resources\views\users\create.blade.php を作成します。

<div>
    <form action="{{ url('users') }}" method='post'>
        @csrf
        <p>name <input id="name" type="text" name="name"></p>
        <p>e-mail <input id="email" type="email" name="email"></p>
        <p>password <input id="password" type="password" name="password"></p>
        <p>password(confirm) <input id="password_confirmation" type="password" name="password_confirmation"></p>
        <p><button type="submit" name="submit">登録</button></p>
    </form>
</div>

※バリデーションもcssも何もないので良い子はマネしてはいけません。

入力したデータの行先は action="url('users’)" となっていて、php artisan route:list で確認してみます。

>php artisan route:list
+--------+-----------+-------------------+---------------+---------------------------------------------+--------------+
| Domain | Method    | URI               | Name          | Action                                      | Middleware   |
+--------+-----------+-------------------+---------------+---------------------------------------------+--------------+
|        | GET|HEAD  | /                 |               | Closure                                     | web          |
|        | GET|HEAD  | api/user          |               | Closure                                     | api,auth:api |
|        | GET|HEAD  | users             | users.index   | App\Http\Controllers\UserController@index   | web          |
|        | POST      | users             | users.store   | App\Http\Controllers\UserController@store   | web          |
|        | GET|HEAD  | users/create      | users.create  | App\Http\Controllers\UserController@create  | web          |
|        | GET|HEAD  | users/{user}      | users.show    | App\Http\Controllers\UserController@show    | web          |
|        | PUT|PATCH | users/{user}      | users.update  | App\Http\Controllers\UserController@update  | web          |
|        | DELETE    | users/{user}      | users.destroy | App\Http\Controllers\UserController@destroy | web          |
|        | GET|HEAD  | users/{user}/edit | users.edit    | App\Http\Controllers\UserController@edit    | web          |
+--------+-----------+-------------------+---------------+---------------------------------------------+--------------+

POST の users は App\Http\Controllers\UserController@store ということで、 UserController の store メソッドを実装します。

(リソースコントローラー生成時にメソッド定義だけ自動的に用意されてます。)

    public function store(Request $request)
    {
        $user = new User;
        $user->name = $request->name;
        $user->email = $request->email;
        $user->password = $request->password;
        $user->save();
        return redirect('users/'. $user->id);
    }

ついでに、指定されたユーザーを表示する show メソッドを用意しておきましょう。(登録完了時に リダイレクトした先が無いと真っ白画面になってしまうのです。)

    public function show(User $user)
    {
        return view('users.show', ['user' => $user]);
    }

初期状態では仮引数が型宣言(タイプヒンティングという呼び方はPHP5の時代みたいっすよ)なしで show( $user ) になっていますが、show( User $user ) と型宣言してやることにより、Laravel の「モデル結合ルート」によってモデルを直接やり取りできるようになります。

そして、そのモデルをそのまま view() で Blade に渡してやります。

<div>
    <h1>user id : {{ $user->id }}</h1>
    <p>name : {{ $user->name }}</p>
    <p>e-mail : {{ $user->email }}</p>
</div>

さて、動かしてみます。ブラウザで

http://localhost:8000/users/create

にアクセスすると、create.blade.php がレンダリングされます。

適当に値を入れて「登録」ボタンをクリックすると、

無事DBに登録されました。

さて、お次は、created_at と updated_at のカラム属性を変更してマイクロ秒まで持てるようにしてみます。

Laravel インストール時に生成されている users テーブルでは、

    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

このように定義されているので、最後の行を

            $table->timestamps(6);

としてテーブルを再生成します。(↓データ消えるのでご注意を)

php artisan migrate:refresh

でテーブルが再生成されました。

DBツール(A5:SQL Mk-II 使ってます)で確認すると、

無事、timestamp(6) without time zone となりました。

さて、実はここからが本題なんですが、Laravelの公式ドキュメントには、

タイムスタンプフォーマットをカスタマイズする必要があるなら、モデルの$dateFormatプロパティを設定してください。

と書かれています。

「ほー。スラッシュ区切りにしよっと。」とか思って、

    protected $dateFormat = "Y/m/d H:i:s.u";

なんてやってみるんですが、これで上手くいったためしがありません…。

vendor\nesbot\carbon\src\Carbon\Traits\Creator.php#createFromFormatAndTimezone()

辺りで、日付の値と指定フォーマットが違うじゃん、って怒られてエラーになってしまいます。

「2019-09-10 みたいなハイフンじゃなくて、スラッシュ区切りで 2019/09/10 って出したいんじゃい!」という場合は、 protected $dateFormat = で指定するのではなく、Eloquentのアクセサ機能を使って希望の書式を返してやる、ということのようです。

use Carbon\Carbon;

class User extends Authenticatable
{
    (略)

    public function getUpdatedAtAttribute($value)
    {
        return Carbon::parse($value)->format("Y/m/d H:i:s.u");
    }
}

こうしてやることで、

希望通りの書式で表示されるようになりました。