LaravelのModelクラスのcreated_atのようなDateTime型をCarbonとして扱う

Laravelには日時を扱う際に便利なCarbonライブラリが組み込まれており、

現在ではPHP界隈でメジャーになってきたかなとひしひしと感じる今日この頃。

同様にLaravelのModelはデフォルトでcreated_atとupdated_atをサポートしており、

DBにデータを反映させる際に自動的に面倒を見てくれますよね。

…で、例えばあるレコードの日時型のカラムを扱う際に、その都度Caron::parse($this->created_at)なんてしていると、非効率でいなくなりたくなります。

 

自分の同じような経験をしたことが人へ、少しでも手助けとなりますように。

 

《想定読者》

  • Laravelを使っている/今後使いたい人で、先人の失敗談に興味のある人。
  • LaravelでModelとCarbon使っているときに、少しイケてないコードを書いちゃってるなと思っている人。
  • PHPのクラスにマジックメソッドという素晴らしいものがあることを知らない人。
  • Laravelを構成するライブラリのソースコードを読む習慣のない人。(読みましょう)

 

《背景》

冒頭でも書きましたが、LaravelのModelクラスで例えばMySQLのDateTime型でデータを取り出したときは、デフォルトのままでは以下のようになります。

$ php artisan tinker
Psy Shell v0.8.17 (PHP 7.2.9-1+ubuntu18.04.1+deb.sury.org+1 — cli) by Justin Hileman
>>> $sampleModel = SampleModel::find(1)
[!] Aliasing 'SampleModel' to 'App\SampleModel' for this Tinker session.
=> App\SampleModel {#886
  id: 1,
  created_at: "2018-11-20 15:24:53",
  updated_at: "2018-11-20 15:24:53"
}
>>> $sampleModel->created_at
=> "2018-11-29 10:40:00"

 

このように

>>> $sampleModel->created_at
=> "2018-11-29 10:40:00"

となっていることからcreated_atは文字列型の扱いです。(DateTimeですらない)

もしcreated_atをCarbonとして利用したい場合は、

\Carbon\Carbon::parse($sampleModel->created_at)

としなくてはいけません。

 

基本的には上記の方法で問題ないのですが、コントローラを1度コールされた際に、

\Carbon\Carbon::parse($sampleModel->created_at);

\Carbon\Carbon::parse($sampleModel->created_at);

\Carbon\Carbon::parse($sampleModel->created_at);

と何度も呼び出すのは非効率だし、

使いまわすために

$createdAt = \Carbon\Carbon::parse($sampleModel->created_at);

$createdAt;

$createdAt;

とやるのもいけてない気がする。。。 

 

「そうだ、Modelクラス側でDateTimeカラムは自動的にCarbon型でとれるようにしよう!」というのが今回の内容です。

 

《方法》

Modelクラスの以下のマジックメソッドにテコ入れを行う。

  • __construct
  • __get
  • __set

 

《マジックメソッドとは?》

それくらい公式のドキュメントをご覧ください。

http://php.net/manual/ja/language.oop5.magic.php

 

《実際のコード》

今回はトレイトとして実装します。

<?php
namespace App;

use Carbon\Carbon;

trait AutoCarbonField
{
protected $carbonFields = [];
protected $carbonValues = [];

public function __construct(array $attributes = [])
{
parent::__construct($attributes);

if ($this->timestamps) {
$this->carbonFields[] = static::CREATED_AT;
$this->carbonFields[] = static::UPDATED_AT;
}
}

/**
* getter magic method.
*
* @param string $key
* @return Carbon|mixed|null
* @throws \Exception
*/
public function __get($key)
{
if (array_search($key, $this->carbonFields) !== false) {
$attribute = $this->getAttribute($key);
if (isset($attribute)) {
if (array_key_exists($key, $this->carbonValues)) {
return $this->carbonValues[$key];
}
try {
return $this->carbonValues[$key] = Carbon::parse($attribute);
} catch (\Exception $e) {
throw new \Exception(sprintf("Invalid Carbon field. [class: %s, property: %s, value: %s]", static::class, $key, $attribute), null, $e);
}
}
return null;
}

return parent::__get($key);
}

/**
* setter magic method.
*
* @param string $key
* @param mixed $value
* @throws \Exception
*/
public function __set($key, $value)
{
if (array_search($key, $this->carbonFields) !== false && array_key_exists($key, $this->carbonValues)) {
unset($this->carbonValues[$key]);
parent::__set($key, $value);
$this->__get($key);
} else {
parent::__set($key, $value);
}
}
}

このトレイトをuseして、

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class SampleModel extends Model
{
use AutoCarbonField;
}

その後、SampleModel->created_atがCarbonになっているか確認

>>> $sampleModel = \App\SampleModel::find(1)
=> App\SampleModel {#887
  id: 1,
  created_at: "2018-11-20 15:18:25",
  updated_at: "2018-11-20 15:25:22"
}
>>> $sampleModel->created_at
=> Carbon\Carbon @1542694705 {#875
  date: 2018-11-20 15:18:25.0 Asia/Tokyo (+09:00),
}

>>> get_class($sampleModel->created_at)
=> "Carbon\Carbon"
>>> $sampleModel->created_at->format('Y/m/d H:i:s')
=> "2018/11/20 15:18:25"
>>>

これで煩わしいCarbon::parseから解放されました。

 

以上です。

今回も閲覧いただきありがとうございました。