Yii2基本概念之——屬性(property)

學習任何一門學問,往往都是從起基本的概念學起。萬丈高樓平地起,這些基本概念就是高樓的基石,必須做詳盡的分析。我們知道,Yii2是一款脈絡清晰的框架,理順了基礎的概念和基本功能,學習更高級和複雜的功能就容易多了。Yii2是一款純面向對象的框架,它對類的功能做了擴充:PHP類的功能分爲屬性和方法,而Yii2定義了類的三個功能:屬性(property),行爲(behavior)和事件(event)。

爲了更好的實現面向對象的編程,拿到一個現實的對象,要構造一個PHP對象與之對應,如果用Yii2框架去實現,那麼首先要想到的是這個對象有哪些屬性,哪些行爲,哪些事件。

今天先來說說屬性的概念。

我們舉個例子,我們需要對$user對象的name屬性做trim操作,那麼我們首先想到這麼做:

// $user is a instance of User
$user->name = trim($name);

然而,這樣做有個問題,就是如果我們需要對所有地方的User實例的name屬性都要進行trim操作,我們就需要改動多處;另外,假如哪一天我不僅要trim操作,還要首字母大寫,那我還得改動很多地方。萬一我遺漏了怎麼辦?

爲了解決這個問題,Yii2引入了自己的屬性概念。在yii\base\BaseObject中實現了。

注:Yii2.0.12之前是yii\base\Object,Yii2.0.13及以後,爲了考慮到”object”將在PHP7.2版本成爲受保留的專屬名詞,改爲了yii\base\BaseObject

屬性和成員變量

在 PHP 中,類的成員變量和屬性既有區別,又有聯繫,從訪問形式來看看,二者沒有什麼區別,但是,成員變量是就類的結構而言的概念,而屬性是就類的功能邏輯而言的概念。

在具體實踐中,常常會想用一個稍微特殊些的方法實現屬性的讀寫。這就需要用到魔術方法讀取器getter(),和設定器setter()。這兩個方法提供了這樣一種便利:對類的屬性進行某種加工之後再寫,對類的屬性進行某種加工之後再讀(預處理和後處理)。

成員變量和屬性的區別與聯繫在於:

  1. 成員變量是一個“內”概念,反映的是類的結構構成,與其相對應的是成員函數(類方法)。屬性是一個“外”概念,反映的是類的邏輯意義
  2. 成員變量沒有讀寫權限控制,而屬性可以指定爲只讀或只寫,或可讀可寫,這就有了權限這麼一說
  3. 成員變量不對讀出作任何後處理,不對寫入作任何預處理,而屬性則可以
  4. public成員變量可以視爲一個可讀可寫、沒有任何預處理或後處理的屬性。 而private成員變量由於外部不可見,與屬性“外”的特性不相符,所以不能視爲屬性。同樣的,protect屬性也不能視爲屬性
  5. 雖然大多數情況下,屬性要由成員變量來實現,但是二者並沒有必然的關係
    在Yii中,由 yii\base\BaseObject 提供了對屬性的支持,因此,如果要使你的類支持屬性,只需要繼承此類即可。

getter 和 setter 方法

屬性是通過getter方法和setter方法來定義的。getter 方法是名稱以 get 開頭的方法,而 setter 方法名以 set 開頭,分別是對魔術方法getter()setter()的進一步封裝。

getter方法

public function __get($name)
{
    $getter = 'get' . $name;
    if (method_exists($this, $getter)) {
        return $this->$getter();
    } elseif (method_exists($this, 'set' . $name)) {
        ....
    }

    ....
}

setter方法

public function __set($name, $value)
{
    $setter = 'set' . $name;
    if (method_exists($this, $setter)) {
        $this->$setter($value);
    } elseif (method_exists($this, 'get' . $name)) {
        ...
    } else {
        ...
    }
}

方法名中 get 或 set 後面的部分就定義了該屬性的名字(get或者set後面的部分就是要加工的類的屬性)。 如下面代碼所示,getter 方法 getName() 和 setter 方法 setName() 操作的是 name 屬性:

namespace app\components;
use yii\base\BaseObject ;
class User extend  BaseObject 
{
    private $_name;

    public function getName()  // 讀操作
    {
        return $this->_name;  // 讀之前可以做後處理
    }

    public function setName($value)  // 寫操作
    {
        $this->_name = trim($value); // 寫之前可以做預處理
    }
}

小編心得:這種對某種操作的“預處理”和“後處理”在框架中到處可見,雖說還算不上一種設計思想,但是作爲一種技巧,可是大大提高了程序的可擴展性!

getter和setter方法創建了一個名爲name的屬性。在這個例子裏,它指向了一個私有成員變量$_name。getter和setter定義的屬性和類的成員變量一樣。二者的的主要區別在於:當這種屬性被讀取時,對應的 getter 方法將被調用; 而當屬性被賦值時,對應的 setter 方法就調用。如:

