概要
LaravelのModelクラスに実装されているtoArrayメソッドの挙動が気になったので、元のコードを読んで仕様を噛み砕いていきます。
環境
Laravel v6.20.26
実際のコード
以下のコードが主となっています。
コメントも書いてあるのでさらにジャンプしたコードについては記載しません
呼び出し
// Illuminate\Database\Eloquent\Model;
public function toArray()
{
return array_merge($this->attributesToArray(), $this->relationsToArray());
}
attributesの変換
前提として、LaravelのModelは内部にattributesという項目をもち、一般的なphpのclassのプロパティとは違う形で管理しています。
getXXAtributes
系のメソッドはそのattributesから条件にあった項目を取り出しています。
以下の流れで取得・変換を行なっています。配列$attributes
に全て追加・変更しています。
-
$dates
で定義したattributeの変換 - ミューテタ(
get{アトリビュート名}Attribute
で定義してプロパティのように扱えるやつ)の取得・変換($this->addMutatedAttributesToArray) -
$casts
で定義された項目の変換。ミューテタが定義されているものはキャストされません。ひっかかるケースありそう。 -
$appends
で定義された追加された項目の取得。
// => 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
をトリガーとしてロードが行われます。
流れ的には以下になります。
- Arrayableなrelationsを取得し、内容が条件が合致していれば
$attributes
に追加していく-
$value
の追加条件- Arrayableなインスタンスの場合 => 配列に変換して追加
- nullの場合 => 追加しない
-
$snakeAttributes
の設定によっては項目名をスネークケースに変換(デフォルトはtrue)
-
// => 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メソッドを使用しますが、エラー対応等で苦戦したので自分用にまとめております。
誰かのお役に立てば幸いです。