LaravelのtoArrayメソッドの実装を見てみる

## 概要
LaravelのModelクラスに実装されているtoArrayメソッドの挙動が気になったので、元のコードを読んで仕様を噛み砕いていきます。

## 環境
Laravel v6.20.26

## 実際のコード
以下のコードが主となっています。
コメントも書いてあるのでさらにジャンプしたコードについては記載しません

### 呼び出し
```php
// Illuminate\Database\Eloquent\Model;
public function toArray()
{
    return array_merge($this->attributesToArray(), $this->relationsToArray());
}
```

### attributesの変換
前提として、LaravelのModelは内部にattributesという項目をもち、一般的なphpのclassのプロパティとは違う形で管理しています。
`getXXAtributes`系のメソッドはそのattributesから条件にあった項目を取り出しています。

以下の流れで取得・変換を行なっています。配列`$attributes`に全て追加・変更しています。
1. `$dates`で定義したattributeの変換
1. ミューテタ(`get{アトリビュート名}Attribute`で定義してプロパティのように扱えるやつ)の取得・変換($this->addMutatedAttributesToArray)
1. `$casts`で定義された項目の変換。**ミューテタが定義されているものはキャストされません。ひっかかるケースありそう。**
1. `$appends`で定義された追加された項目の取得。

```php
// => Illuminate\Database\Eloquent\Concerns\HasAttributes;
/**
 * Convert the model's attributes to an array.
 *
 * @return array
 */
public function attributesToArray()
{
    // If an attribute is a date, we will cast it to a string after converting it
    // to a DateTime / Carbon instance. This is so we will get some consistent
    // formatting while accessing attributes vs. arraying / JSONing a model.
    $attributes = $this->addDateAttributesToArray(
        $attributes = $this->getArrayableAttributes()
    );

    $attributes = $this->addMutatedAttributesToArray(
        $attributes, $mutatedAttributes = $this->getMutatedAttributes()
    );

    // Next we will handle any casts that have been setup for this model and cast
    // the values to their appropriate type. If the attribute has a mutator we
    // will not perform the cast on those attributes to avoid any confusion.
    $attributes = $this->addCastAttributesToArray(
        $attributes, $mutatedAttributes
    );

    // Here we will grab all of the appended, calculated attributes to this model
    // as these attributes are not really in the attributes array, but are run
    // when we need to array or JSON the model for convenience to the coder.
    foreach ($this->getArrayableAppends() as $key) {
        $attributes[$key] = $this->mutateAttributeForArray($key, null);
    }

    return $attributes;
}
```

### relationsの変換
前提として、`hasOne`などの関連性を定義したメソッドは、内部的に`$relations`というプロパティで管理されています。`$this->relations`をトリガーとしてロードが行われます。

流れ的には以下になります。
1. Arrayableなrelationsを取得し、内容が条件が合致していれば`$attributes`に追加していく
    1. `$value`の追加条件
        - Arrayableなインスタンスの場合 => 配列に変換して追加
        - nullの場合 => 追加しない
    1. `$snakeAttributes`の設定によっては項目名をスネークケースに変換(デフォルトはtrue)

```php
// => Illuminate\Database\Eloquent\Concerns\HasAttributes;
/**
 * Get the model's relationships in array form.
 *
 * @return array
 */
public function relationsToArray()
{
    $attributes = [];

    foreach ($this->getArrayableRelations() as $key => $value) {
        // If the values implements the Arrayable interface we can just call this
        // toArray method on the instances which will convert both models and
        // collections to their proper array form and we'll set the values.
        if ($value instanceof Arrayable) {
            $relation = $value->toArray();
        }

        // If the value is null, we'll still go ahead and set it in this list of
        // attributes since null is used to represent empty relationships if
        // if it a has one or belongs to type relationships on the models.
        elseif (is_null($value)) {
            $relation = $value;
        }

        // If the relationships snake-casing is enabled, we will snake case this
        // key so that the relation attribute is snake cased in this returned
        // array to the developers, making this consistent with attributes.
        if (static::$snakeAttributes) {
            $key = Str::snake($key);
        }

        // If the relation value has been set, we will set it on this attributes
        // list for returning. If it was not arrayable or null, we'll not set
        // the value on the array because it is some type of invalid value.
        if (isset($relation) || is_null($value)) {
            $attributes[$key] = $relation;
        }

        unset($relation);
    }

    return $attributes;
}
```

## まとめ
基本的には通常の呼び出し時などと変わらずキャストも行い、ミューテタやリレーションも取得・変換してくれることがわかりました。
バルクインサートなどを行う際はtoArrayメソッドを使用しますが、エラー対応等で苦戦したので自分用にまとめております。
誰かのお役に立てば幸いです。