// 等效於$name = $user->getName();
$name = $user->name;

// $user = $user->setName('Jason');
$user->name = 'Jason';

如果我們像這樣去定義“屬性”,而不是簡單粗暴的定義一個名爲publice $name的成員變量,那麼就可以實現在任何地方,對User的實例的name屬性進行trim操作,不用擔心有漏網之魚。同樣,有了setter函數,我們如果還可以方便的再加上“首字母大寫”的操作:

public function setName($value)  
{
    $this->_name = ucfirst(trim($value));
}

同樣,在屬性讀之後的自定義處理:

public function getName() 
{
    return ucfirst($this->_name); 
}

或者結合更多成員變量做更爲複雜的處理:

public function getName() 
{
    return ucfirst($this->_firstname).' '.ucfirst($this->_lastname); 
}

只讀屬性和只寫屬性

只定義了 getter 沒有 setter 的屬性是隻讀屬性。 嘗試賦值給這樣的屬性將導致 yii\base\InvalidCallException (無效調用)異常。 類似的,只有 setter 方法而沒有 getter 方法定義的屬性是隻寫屬性,嘗試讀取這種屬性也會觸發相同異常,只不過只寫屬性的在現實應用中幾乎沒有。

通過 getter 和 setter 定義的屬性也有一些特殊規則和限制:

  • 這類屬性的名字是不區分大小寫的。如$user->name$user->Name是同一個屬性。 因爲PHP方法名是不區分大小寫的。
  • 如果此類屬性名和類public成員變量相同,以後者爲準。比如$user有age屬性(即擁有setter或getter方法),同時也擁有public $age成員變量。那麼無論在何種情況下只能用到成員變量public $age,不會用到作爲屬性的age。這很好理解,因爲setter和getter都是魔術方法,只在被操作的成員變量不存在時調用,現在成員變量age存在了,那無論是$age->age的讀操作還是$age->age = xxx的寫操作都只會直接調用public $age了。
  • 屬性不支持可見性(訪問限制),無論setter還是getter方法都只能是public的。定義爲private和protected有違初衷。
    這類屬性的 getter 和 setter 方法只能定義爲非靜態的,若定義爲靜態方法(static)則不會以相同方式處理。
  • 既然Yii2屬性已然不同於PHP的成員變量,那麼property_exists()方法就不適宜用來判斷yii\base\BaseObject及其子類的屬性是否存在,而應該改用yii\base\BaseObject的hasProperty()/canGetProperty() /canSetProperty()等方法。

屬性的實現步驟

下面幾步可以實現屬性:

  1. 繼承自 yii\base\BaseObject,如User
  2. 聲明一個用於保存該屬性的私有成員變量,如$_name,$_age
  3. 提供getter或setter函數,或兩者都提供,用於訪問、修改上面提到的私有成員變量。如果只提供了getter,那麼該屬性爲只讀屬性,只提供了setter,則爲只寫屬性。
class User extend  BaseObject // 1.繼承yii\base\BaseObject
{
private $_name;           // 2.聲明一個用於保存該屬性的私有成員變量

public function getName() // 3.提供一個getter或者setter
{
    return $this->_name;  
}

public function setName($value) 
{
    $this->_name = trim($value); 
}
}

小編心得:成員變量對外不可見是比較好的編程習慣。將成員變量的讀寫操作分開比合在一起方便。

BaseObject中屬性的其他方法

hasProperty()

測試一個類是否存在某種屬性:

public function hasProperty($name, $checkVars = true)
{
    return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
}

即定義了getter或setter,如果$checkVars爲true,那麼類如有同名的成員變量(public/protected/private)也會任何屬性存在。

canGetProperty()

測試某個屬性是否可讀:

public function canGetProperty($name, $checkVars = true)
{
    return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
}

$checkVars意義同上。

canSetProperty()

測試一個屬性是否可寫:

public function canSetProperty($name, $checkVars = true)
{
    return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
}

$checkVars意義同上,只要類定義了成員變量,不管是public還是private 還是 protected, 都認爲是可寫。

當然,gettter()和setter()方法是在遍歷所有成員變量,找不到所需的時候才調用的,因此屬性天生效率就要低一些。在一些類功能簡單,表示數據結構,數據集合時且不需要讀寫控制時,可以考慮直接使用成員變量作爲屬性。
另外,我們可以看到,在框架內部,幾乎都是使用$response = $app->getResponse()來代替$response = $app->response; 用$app->setRequest(xxx)代替$app->request = xxx 的情況,幾乎從來看不到直接對屬性賦值的情況(如後者),從而避免對成員變量的遍歷——框架自身還是格外地注重效率的,至於便利性,則留給了開發者啦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章