對於魔術方法__call,__callStatic 新的認識

寫PHP的老王發表於2019-10-13

誤解的一般解釋

__call方法在物件方法不存在的時候被呼叫

__callStatic方法在呼叫物件靜態方法不存在的時候被呼叫

例如


class Car{
    public function __call($method,$params=[]){
        echo "car call\n";
    }
}

(new Car())->color();

class Bus{
    public static function __callStatic($method,$params=[]){
        echo "Bus callStatic\n";
    }
}

Bus::isSale();

特殊情況

其實上面的解釋在某些情況下是正確的。但是在一些特殊情形,如果按照這個解釋來理解,就會覺得結果不可思議了。

以下面幾個例子進行說明。

__call的呼叫關注的是方法能不能被訪問

class Car{
    public function __call($method,$params=[]){
        echo "car call\n";
    }

    public function color(){
        echo "color red\n";
    }

    protected function isRed(){
        echo "yes is Red\n";
    }

    public function checkColor(){
        $this->color();
        $this->isRed();
    }
}
$car = new Car();
$car->color();
$car->isRed();

$car->checkColor();

輸出的結果是

color red
car call isRed
color red
yes is Red

從上面可以看出,其實是否呼叫__call,依賴的是當前呼叫方能否訪問到要呼叫的函式,如果可以訪問到,則直接呼叫函式,如果不能訪問到,則呼叫魔術方法__call。所以,呼叫與否關注的是可訪問性。

__callStatic關注的是方法能否被靜態的方式訪問

接下來看另外一個靜態呼叫的例子

class Car{
    public static function __callStatic($method,$params=[]){
        echo "car callStatic\n";
    }

    public function color(){
        echo "color red\n";
    }

    protected function isRed(){
        echo "yes is Red\n";
    }

    public function checkColor(){
        Car::color();
        Car::isRed();
    }
}

Car::color();
Car::isRed();
(new Car())->checkColor();

輸出內容是

color red
car callStatic isRed
color red
yes is Red

並且在外部以靜態方式呼叫Car::color伴有Notice級別錯誤提示,但是內部呼叫是沒有的。

所以,__callStatic關注的是函式在呼叫位置能否被靜態的方式訪問到。如果能訪問到,則直接執行該方法。如果不能則執行__callStatic方法

__call 與__callStatic同時存在的情況

方法不可訪問的時候,具體呼叫__call,__callStatic方法,依據的並不是呼叫方式是否是靜態呼叫,而是所在的上下文。如果上下文是在可訪問呼叫物件的物件裡,則呼叫__call,在靜態上下文中呼叫一個不可訪問方法時,呼叫__callStatic

class Car{
    public static function __callStatic($method,$params=[]){
        echo "car callStatic $method\n";
    }

    public  function __call($method,$params=[]){
        echo "car call $method\n";
    }

    public function checkColor(){
        Car::color();
        Car::isRed();
    }
}

$car = new Car();
Car::color();
Car::isRed();

$car->color();
$car->isRed();

(new Car())->checkColor();

輸出內容是

car callStatic color
car callStatic isRed
car call color
car call isRed
car call color
car call isRed

從結果看出,外部靜態呼叫color,isRed方法,上下文是靜態方式,所以執行的是__callStatic

而在checkColor方法中,呼叫的上下文處於當前類物件Car當中,即使是以靜態方式呼叫color,isRed,最終執行的是__call方法。

總結

1)__call方法關注方法能否被訪問到,而不僅僅是關注是否存在

2)__callStatic方法關注的是方法能否被靜態的訪問到,而不是關注方法是否存在,是否是靜態方法。

3)具體執行__call,__callStatic,是根據呼叫的上下文。如果處於靜態上下文內,則呼叫__callStatic。如果處於物件的上線文內,則呼叫__call

文章首發於公眾號【寫PHP的老王】,轉載註明出處

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

相關文章