はじめに
何となくで使っているfillable
。
fill()
を使う時に書く認識でしたが、かなり曖昧な理解だなと思ったので改めてどういう時に定義するのかを調べていきたいと思います。
なぜfillableを設定する必要があるのか?
結論から言うと、fillable
を設定する理由は複数代入の脆弱性から守るためである。
例えば、usersテーブルに以下の5つのカラムがあったとする。
id
name
email
password
is_admin
フォームにはname
、email
、password
の3つが入力できるようになっていて、保存処理として以下のようなコードを書いたとする。
もしリクエストの中にis_admin: true
のようなデータが入っていると、悪意あるユーザーが管理者になってしまう危険性がある。
public function store(Request $request)
{
$data = $request->all();
User::create($data);
}
ここで、fillable
に以下のように設定をしておくと、予期せぬデータが登録されることを防げる。
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
}
具体的に見てみる
とはいってもどうやってそれが実現しているのかがイメージがつかないので、クエリビルダのcreate()
を例に処理を追いつつ見ていく。
vendorの中身を探りに行くと、vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php
にcreate()
が定義されている。
ここでの処理はモデルインスタンスを作成・保存をし、インスタンスを返している。
/*
* Save a new model and return the instance.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model|$this
*/
public function create(array $attributes = [])
{
return tap($this->newModelInstance($attributes), function ($instance) {
$instance->save();
});
}
newModelInstance()
の定義を見てみると以下のようになっている。
ここでの処理は指定された属性を持つ新しいモデルインスタンスを作成し、そのインスタンスにクエリビルダが使用しているデータベース接続を設定して返している。
/*
* Create a new instance of the model being queried.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function newModelInstance($attributes = [])
{
return $this->model->newInstance($attributes)->setConnection(
$this->query->getConnection()->getName()
);
}
newInstance()
の定義を見てみると以下のようになっている。
/*
* Create a new Eloquent model instance.
*
* @param array $attributes
* @return void
*/
public function __construct(array $attributes = [])
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
~ 省略 ~
/*
* Create a new instance of the given model.
*
* @param array $attributes
* @param bool $exists
* @return static
*/
public function newInstance($attributes = [], $exists = false)
{
// This method just provides a convenient way for us to generate fresh model
// instances of this current model. It is particularly useful during the
// hydration of new objects via the Eloquent query builder instances.
$model = new static((array) $attributes);
$model->exists = $exists;
$model->setConnection(
$this->getConnectionName()
);
return $model;
}
このメソッド内にあるnew static((array) $attributes);
でfill()
が呼ばれている。
fill()
内で呼ばれている$this->fillableFromArray($attributes)
によって、fillable
に書かれた属性のみを抽出して返しているため、先述の脆弱性対策をすることが可能となる。
/*
* Fill the model with an array of attributes.
*
* @param array $attributes
* @return $this
*
* @throws \Illuminate\Database\Eloquent\MassAssignmentException
*/
public function fill(array $attributes)
{
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
$key = $this->removeTableFromKey($key);
// The developers may choose to place some attributes in the "fillable" array
// which means only those attributes may be set through mass assignment to
// the model, and all others will just get ignored for security reasons.
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException($key);
}
}
return $this;
}
/*
* Get the fillable attributes of a given array.
*
* @param array $attributes
* @return array
*/
protected function fillableFromArray(array $attributes)
{
if (count($this->getFillable()) > 0 && ! static::$unguarded) {
return array_intersect_key($attributes, array_flip($this->getFillable()));
}
return $attributes;
}
まとめ
fillable
は悪意ある複数代入から守るために設定する必要がある。