【Laravel】EloquentのsaveではCarbonオブジェクトをStringに変換している

はじめに

Eloquentを利用すると、以下の①、②のどちらのパターンでも保存することが出来ます。

$foo = Foo::getModel();

// ①明示的にStringを渡す
$foo->fill([
    'datetime' => Carbon::now()->toDateTimeString(),
    'date'     => Carbon::now()->toDateString(),
])->save();

// ②Carbonオブジェクトのまま渡す
$foo->fill([
    'datetime' => Carbon::now(),
    'date'     => Carbon::now(),
])->save();

②では、datetime型のカラムに文字列ではなくCarbonオブジェクトを渡していますが、おそらくSQLを組み立てる際にキャストして文字列を組み立てているのだろうな、とは思っていました。

ただ、どちらでもOKというのが腑に落ちなかったので、該当箇所のコードリーディングをしてみます。

コードリーディング

fill

fillでは主に、「入力されたattributesがassignableであれば $attributes 配列(property)に詰め込む」という処理を行っています。

https://github.com/laravel/framework/blob/9.x/src/Illuminate/Database/Eloquent/Model.php#L499-L547

実際に詰め込んでいるのは、 HasAttributes traitです。

https://github.com/laravel/framework/blob/9.x/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L938-L993
$this->attributes[$key] = $value;

save

saveでは、fillで詰め込んだattributesを元にSQLを組み立て実行します。

https://github.com/laravel/framework/blob/9.x/src/Illuminate/Database/Eloquent/Model.php#L1097-L1144

途中は省略しますが、insert実行までの一連の処理を追っていくと、Connectionクラス の prepareBindings に到達します。

    /**
     * Prepare the query bindings for execution.
     *
     * @param  array  $bindings
     * @return array
     */
    public function prepareBindings(array $bindings)
    {
        $grammar = $this->getQueryGrammar();

        foreach ($bindings as $key => $value) {
            // We need to transform all instances of DateTimeInterface into the actual
            // date string. Each query grammar maintains its own date string format
            // so we'll just ask the grammar for the format to get from the date.
            if ($value instanceof DateTimeInterface) {
                $bindings[$key] = $value->format($grammar->getDateFormat());
            } elseif (is_bool($value)) {
                $bindings[$key] = (int) $value;
            }
        }

        return $bindings;
    }

ここまで引き渡してきたattributeの値が DateTimeInterface の実装である場合、フォーマットされた文字列で詰め込み直しています。Carbonは DateTimeInterface を実装しているので、ここまで来てようやく文字列に変換される、ということでした。

フォーマットは Grammerクラス に定義されている Y-m-d H:i:s になるようです。

    /**
     * Get the format for database stored dates.
     *
     * @return string
     */
    public function getDateFormat()
    {
        return 'Y-m-d H:i:s';
    }

ConnectionとGrammerクラスは、DB接続とSQLの構文を抽象化しており、databases.phpで指定しているdriverによって解決されるクラスが変わります。

おわりに

Carbonオブジェクトを渡した場合、SQLを組み立てる最終段階で文字列に変換されることが確認出来ました。Eloquentを利用する際はどちらを使っても仕様上は問題なさそうですね。

ちなみに created_at と updated_at のように自動的に挿入されるカラムはCarbonオブジェクトをそのまま attributes に突っ込んでいました。

    /**
     * Update the creation and update timestamps.
     *
     * @return $this
     */
    public function updateTimestamps()
    {
        $time = $this->freshTimestamp();

        $updatedAtColumn = $this->getUpdatedAtColumn();

        if (! is_null($updatedAtColumn) && ! $this->isDirty($updatedAtColumn)) {
            $this->setUpdatedAt($time);
        }

        $createdAtColumn = $this->getCreatedAtColumn();

        if (! $this->exists && ! is_null($createdAtColumn) && ! $this->isDirty($createdAtColumn)) {
            $this->setCreatedAt($time);
        }

        return $this;
    }
    /**
     * Get a fresh timestamp for the model.
     *
     * @return \Illuminate\Support\Carbon
     */
    public function freshTimestamp()
    {
        return Date::now();
    }
タイトルとURLをコピーしました