Yii2之行為


  Yii三大特性:屬性、事件、行為。前面兩篇文章已經分別講解了屬性和事件,本文接着講講yii的行為,分析yii行為的實現原理。

   yii中,一個對象綁定了行為之后,就擁有了所綁定行為擁有的所有事件,而且可以訪問所綁定行為的成員變量,調用其行為方法。那么,yii是怎么做到的呢?

  Yii中行為的實現需要yii\base\Componentyii\base\Behavior這兩個類的交互與配合,其中Component是組件類,Behavior是行為類。Component這個類在上一篇文章中講解事件的時候已經了解了它的事件機制的實現原理,本文將解析它是如何實現行為支持的,不過在這之前先來了解一下Behavior這個行為類。話不多說,先上yii\base\Behavior類源碼:

class Behavior extends Object
{
	/**
	 * 當前行為的擁有對象,一般為Component對象
	 * @var type 
	 */
    public $owner;

	/**
	 * 返回當前行為類擁有的所有事件,這些事件將被attach()方法綁定到行為對象的擁有者。
	 * 配置格式:事件名稱 => 事件處理器
	 * 事件處理器的配置有4種格式:
	 *                  1.當前類成員方法名稱,字符串形式,相當於 [$this, '成員方法名稱']
     *                  2.[類名, 方法名],數組形式
     *                  3.[對象, 方法名],數組形式
     *                  4.匿名函數,形式:function($event){ ... }
	 * @return type
	 */
    public function events()
    {
        return [];
    }

	/**
	 * 綁定行為到組件
	 * @param type $owner
	 */
    public function attach($owner)
    {
        $this->owner = $owner;
        foreach ($this->events() as $event => $handler) {//遍歷行為的事件列表,調用組件對象的on()方法把所有事件都綁定到組件上
            $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
        }
    }

    /**
     * 從組件上解綁行為
     */
    public function detach()
    {
        if ($this->owner) {
            foreach ($this->events() as $event => $handler) {//遍歷行為的事件列表,調用組件對象的off()方法把所有事件從組件解綁
                $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
            }
            $this->owner = null;
        }
    }
}

  可以看到yii\base\Behavior行為類其實很簡單,只有一個成員變量和三個成員方法。成員變量$owner用於保存綁定當前行為的對象,這個對象所屬類應該支持事件與行為,在yii中一般為Component組件類或其子類的對象,因為綁定/解綁行為的時候需要通過這個對象調用其所屬類的on()/off()方法來綁定/解綁事件。成員方法events()則用於配置當前行為類所擁有的所有事件,事件處理器有4種表示方法,具體已在上面源碼注釋中注明。attach()detach()兩個方法分別用於綁定和解綁行為,它們分別調用$owner對象的on()off()方法將當前行為類擁有的全部事件綁定到$owner對象或從$owner對象解綁。

  我們知道,當一個Component組件對象要綁定一個Behavior行為的時候,其實主動方是這個Component組件對象,所以yii行為機制中行為的綁定和解綁都是由Component類發起的,下面就來看看Component是如何發起行為的綁定和解綁的。

        先來看看Component是如何綁定行為的。Component綁定行為有兩種方式:靜態綁定和動態綁定,首先來了解靜態綁定方法。Component類使用成員方法behaviors()來返回一個配置數組表示當前組件擁有的所有行為,使用ensureBehaviors()方法來確保這些行為都已綁定到組件上,只要訪問Component的成員方法,都會先去調用ensureBehaviors()方法,若發現這些行為尚未綁定則進行綁定,所以看起來behaviors()這里配置的行為是會自動綁定的,無需我們自己寫代碼去綁定,所以就稱之為靜態綁定。behaviors()中行為的表示方法有4中方式,這里直接上源碼,主要看注釋:

/**
 * 用於靜態綁定行為
 * 當訪問當前組件類的屬性、方法、事件的時候,都先會調用ensureBehaviors()確保這里配置的所有行為都已經綁定到組件中,
 * 若未綁定則一一進行綁定,這里返回的行為列表最后會保存在_behaviors成員變量中。
 * @return 靜態綁定到當前組件的所有行為的列表,這里的行為可以有4種表示形式:
 *			1. '行為類名稱':匿名行為,只有行為類的名稱
 *			2. '行為名稱' => '行為類名稱':命名行為,只有行為類名稱
 *			3. [//匿名行為,配置數組
 *					'class' => '行為類名稱',
 *					'屬性名1' => '屬性值1',
 *					'屬性名2' => '屬性值2'
 *			   ],
 *			4. '行為名稱' => [//命名行為,配置數組
 *					'class' => '行為類名稱',
 *					'屬性名1' => '屬性值1',
 *					'屬性名2' => '屬性值2'
 *			   ]
 */
public function behaviors()
{
	return [];
}

public function ensureBehaviors()
{
	if ($this->_behaviors === null) {
		$this->_behaviors = [];
		foreach ($this->behaviors() as $name => $behavior) {
			$this->attachBehaviorInternal($name, $behavior);
		}
	}
}

其中ensureBehaviors()方法又調用了attachBehaviorInternal()方法,這個稍后再講。

  動態綁定行為則使用的是attachBehavior()方法,該方法源碼如下:

/**
 * 為組件綁定一個行為
 * @param type $name:行為名稱
 * @param type $behavior:行為配置,可以是一個對象,一個行為類名,或者一個行為類對象的配置數組
 * @return type
 */
public function attachBehavior($name, $behavior)
{
	$this->ensureBehaviors();
	return $this->attachBehaviorInternal($name, $behavior);
}

  我們發現,不管是靜態綁定還是動態綁定,最后都調用的是attachBehaviorInternal()方法,那么這個方法到底是干嘛的呢?先看源碼:

