1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CarbonImmutable、NoOverflow をデフォルトに

Last updated at Posted at 2022-05-18

PHP の日付操作ライブラリ Carbon の問題

プロジェクトのコーディング規約で、私は PHP のdate 関数と strtotime 関数、及び MySql の NOW 関数の使用禁止を第一に謳う。
日付操作のロジックを可視化するには Carbon を使用するべきであり、Sql クエリ内の固定された NOW はユニットテストの障害となるから。

しかし、Carbon を使用さえすれば良いかといえば、そうはいかない。
初期状態の Carbon には、プログラムコードにバグを潜らせる問題点が2つある。

  1. Carbon オブジェクトは値が変動する
    $today = Carbon::now(); $tomorrow = $today->addDay(); のようなコードは書きがちであるが、もとの変数も変動してしまう。これを避けるには CarbonImmutable を意識して宣言する必要がある。
  2. 1月末の1ヶ月後が2月末にならない
    これを回避するために addMonth ではなく addMonthNoOverflow を使用しろと言うのは無理がありすぎる。

ということで、Laravel で Carbon を使用するに当たり、CarbonImmutable と NoOverflow をデフォルトに 設定する。

必要な設定は次の3項目だ。

  • src/Illuminate/Support/Carbon.php を複写して app/Helpers/Carbon.php を作成し、継承元を Carbon\CarbonImutable に差し替える。
  • config/app.php に App\Helpers\Carbon のエイリアスを追加する。
  • app/Providers/AppServiceProvider.php で、noOverflow を初期値設定と now ヘルパーの使用クラス設定を追加する。

Laravel の初期状態を確認

now() ヘルパーは、Carbon\Carbon ではなく Illuminate\Support\Carbonを参照している。
ただし、これは Carbon\Carbon のラッパーでしかない。

$ php artisan tinker
Psy Shell v0.11.2 (PHP 8.0.15  cli) by Justin Hileman
>>> now()
=> Illuminate\Support\Carbon @1652861045 {#4296
     date: 2022-05-18 17:04:05.830169 Asia/Tokyo (+09:00),
   }

1月末から1ヶ月後は3月に繰り越す。
そして、もとの変数 $now まで変化してしまう。

>>> Illuminate\Support\Carbon::setTestNow('2020-01-31')
=> null
>>> $now = now()
=> Illuminate\Support\Carbon @1580396400 {#4300
     date: 2020-01-31 00:00:00.0 Asia/Tokyo (+09:00),
   }
>>> $next = $now->addMonth()
=> Illuminate\Support\Carbon @1583074800 {#4300
     date: 2020-03-02 00:00:00.0 Asia/Tokyo (+09:00),
   }
>>> $now
=> Illuminate\Support\Carbon @1583074800 {#4300
     date: 2020-03-02 00:00:00.0 Asia/Tokyo (+09:00),
   }

app/Helpers/Carbon.php の作成

src/Illuminate/Support/Carbon.php を複写して app/Helpers/Carbon.php を作成する。
継承元を Carbon から CarbonImmutable に変えただけだ。

<?php

namespace App\Helpers;  // 複写先に修正

use Carbon\Carbon as BaseCarbon;
use Carbon\CarbonImmutable as BaseCarbonImmutable;

class Carbon extends BaseCarbonImmutable // 継承元を修正
{
    /**
     * {@inheritdoc}
     */
    public static function setTestNow($testNow = null)
    {
        BaseCarbon::setTestNow($testNow);
        BaseCarbonImmutable::setTestNow($testNow);
    }
}

Carbon を使用するときの use 宣言は次のようにすることになる。
これで、もとの変数は変化しない CarbonImmutable がデフォルトとなる。

// use Carbon\Carbon;              // 一般的な use 宣言
// use Illuminate\Support\Carbon;  // Laravel における use 宣言
use App\Helpers\Carbon;

config でエイリアスを指定することで、use 宣言なし(代わりに \Carbon 指定)で呼び出すことも可能。

config/app.php

    'aliases' => [
        ~~ 省略~~

        'Carbon' => App\Helpers\Carbon::class,
    ],

now(), today() ヘルパー

大抵は、use 宣言をせずに使用できる now() ヘルパーを常用するだろう。
ヘルパーが参照するクラスが App\Helper\Carbon となるように、AppServiceProvider で定義することができる。
ついでに、ここで noOverflow を初期値に設定する。

app/Providers/AppServiceProvider.php

<?php

namespace App\Providers;

use App\Helper\Carbon;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // noOverflowを初期値に設定
        Carbon::useMonthsOverflow(false);
        Carbon::useYearsOverflow(false);
        // now(), today() の使用クラスを変更
        Date::use(Carbon::class);
    }

変更後の確認

now() 関数の参照クラスは App\Helpers\Carbon となった。

$ php artisan tinker
Psy Shell v0.11.2 (PHP 8.0.15  cli) by Justin Hileman
>>> now()
=> App\Helpers\Carbon @1652861045 {#4296
     date: 2022-05-18 17:04:05.830169 Asia/Tokyo (+09:00),
   }

1月末の1ヶ月後は2月末であり、もとの変数 $now は維持されるようになった。

>>> Carbon::setTestNow('2020-01-31')
=> null
>>> $now = now()
=> App\Helpers\Carbon @1580396400 {#4300
     date: 2020-01-31 00:00:00.0 Asia/Tokyo (+09:00),
   }
>>> $next = $now->addMonth()
=> App\Helpers\Carbon @1583074800 {#4300
     date: 2020-02-28 00:00:00.0 Asia/Tokyo (+09:00),
   }
>>> $now
=> App\Helpers\Carbon @1583074800 {#4300
     date: 2020-01-31 00:00:00.0 Asia/Tokyo (+09:00),
   }

Eloquent の日時オブジェクトも App\Helpers\Carbon に変更になっている。

>>> User::find(1)->created_at
[!] Aliasing 'User' to 'App\Models\Eloquent\User' for this Tinker session.
=> App\Helpers\Carbon @1652765323 {#4320
     date: 2022-05-17 14:28:43.0 Asia/Tokyo (+09:00),
   }
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?