1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Basic Study LogAdvent Calendar 2024

Day 13

Laravelのfillableって何で書くの?

Posted at

はじめに

何となくで使っているfillable

fill()を使う時に書く認識でしたが、かなり曖昧な理解だなと思ったので改めてどういう時に定義するのかを調べていきたいと思います。

なぜfillableを設定する必要があるのか?

結論から言うと、fillableを設定する理由は複数代入の脆弱性から守るためである。

例えば、usersテーブルに以下の5つのカラムがあったとする。

  • id
  • name
  • email
  • password
  • is_admin

フォームにはnameemailpasswordの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.phpcreate()が定義されている。

ここでの処理はモデルインスタンスを作成・保存をし、インスタンスを返している。

vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php
/*
 * 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()の定義を見てみると以下のようになっている。

ここでの処理は指定された属性を持つ新しいモデルインスタンスを作成し、そのインスタンスにクエリビルダが使用しているデータベース接続を設定して返している。

vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php
/*
 * 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()の定義を見てみると以下のようになっている。

vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
/*
 * 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に書かれた属性のみを抽出して返しているため、先述の脆弱性対策をすることが可能となる。

vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
/*
 * 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;
}
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php
/*
 * 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は悪意ある複数代入から守るために設定する必要がある。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?