/**
 * 為組件綁定一個行為
 * @param type $name:行為名稱,若是數字,表示匿名行為
 * @param type $behavior:行為配置,可以是一個對象,一個行為類名,或者一個行為類對象的配置數組
 * @return type
 */
private function attachBehaviorInternal($name, $behavior)
{
	if (!($behavior instanceof Behavior)) {//$behavior不是行為類對象,先根據配置創建一個對象
		$behavior = Yii::createObject($behavior);
	}
	if (is_int($name)) {//匿名行為:直接綁定
		$behavior->attach($this);
		$this->_behaviors[] = $behavior;
	} else {//命名行為
		if (isset($this->_behaviors[$name])) {//存在同名行為:先解綁原有同名行為
			$this->_behaviors[$name]->detach();
		}
		$behavior->attach($this);//調用行為類對象的attach()方法,此方法將會把行為類對象的所有事件綁定到當前組件對象
		$this->_behaviors[$name] = $behavior;
	}

	return $behavior;
}

  可以看到attachBehaviorInternal()方法是負責和Behavior交互的,最終調用的是Behavior類的attach()方法,上面介紹Behavior的時候已經知道attach()會調用Component類的on()方法進行事件綁定,所以,綁定了行為之后,Component就擁有了這個行為所有的事件。另外,不管是靜態綁定的事件還是動態綁定的,最終Component所擁有的行為都將保存在其成員變量_behaviors中。

   高度總結一下,Component綁定Behavior的過程其實是很簡單的事情,只有兩個步驟:

1. Component調用Behaviorattach()方法,將自身對象作為參數傳遞過去。

2. Behavior通過Component傳遞過去的對象調用Componenton()方法進行事件綁定。

 

   講完了Component綁定行為,接下來看看Component是如何解綁行為的。Component解綁行為的方法是detachBehavior(),源碼如下:

/**
 * 從組件解綁一個行為
 * @param type $name:行為名稱
 * @return null或解綁的行為對象
 */
public function detachBehavior($name)
{
	$this->ensureBehaviors();
	if (isset($this->_behaviors[$name])) {
		$behavior = $this->_behaviors[$name];
		unset($this->_behaviors[$name]);//從組件的行為列表中刪除
		$behavior->detach();//調用行為對象的detach()方法,此方法將解綁此行為綁定在組件上的所有事件
		return $behavior;
	}

	return null;
}

  該方法最終調用Behaviordetach()方法,由於行為綁定的時候Behavior類的attach()方法中已經保存了行為的綁定者owner,所以解綁行為的

時候不需要傳參了。在Componentdetach()方法中則調用了Component類的off()方法,將行為的所有事件從組件上解綁。

 

  這樣就把yii行為的綁定和解綁原理解析完畢了。然而,文章開頭說了,一個對象綁定了行為之后,不但擁有所綁定行為擁有的所有事件,而且還可以訪問所綁定行為的成員變量,調用其行為方法。通過上面的講解,我們知道Component確實擁有了Behavior的所有事件,但是沒看出來Component可以訪問Behavior的成員變量和方法啊。這就是魔術方法__get()__set()以及__call()的功勞了,Component類重載了這三個方法,源碼如下:

/**
 * 重載PHP魔術方法__get()支持行為
 * @param type $name:屬性名稱
 * @return type
 * @throws InvalidCallException
 * @throws UnknownPropertyException
 */
public function __get($name)
{
	$getter = 'get' . $name;
	if (method_exists($this, $getter)) {//當前對象存在$name屬性的getter方法:直接調用
		return $this->$getter();
	}

	$this->ensureBehaviors();
	foreach ($this->_behaviors as $behavior) {//遍歷當前對象已綁定的所有行為,若行為對象中存在$name屬性且可讀則返回
		if ($behavior->canGetProperty($name)) {
			return $behavior->$name;
		}
	}

	if (method_exists($this, 'set' . $name)) {
		throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
	}

	throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}

/**
 * 重載PHP魔術方法__set()支持行為
 * @param type $name:屬性名稱
 * @param \yii\base\Behavior $value
 * @return type
 * @throws InvalidCallException
 * @throws UnknownPropertyException
 */
public function __set($name, $value)
{
	$setter = 'set' . $name;
	if (method_exists($this, $setter)) {//當前對象存在$name屬性的setter方法:直接調用
		$this->$setter($value);
		return;
	} elseif (strncmp($name, 'on ', 3) === 0) {//$name以'on '開頭,表示給當前對象的某個事件綁定處理器
		$this->on(trim(substr($name, 3)), $value);
		return;
	} elseif (strncmp($name, 'as ', 3) === 0) {//$name以'as '開頭,表示給當前對象綁定行為
		$name = trim(substr($name, 3));
		$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
		return;
	}

	$this->ensureBehaviors();
	foreach ($this->_behaviors as $behavior) {//遍歷當前對象已綁定的所有行為,若行為對象中存在$name屬性且可寫則設置值
		if ($behavior->canSetProperty($name)) {
			$behavior->$name = $value;
			return;
		}
	}

	if (method_exists($this, 'get' . $name)) {
		throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
	}

	throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}

/**
 * 重寫PHP魔術方法__call()支持行為,
 * @param type $name:方法名稱
 * @param type $params:傳遞給方法的參數
 * @return type
 * @throws UnknownMethodException
 */
public function __call($name, $params)
{
	$this->ensureBehaviors();
	foreach ($this->_behaviors as $object) {//遍歷組件綁定的所有行為,若某個行為對象中存在$name成員方法則調用之
		if ($object->hasMethod($name)) {
			return call_user_func_array([$object, $name], $params);
		}
	}
	throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
}

這樣Component綁定了Behavior之后就可以訪問Behavior的成員變量,調用Behavior的成員方法了!

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM