【Laravel】Model の wasRecentlyCreated プロパティが true になるタイミングを調べる




## 環境
```
$ sail php -v
PHP 8.2.13 (cli) (built: Nov 24 2023 08:47:18) (NTS)
```

```
$ sail artisan --version
Laravel Framework 10.39.0
```

```
mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.32    |
+-----------+
1 row in set (0.00 sec)
```

## 目的

* Illuminate/Database/Eloquent/Model には wasRecentlyCreated という publuc なプロパティがある。その値が true になるタイミングを調べる。
* 以下の通り、wasRecentlyCreated は「オブジェクトのライフサイクル中にモデルが INSERT されたかどうかを示す」ために存在するプロパティである。

```php:Illuminate/Database/Eloquent/Model
abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
    // 略

    /**
     * Indicates if the model was inserted during the object's lifecycle.
     *
     * @var bool
     */
    public $wasRecentlyCreated = false; // デフォルト値は false
    
    // 略
}
```

## 各メソッドを呼んだとき等の wasRecentlyCreated の変化を調べる

### new したとき
```php
use App\Models\User;

$user = new User();
dump($user->wasRecentlyCreated); // false
```



### first() のとき

* [Illuminate/Database/Concerns/BuildsQueries@first](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Concerns/BuildsQueries.php#L331)


```php
use App\Models\User;

$user = User::first();
dump($user->wasRecentlyCreated); // false
```

なお、find() も中身は first() なので find() は省略。


### create() のとき

* [Illuminate/Database/Eloquent/Builder@create](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Eloquent/Builder.php#L1020)

```php
use App\Models\User;
use Illuminate\Support\Str;

$name = Str::random();
$user = User::create(
    [
        'name'     => $name,
        'email'    => "{$name}@example.com",
        'password' => Hash::make('password'),
    ]
);
dump($user->wasRecentlyCreated); // true
```

### save() のとき(新規作成)

* [Illuminate/Database/Eloquent/Model@save](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Eloquent/Model.php#L1112)

```php
use App\Models\User;
use Illuminate\Support\Str;

$user = new User();

$name = Str::random();
$user->fill([
    'name'     => $name,
    'email'    => "{$name}@example.com",
    'password' => Hash::make('password'),
])
->save();
dump($user->wasRecentlyCreated); // true
```


### save() のとき(更新)

* [Illuminate/Database/Eloquent/Model@save](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Eloquent/Model.php#L1112)


```php
use App\Models\User;
use Illuminate\Support\Str;

$user = User::first();
$user->fill([
    'name' => Str::random(),,
])
->save();
dump($user->wasRecentlyCreated); // false
```

なお、update() も中身は save() なので、update() は省略。
(話がそれるが、処理を追うと create() も中身は save() であることが分かる)



## wasRecentlyCreated が true になるタイミングは？

wasRecentlyCreated のデフォルト値は false である。

```php:Illuminate/Database/Eloquent/Model
abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
    // 略

    /**
     * Indicates if the model was inserted during the object's lifecycle.
     *
     * @var bool
     */
    public $wasRecentlyCreated = false;
    // 略

}
```

Illuminate/Database/Eloquent/Model の performInsert() メソッド(DBにレコードを挿入する処理)の中でのみ、wasRecentlyCreated へ true を代入する処理が行われる。
これは、「オブジェクトのライフサイクル中にモデルが INSERT されたかどうかを示す」という上記のコメントの記述や当記事での動作確認の結果と一致する。


```php:Illuminate/Database/Eloquent/Model@performInsert
    /**
     * Perform a model insert operation.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return bool
     */
    protected function performInsert(Builder $query)
    {
        // 略

        // We will go ahead and set the exists property to true, so that it is set when
        // the created event is fired, just in case the developer tries to update it
        // during the event. This will allow them to do so and run an update here.
        $this->exists = true;

        // DBへレコードを挿入したときのみ true
        // DB からレコードを取得したときやModel のインスタンスを作成した直後(DBにレコードがない)ときは false
        $this->wasRecentlyCreated = true;

        $this->fireModelEvent('created', false);

        return true;
    }

```

ちなみに、wasRecentlyCreated が true になるのと同じタイミングで以下の処理も行われることが確認できる。

* exists プロパティ(Modelクラスのインスタンスと対応するレコードが存在するかどうかを示す)が true になる
* created イベント(レコードを新規作成した直後のイベント)の発火


## まとめ
* wasRecentlyCreated が true になるのは、DBにレコードを挿入した直後(Illuminate/Database/Eloquent/Model の performInsert() メソッドを呼び出したとき)である
* 「DBのレコードが今まさに作られたとき」にのみ何らかの処理を行いたい、というときに便利
    * 以下のような感じ

```php
use App\Models\User;

$user = User::firstOrCreate([...]);
if($user->wasRecentlyCreated) {
    // 新規登録時にのみ行いたい処理を記述
}
```


## 今回のコード
* [https://github.com/orange-alien/laravel-blog/pull/3](https://github.com/orange-alien/laravel-blog/pull/3)


## 参考

* [laravel/framework](https://github.com/laravel/framework)
    * [Illuminate/Database/Eloquent/Model](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Eloquent/Model.php)
    * [Illuminate/Database/Eloquent/Builder](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Eloquent/Builder.php)
    * [Illuminate/Database/Concerns/BuildsQueries](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Concerns/BuildsQueries.php)