Laravel 動態屬性的實現

KevinYang發表於2018-04-13

什麼是動態屬性

假設我們有一個 User 模型:

class User extends Model
{
    /**
     * 獲取與使用者關聯的電話號碼記錄。
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }

    public function getFullNameAttribute()
    {
        return "{$this->first_name} {$this->last_name}";
    }
}
$user = User::find(1);
// 這種呼叫不存在的屬性就是 laravel 中的動態屬性
$user->full_name;
$user->phone

動態屬性的實現

1. 萬惡之源 __get()

PHP 的魔術方法 __get(),當讀取不可訪問屬性的值時,__get()會被呼叫。我們在在使用動態屬性時
都會觸發這個魔術方法。然後我順利的在 Eloquent\Model.php 中找到了這個這個方法:

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

2. getAttribute

繼續追蹤:

/**
     * 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 (array_key_exists($key, $this->attributes) ||
            $this->hasGetMutator($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;
        }

        return $this->getRelationValue($key);
    }

第二個 if 處理了model有這個attributemodel有對應訪問器的情況。

3. getRelationValue

繼續追蹤, 關聯關係的動態屬性用法:

/**
    * Get a relationship.
    *
    * @param  string  $key
    * @return mixed
    */
public function getRelationValue($key)
{
    // If the key already exists in the relationships array, it just means the
    // relationship has already been loaded, so we'll just return it out of
    // here because there is no need to query within the relations twice.
    if ($this->relationLoaded($key)) {
        return $this->relations[$key];
    }

    // If the "attribute" exists as a method on the model, we will just assume
    // it is a relationship and will load and return results from the query
    // and hydrate the relationship's value on the "relationships" array.
    if (method_exists($this, $key)) {
        return $this->getRelationshipFromMethod($key);
    }
}

這裡可以看到首先會讀一個關聯關係的快取,若沒有快取才會判斷是否存在和所呼叫屬性同名的方法,
如果存在則呼叫 getRelationshipFromMethod($key) 方法。

4. getRelationshipFromMethod($method)

protected function getRelationshipFromMethod($method)
    {
        $relations = $this->$method();

        if (! $relations instanceof Relation) {
            throw new LogicException('Relationship method must return an object of type '
                .'Illuminate\Database\Eloquent\Relations\Relation');
        }

        $this->setRelation($method, $results = $relations->getResults());

        return $results;
    }

setRelation 的意思是將沒有載入的 relation 進行載入,那麼下次需要時就可以在getRelationValue($key)的第一個 if 中即返回需要的結果。

此方法的返回值返回的是 Collection 型別,也就是說動態屬性訪問關聯模型返回的是Collection型別,而如果我們直接呼叫方法返回的則是 Relation 型別。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章