LaravelのpreventAccessingMissingAttributesの挙動+おまけ

preventAccessingMissingAttributesの挙動

Laravel の Eloquent を利用してデータを取得する場合、特に列の指定がなければすべての列情報を取得してきます。

$user = User::find(1);

// App\Models\User {
//    id: 5,
//    name: "kiichiro",
//    email: "kiichiro@example.com",
//    email_verified_at: "2024-05-28 06:44:34",
//    password: "$2y$12$jJuJzdMPZ/gSwVSp3DzNE.1SwACJGqPLsWLWP3HGUZRhP1Q/9zJs2",
//    remember_token: "KjmFVc1rUZ",
//    created_at: "2024-05-28 06:44:35",
//    updated_at: "2024-05-28 06:51:32",
// }

列の指定があれば指定された列情報のみを取得します。

$user = User::select(['id', 'name'])->find(1);

// App\Models\User {
//     id: 1,
//     name: "kiichiro",
// }

このとき、取得しなかったプロパティへのアクセスが発生すると、null を返します。

$user->email; // null

この挙動故に、アクセスしたプロパティをそもそも取得していないのか、取得した上で null なのかが判断出来ず、バグになってしまうのはよくある話ですね。

これを解決するには AppServiceProviderModel::preventAccessingMissingAttributes を呼び出すと良いらしいです。

public function boot(): void
{
    Model::preventAccessingMissingAttributes();
}

取得していないプロパティへのアクセスが発生した場合は MissingAttributeException を投げてくれます。

$user = User::select(['id', 'name'])->find(1);
$user->email;

// Illuminate\Database\Eloquent\MissingAttributeException  The attribute [email] either does not exist or was not retrieved for model [App\Models\User].

ただし、列情報を取得していなくても動的プロパティとしてセットしたり、fill でプロパティを埋めた場合はこの例外は投げられません。

$user = User::select(['id', 'name'])->find(1);
$user->email = 'kiichiro@example.com';
$user->email;
// 'kiichiro@example.com'

$user = User::select(['id', 'name'])->find(1);
$user->fill(['email' => 'kiichiro@example.com']);
$user->email;
// 'kiichiro@example.com'

プロパティへのアクセスは基本的に attributes という配列のキーに相当するものがあるかどうかを判別しています。判別の結果、attributes になければ MissingAttributeException を投げ、attributes にあればその値を返します。よって、動的プロパティとしてセットした場合は attributes にあるので値がそのまま返ってくるということのようです。

/**
 * Dynamically retrieve attributes on the model.
 *
 * @param  string  $key
 * @return mixed
 */
public function __get($key)
{
    return $this->getAttribute($key);
}

/**
 * Get an attribute from the model.
 *
 * @param  string  $key
 * @return mixed
 */
public function getAttribute($key)
{
    if (! $key) {
        return;
    }

    // If the attribute exists in the attribute array or has a "get" mutator we will
    // get the attribute's value. Otherwise, we will proceed as if the developers
    // are asking for a relationship's value. This covers both types of values.
    if ($this->hasAttribute($key)) {
        return $this->getAttributeValue($key);
    }

    // Here we will determine if the model base class itself contains this given key
    // since we don't want to treat any of those methods as relationships because
    // they are all intended as helper methods and none of these are relations.
    if (method_exists(self::class, $key)) {
        return $this->throwMissingAttributeExceptionIfApplicable($key);
    }

    return $this->isRelation($key) || $this->relationLoaded($key)
                ? $this->getRelationValue($key)
                : $this->throwMissingAttributeExceptionIfApplicable($key);
}

/**
 * Determine whether an attribute exists on the model.
 *
 * @param  string  $key
 * @return bool
 */
public function hasAttribute($key)
{
    if (! $key) {
        return false;
    }

    return array_key_exists($key, $this->attributes) ||
        array_key_exists($key, $this->casts) ||
        $this->hasGetMutator($key) ||
        $this->hasAttributeMutator($key) ||
        $this->isClassCastable($key);
}

おまけ

preventAccessingMissingAttributes は Laravel 9系で実装された機能なので、Laravel 9 の公式ドキュメントには記載があるのですが、なぜか10以降のドキュメントから削除されていました。

Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...
Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...

どうやら以下のコミットで削除されたようです。

wip · laravel/docs@616edf4
The Laravel documentation. Contribute to laravel/docs development by creating an account on GitHub.

Eloquent ORM の機能を制限するガードレールとして設定したいという需要がありそうなので、ドキュメントの削除はミスかなと思ったのですが、同じようなことを思った人が既にいたようでプルリクエストが上がっていました。

[10.x] Adding Missing Content of `Configuring Eloquent Strictness` by devajmeireles · Pull Request #9260 · laravel/docs
I used 9.x as a base to include the missing content in 10.x

コメントに削除した理由についての言及がありました。

I don’t really like this feature so I removed it from the docs.

https://github.com/laravel/docs/pull/9260#issuecomment-1892644460

まあ、好みじゃないならしょうがないですよね。。

タイトルとURLをコピーしました