livewireのpublic変数にModelを入れるとIDが照合される問題

タイトルは厳密には間違っています。
どちらかというと、ComponentがJSと裏側でやり取りしているデータをModelとしてリストアする際、「id」というキーを元にDBから再取得をしていることで生じる問題です。

## 例
例えばEC系のシステムで、商品規格IDをフォームに入力すると該当する情報を閲覧できる機能をLivewireで作ったとします。

テーブルと商品データはおおまかに下記のようなものとし、
**products（商品テーブル）**
| id | name |
| --- | --- |
| 1 | 商品１ |
| 2 | 商品２ |

**product_classes（商品規格テーブル）**
| id | product_id | color | price |
| --- | --- | --- | --- |
| 1 | 1 | black | 100 |
| 2 | 2 | black | 150 |
| 3 | 2 | white | 200 |

また、Componentとbladeはそれぞれ↓のように作成しました。

```php:Item.php（Component）
<?php

namespace App\Http\Livewire;

use App\Models\Product;
use App\Models\ProductClass;

class Item extends \Livewire\Component
{
    public $product_class_id = ''; // 商品規格ID
    public $product; // 商品データ

    // product_class_id更新時の処理
    public function updatedProductClassId()
    {
        $this->product = Product::join('product_classes','products.id','=','product_classes.product_id')
            ->where( 'product_classes.id', $this->product_class_id )
            ->first();
    }

    public function render()
    {
        return view('product');
    }
}
```

```html:product.blade.php（blade）
<div>
	<input wire:model="product_class_id" type="text">
	<div>name : {{ $product?->name }}</div>
	<div>color : {{ $product?->color }}</div>
	<div>price : {{ $product?->price }}</div>
</div>
```

これで、下記のような動作が実装できました。
[![Image from Gyazo](https://i.gyazo.com/9cf4bde32331695218cfe533cd38ab23.gif)](https://gyazo.com/9cf4bde32331695218cfe533cd38ab23)

## ↑の問題点
これだけ見ると問題のないようにも思えますが、上記の場合、フォームに「3」を入力した以降にフォームの値が更新されると404が返ってくるようになってしまいます。
[![Image from Gyazo](https://i.gyazo.com/ab9440b2aca0835f934ca95ab85b92a1.gif)](https://gyazo.com/ab9440b2aca0835f934ca95ab85b92a1)

## 404の原因
これは冒頭で書いた、Component<->JS間でやり取りしているデータをLivewireがModelへリストアする際に発生します。
今回の場合、`$product`に入っているModelは`App\Models\Product`なのに対し、
idに入っている値はjoinによって別テーブルから上書きされて3になっています。

```php
App\Models\Product {#1334 ▼
~~~
  #attributes: array:7 [▼
    "id" => 3 // ← productsテーブルにidが3のレコードは存在しない
    "name" => "商品２"
    "product_id" => 2
    "color" => "white"
    "price" => 200
  ]
~~~
}
```

Livewireは上記データを元にproductsテーブルからidが3のデータを`firstOrFail()`で取得しようとするも、見つからずに`ModelNotFoundException`の例外が投げられるため404が返されることになります。

やり取りしているデータ形式の判別やModelへの復元は下記で行っているみたいです。（多分）

[HydratePublicProperties::hydrate()](https://github.com/livewire/livewire/blob/master/src/HydrationMiddleware/HydratePublicProperties.php#L29)
↓
[HydratePublicProperties::hydrateModel()](https://github.com/livewire/livewire/blob/master/src/HydrationMiddleware/HydratePublicProperties.php#L140)
↓
[SerializesAndRestoresModelIdentifiers::getRestoredPropertyValue()](https://github.com/laravel/framework/blob/9.x/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php#L53)
↓
[SerializesAndRestoresModelIdentifiers::restoreModel()](https://github.com/laravel/framework/blob/9.x/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php#L104)　←`firstOrFail()`はこの中


## 回避策
コメントでいただきました。
感謝(*´∀人)
https://qiita.com/ast_and_kaya/items/519d4a43a769b9385b7b#comment-caea2287bfb3ef43a726


## おまけ

https://github.com/laravel/framework/blob/9.x/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php#L104

104行目～の処理って↓みたいにしてnullを返すと何か問題あるんですかね？
わけのわからない404は回避出来ても、結局Modelへの復元は仕組みとして出来なさそう……？
```diff_php
    public function restoreModel($value)
    {
        return $this->getQueryForModelRestoration(
            (new $value->class)->setConnection($value->connection), $value->id
-        )->useWritePdo()->firstOrFail()->load($value->relations ?? []);
+        )->useWritePdo()->first()?->load($value->relations ?? []);
    }
```