1. 程式人生 > >總結2: PHP中的魔術方法及其應用場景

總結2: PHP中的魔術方法及其應用場景

PHP中有一類特別的系統方法,它們統一以__開頭,使用語義清晰簡單,這類形式特殊、作用特殊的方法被稱為魔術方法。

常見的魔術方法有__construct()、__destruct()、__call()、__callStatic()、__get()、__set()、__isset()、__unset()、__sleep()、__wakeup()、__toString()、__invoke()、__set_state()、__clone()、__debugInfo(),這些魔術方法往往是成對出現的,下面把一對對魔術方法拿出來做對比分析。

1、__construct()和__desctuct()

這兩個魔術方法都是在PHP5中開始出現支援的函式。

__construct()為建構函式,如果一個類具有建構函式,則這個類的物件在進行例項化時都會先呼叫這個方法,非常適合用來做一些初始化的工作。比如我們上一講中的people類,還有印象嗎?我們是在物件產生後才對物件的屬性進行初始化,如下圖所示:

   $tom = new people();
   $tom->name = 'Tom';
   $tom->age = 18;
   $tom->sex = 'boy';

我們完全可以用建構函式在建立物件的時候完成這部分工作

<?php
  /**
   * Created by PhpStorm.
   * User: ahao
   * Date: 2016/11/9
   * Time: 23:25
   */
class people{ public $name; public $age; public $sex; public function __construct($name, $age, $sex){ $this->name = $name; $this->age = $age; $this->sex = $sex; echo "A people was born!"."<br/>"; } public function
description(){
echo $this->name," is a ",$this->age," years old ",$this->sex; } }

這樣,建立物件並完成初始化的程式碼可以簡化為

  $codeman = new people('codeman',22,'man');

當然,對物件資料屬性的初始化只是建構函式的一個應用,很多時候在執行一個類的所有方法之前都要進行某項操作或檢測,比如是否登入、是否達到許可權等級等,非常適合用建構函式來實現,程式碼清晰整潔。

__destruct()解構函式和建構函式相反,是在物件的所有引用都被刪除或者當物件被顯示銷燬時執行,主要是用來做一些記憶體資源等的釋放工作,具體的應用場景要看需求而言,實際應用並不是很多。

同樣的,我們為上面的people類新增一個解構函式

<?php
  /**
   * Created by PhpStorm.
   * User: ahao
   * Date: 2016/11/9
   * Time: 23:25
   */
  class people{
      public $name;
      public $age;
      public $sex;

      public function __construct($name, $age, $sex){
          $this->name = $name;
          $this->age = $age;
          $this->sex = $sex;
          echo "A people was born!"."<br/>";
      }

      public function description(){
          echo $this->name," is a ",$this->age," years old 
        ",$this->sex;
      }

      public function __destruct(){
          echo "A people was die!"."<br/>";
      }
  }

  $codeman = new people('codeman',22,'man');
  var_dump($codeman);

現在我們在指令碼中建立這個類的物件,執行這個指令碼,輸出結果如下所示:

A people was born!
object(people)[1]
public ‘name’ => string ‘codeman’ (length=7)
public ‘age’ => int 22
public ‘sex’ => string ‘man’ (length=3)
A people was die!

可以看到建構函式在類被建立時呼叫,解構函式在指令碼的末尾被呼叫,在指令碼結束之前執行的任何語句如上面對物件的輸出,都會在執行解構函式之前被執行。

注意:
(1)PHP和C++、Java等OOP語言的鏈式呼叫不同,執行子類的建構函式時不會先去執行父類的建構函式,需要分別在建構函式和解構函式中使用parent::__construct()和parent::__dertruct()來顯式呼叫父類的構造和解構函式。當然,如果一個子類沒有自己的建構函式,那麼假如父類有建構函式,子類建立物件時會自動去呼叫父類的建構函式,解構函式也是如此。演示如下:

<?php
  /**
   * Created by PhpStorm.
   * User: ahao
   * Date: 2016/11/10
   * Time: 0:22
   */
  class A{
      public function __construct()
      {
          echo "A was create!<br/>";
      }
      public function __destruct()
      {
          echo "A was destroy!<br/>";
      }
  }

  class B extends A{
      public function __construct()
      {
          echo "B was create!<br/>";
      }
      public function __destruct()
      {
          echo "B was destroy!<br/>";
      }
  }

  class C extends A{

 }

  $a = new A();
  $b = new B();
  $c = new C();

程式碼執行輸出結果為:

A was create!

B was create!
A was create!
A was destroy!
B was destroy!
A was destroy!

(2)解構函式即使在使用exit()和die()終止指令碼執行時也會被呼叫,這很好理解,上面我們已經說過了,解構函式在任何語句執行完之後執行。

我們在上面的程式碼末尾加上一行exit(),執行結果並沒有發生變化

  # previous code 
  $c = new C();
  exit();

(3)在解構函式裡執行exit()會中止其他關閉操作的執行。

例如在類B的解構函式的開頭執行exit(),後面的輸出語句不會被執行。

  class B extends A{
      public function __construct()
      {
          echo "B was create!<br/>";
      }

      public function __destruct()
      {
          exit();
          echo "B was destroy!<br/>";
      }
  }

執行結果如下:

A was create!
B was create!
A was create!
A was destroy!

2、__call()和__callStatIc()

當我們呼叫了一個不存在的方法時,會發生什麼?會報錯,呼叫了一個不存在的方法,如果我們想在程式碼發生呼叫了不存在的方法時做點什麼怎麼辦?使用__call()魔術方法!同樣的當呼叫了一個不存在的靜態方法想做點什麼,用__callStatic()魔術方法。

簡單的呼叫示例:

<?php
 /**
  * Created by PhpStorm.
  * User: ahao
  * Date: 2016/11/11
  * Time: 13:29
  */
 class Test{
     public function __call($name, $arguments){
         echo "Calling object method '$name' "
             . implode(', ', $arguments). "<br/>";
     }

     public static function __callStatic($name, $arguments){
         echo "Calling static method '$name' "
             . implode(', ', $arguments). "<br/>";
     }
 }

 $obj = new Test();

 $obj->runTest('in object context');
 Test::runTest('in static context');

輸出結果:

Calling object method ‘runTest’ in object context
Calling static method ‘runTest’ in static context

按照上面的說法,這兩個方法是為了在呼叫了不存在的方法時發生點什麼如防止系統報錯,自己包一層異常處理等,但這並不是魔術方法存在的真正意義,魔術方法可以讓類和方法的動態建立有了可能性,這在MVC框架的設計裡是很有用的,後面會開一個文章專門講講如何實現製作簡單的MVC框架。

藉助這兩個魔術方法,我們還可以實現類似於JS中的鏈式呼叫,在很多PHP MVC框架中大量開發封裝了這種鏈式呼叫的方法,在以後談到一些框架原始碼的設計中,會具體分析。

現在來實現一個簡單的類似JS的字串鏈式呼叫,在PHP中要求一個字串的真正長度會這樣寫:

strlen(trim($str));

我們要實現這樣的優雅地呼叫:

$str->trim()->strlen();

實現例項程式碼如下:

<?php
 /**
  * Created by PhpStorm.
  * User: ahao
  * Date: 2016/11/11
  * Time: 14:07
  */
 class Strings{
     public $str = '';

     public function __construct($str)
     {
         $this->str = $str;
     }

     public function __call($name, $arguements){
         $ret = '';
         switch ($name){
             case 'trim':
                 $new_s = trim($this->str);
                 $ret = new Strings($new_s);
                 break;
             case 'strlen':
                 $ret = strlen($this->str);
                 break;
             default:
         }
         return $ret;
     }
 }

 $s = new Strings(' codeman ');
 $length = $s->trim()->strlen();
 echo $length;

更好的寫法:

 public function __call($name, $arguements){
     $ret = call_user_func($name, $this->str);
     switch ($name){
         case 'trim':
             $ret = new Strings($ret);
             break;
         default:
     }
     return $ret;
 }

在鏈式呼叫中特別注意要明確每次呼叫後返回值是什麼,是普通變數還是一個物件,還是新建立的物件。

3、__get()和__set()

前面說到呼叫一個不存在的方法會觸發__call(),那使用到了不存在的類的屬性值呢?會觸發__get()和__set(),當引用了類一個不存在的屬性時,會觸發__get();當設定了類一個不存在的屬性時,會觸發__set()。
注意,當引用一個動態建立的屬性時也會呼叫__get()函式。

使用這個兩個魔術方法,可以在PHP中實現動態地操作變數,這就是PHP中的過載。注意PHP中的過載與其它絕大多數面嚮物件語言不同。傳統的”過載”是用於提供多個同名的類方法,但各方法的引數型別和個數不同。

使用例項程式碼:

<?php
 header('Content-type:text/html; charset=utf-8');
 /**
  * 動態建立屬性,避免取不存在的屬性的值和給不存在的屬性賦值時的報錯
  * Created by PhpStorm.
  * User: ahao
  * Date: 2016/8/18
  * Time: 0:25
  */
 class Account{
     private $user = 1;
     private $pwd = 2;

     public function __set($name, $value){
         echo "Setting $name to $value \r\n";;
         $this->$name = $value;
     }

     public function __get($name){
         if(!isset($this->$name)){
             echo '未設定<br/>';
             $this->$name = "正在為你設定預設值";
         }
         return $this->$name;
     }
 }
 $a = new Account();
 echo $a->user;  // 這裡會呼叫__get方法
 echo "<br/>";
 $a->name = 5;   // 這裡會呼叫__set方法
 echo "<br/>";
 echo $a->name;

4、__isset()和__unset()

我們經常使用isset()和unset()函式分別來判斷一個變數是否被設定了值和來釋放變數。假如判斷或釋放了不存在、動態建立的變數,會呼叫__isset()和__unset()。

在手冊裡是這麼說的:

當對不可訪問屬性呼叫 isset() 或 empty() 時,__isset() 會被呼叫。
當對不可訪問屬性呼叫 unset() 時,__unset() 會被呼叫。

PS.屬性過載只能在物件中進行。在靜態方法中,這些魔術方法將不會被呼叫。所以這些方法都不能被 宣告為 static。

例項程式碼如下:

<?php
 /**
  * Created by PhpStorm.
  * User: ahao
  * Date: 2016/11/11
  * Time: 15:46
  */
 class Test{
    private $val = '1';

    public function __get($name)
    {
        echo "Getting '$name'\n";
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }else{
            return null;
        }
    }

    public function __isset($name)
    {
        echo "__isset() is used!\n";
        if(isset($this->$name)){
            echo $this->$name."\n";
        }else{
            echo $name." is not set!\n";
        }
    }

    public function __unset($name)
    {
        echo "__unset() is used!\n";
        unset($this->$name);
    }
 }

 echo "<pre>";
 $obj = new Test();
 isset($obj->val);
 unset($obj->val);
 isset($obj->val);

輸出結果:

__isset() is used!
1
__unset() is used!
__isset() is used!
val is not set!

5、__sleep()和__wakeup()

我們經常使用serialize()和unserialize()函式來序列化和反序列化物件。我們在第一講中有說到,物件的序列化實際上是將物件的屬性按照屬性陣列+指標的形式進行序列化儲存。有的時候我們物件的屬性非常多,但是我們只是想儲存部分有用的屬性,如何實現?
答案就是__sleep()和__wakeup(),__sleep()在serialize()被呼叫時被檢查呼叫(如果存在),__wakeup()在unserialize()被呼叫時被檢查呼叫(如果存在)。

__sleep()可以用於清理物件,並返回一個包含物件中所有應被序列化的變數名稱的陣列。如果該方法未返回任何內容,則 NULL 被序列化,併產生一個 E_NOTICE 級別的錯誤。

__wakeup() 經常用在反序列化操作中,例如重新建立資料庫連線,或執行其它初始化操作。

使用程式碼示例:

<?php
class Connection
{
    protected $link;
    private $server, $username, $password, $db;

    public function __construct($server, $username, $password, $db)
    {
        $this->server = $server;
        $this->username = $username;
        $this->password = $password;
        $this->db = $db;
        $this->connect();
    }

    private function connect()
    {
        $this->link = mysql_connect($this->server, $this->username, $this->password);
        mysql_select_db($this->db, $this->link);
    }

    public function __sleep()
    {
        return array('server', 'username', 'password', 'db');
    }

    public function __wakeup()
    {
        $this->connect();
    }
}
?>

6、__toString()

__toString() 方法用於一個類被當成字串時應怎樣迴應。例如 echo $obj; 應該顯示些什麼。此方法必須返回一個字串,否則將發出一條 E_RECOVERABLE_ERROR 級別的致命錯誤。這在程式碼除錯中非常有用,只要針對類編寫好相應的__toString()方法,就是方便地直接echo物件來了解物件的結構,包括存在哪些屬性及屬性值等。

使用程式碼示例:

<?php
 class TestClass
 {
    public $foo;

    public function __construct($foo)
    {
        $this->foo = $foo;
    }

    public function __toString() {
        return $this->foo;
    }
 }

 $class = new TestClass('Hello');
 echo $class;
 ?>

輸出結果:

Hello

7、__invoke()

當一個物件被當成函式方法使用時,就會觸發__invoke()方法,可帶引數,使用形式好像有點像建構函式,並且可帶引數列表。我也沒有過具體的應用場景的使用,在某些需求中可能會很有用。

使用程式碼示例:

<?php
 /**
  * Created by PhpStorm.
  * User: ahao
  * Date: 2016/11/11
  * Time: 16:27
  */
 class CallableClass
 {
     function __invoke($x) {
         var_dump($x);
     }
 }
 $obj = new CallableClass;
 var_dump(is_callable($obj));
 $obj(5);
 ?>

輸出結果:
true
5

PS.可以使用is_callable($obj)來判斷物件是否實現了__invoke()的魔術方法。

8、__set_state()

我們可以用var_export()來輸出或返回一個變數的字串表示,當呼叫 var_export() 匯出類時,此靜態方法會被呼叫(必須有)。

使用程式碼示例:

<?php

 class A
 {
     public $var1;
     public $var2;

     public static function __set_state($an_array) // As of PHP 5.1.0
     {
         $obj = new A;
         $obj->var1 = $an_array['var1'];
         $obj->var2 = $an_array['var2'];
         return $obj;
     }
 }

 $a = new A;
 $a->var1 = 5;
 $a->var2 = 'foo';

 eval('$b = ' . var_export($a, true) . ';'); 
 var_dump($b);
 ?>

輸出結果:

object(A)[2]
public ‘var1’ => int 5
public ‘var2’ => string ‘foo’ (length=3)

9、__clone()

__clone()這個魔術方法設計的目的就是為了實現類的深拷貝。當我們使用newObj=cloneobj克隆物件時,實際上只是實現了淺拷貝,只是對物件屬性的簡單複製,包括引用型別的屬性,這樣就產生一個問題,複製後產生的物件的引用屬性比如說物件和原來的物件裡的引用屬性指向的是同一個物件地址空間。這顯然不是我們想要的,這時候就可以藉助__clone()來實現深拷貝。

使用程式碼示例:

<?php
class SubObject
{
    static $instances = 0;
    public $instance;

    public function __construct() {
        $this->instance = ++self::$instances;
    }

    public function __clone() {
        $this->instance = ++self::$instances;
    }
}

class MyCloneable
{
    public $object1;
    public $object2;

    function __clone()
    {

        // 強制複製一份this->object, 否則仍然指向同一個物件
        $this->object1 = clone $this->object1;
        $this->object2 = clone $this->object2;
    }
}

$obj = new MyCloneable();

$obj->object1 = new SubObject();
$obj->object2 = new SubObject();

$obj2 = clone $obj;

echo "<pre>";

print("Original Object:\n");
print_r($obj);

print("Cloned Object:\n");
print_r($obj2);

?>

輸出結果:

Original Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 1
)

[object2] => SubObject Object
    (
        [instance] => 2
    )

)
Cloned Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 3
)

[object2] => SubObject Object
    (
        [instance] => 4
    )

)

在這裡instance,實際上就是我們不同物件的標識,物件被複制時,呼叫類中的__clone()方法,在方法裡再實現對類物件屬性的clone,物件屬性在被克隆時再去呼叫自己類中的__clone()。

10、__debuginfo()

當呼叫var_dump($obj)輸出物件時,物件所屬的類中的__debuginfo()魔術方法會被呼叫,如果該類中沒有定義這個魔術方法,則預設輸出物件的所有屬性。

注意:這個特性在PHP5.6.0中被新增,所以低版本的PHP環境中是無法使用這個方法的。

使用例項程式碼:

<?php
class C {
    private $prop;

    public function __construct($val) {
        $this->prop = $val;
    }

    public function __debugInfo() {
        return [
            'propSquared' => $this->prop ** 2,
        ];
    }
}

var_dump(new C(42));
?>

輸出結果:

object(C)#1 (1) { [“propSquared”]=> int(1764) }

對PHP中魔術方法的介紹就到這裡,很多人在工作或專案中基本沒怎麼用到魔術方法,學習的時候也僅僅侷限於學習,也沒有思考怎麼去用。其實不僅僅是框架設計,在專案的一些業務邏輯實現、程式碼除錯跟蹤等方面,如果能充分魔術方法提供的特性,對程式碼的優化和實現的簡潔優雅很有幫助,一些應用場景你可能自己去寫程式碼實現邏輯非常複雜,但如果用得上魔術方法,有時候問題就迎刃而解。

相關推薦

總結2 PHP魔術方法及其應用場景

PHP中有一類特別的系統方法,它們統一以__開頭,使用語義清晰簡單,這類形式特殊、作用特殊的方法被稱為魔術方法。 常見的魔術方法有__construct()、__destruct()、__call()、__callStatic()、__get()、__set(

PHP魔術方法__get和__set的用法

PHP中,魔術方法有很多種,本文章給大家記錄分享:__get\__set的用法跟理解 首先,我們得明白oop中的三個訪問修飾符:public、protected、private 分別的意思: 1、公共的,任何地方都可以訪問,包括本類,子類,外部 2、受保護的成員屬性或者方法,只能

盤點PHP最實用的5大魔術方法及其功能作用

  PHP是一門非常優秀的指令碼程式語言,與其它程式語言有一個非常不同的地方,那就是魔術方法,PHP有非常多的魔術方法用於實現一些非常不可思議的功能。 啥是PHP的魔術方法? 在定義類時,以兩個下劃線字元(__)開頭的方法都是魔術方法,而且方法名都是PHP預先定義好的,每一個都

PHP防止SQL注入的方法

【一、在伺服器端配置】        安全,PHP程式碼編寫是一方面,PHP的配置更是非常關鍵。 我們php手手工安裝的,php的預設配置檔案在 /usr/local/apache2/conf/php.ini,我們最主要

PHP面向物件魔術方法使用

一: PHP為我們提供了一系列用__開頭的函式,這些函式無需自己動手呼叫,會在何時的時機自動呼叫,稱這類函式為魔術函式。 如: function __construct( ) {} 在new一個新物件時自動呼叫此函式。 二: 1:建構函式__cons

java多執行緒2Thread的例項方法

1.start()   start()方法的作用講白了 就是 “執行緒規劃器”,此執行緒可以執行,正在等待CPU呼叫執行緒物件得run()方法,產生一個非同步執行的效果。 結論:CPU執行哪個執行緒的程式碼具有不確定性。 啟動的順序是 m0 m1 m2 但是輸出的結

php靜態方法的使用

time ima 方法 cte count() turn font 應該 面向對象 靜態方法 (1)靜態方法不能訪問這個類中的普通屬性,因為那些屬性屬於一個對象,但可以訪問靜態屬性; (2)從當前類(不是子類)中訪問靜態方法或屬性,可以使用 self 關鍵字,self 指向

php學習之道phpis_file和file_exist的差別,and推斷文件夾is_dir

文件 -m post 文件的 style 文件夾 原因 -a 文件名 在PHP中,is_file和file_isexist是有非常小差別的 1) is_file: $path ="/path/to/file/text.txt"; if(file_exis

PHP OOP 魔術方法

需要 con 實例化 echo 對象 def sse ase urn 1、__construct():構造函數,new一個新對象時,自動調用    [public] function __construct($name=""){ $this ->

SSM-SpringMVC-21SpringMVC處理器方法之返回值Object篇

自定義user RR jsp頁面 input tle color int() bin 重復 ------------吾亦無他,唯手熟爾,謙卑若愚,好學若饑------------- 今天要記錄的是處理方法,返回值為Object的那種,我給它分了一下類: 1.返回

求100以內素數的5基本方法及其優化

其他 依然 都是 耗時 基本 for proc rime 數字 求100以內素數的5中基本方法及其優化方法1 基本做法 錯解比較:進入了小循環:有時加pass也可以。錯解:這裏的print也同樣註意不要寫到循環內。 註釋:1.兩種條件運用:為合數。2.以上錯誤點。方法二

python魔術方法簡述

圖片 water pro 程序 基類 get ffffff http cbc 魔術方法:***實例化:new :實例化一個對象 方法很少使用,一般使用return super().))new(cls)基類ibject方法來創建實例並返回。 hash:返回一個整數,如

php靜態方法和靜態屬性的介紹

靜態屬性 size col 實例 生效 訪問類 都是 靜態 self 靜態分為兩個部分:靜態屬性和靜態方法 靜態的東西都是給類用的(包括類常量),非靜態的都是給對象用的 靜態屬性 在定義屬性的時候,使用關鍵字static修飾的屬性稱之為靜態屬性。 靜態方法 使用sta

Go基礎系列Go方法

Go方法簡介 Go中的struct結構類似於面向物件中的類。面向物件中,除了成員變數還有方法。 Go中也有方法,它是一種特殊的函式,定義於struct之上(與struct關聯、繫結),被稱為struct的receiver。 它的定義方式大致如下: type mytype struct{} func

美團大腦知識圖譜的建模方法及其應用

作為人工智慧時代最重要的知識表示方式之一,知識圖譜能夠打破不同場景下的資料隔離,為搜尋、推薦、問答、解釋與決策等應用提供基礎支撐。美團大腦圍繞吃喝玩樂等多種場景,構建了生活娛樂領域超大規模的知識圖譜,為使用者和商家建立起全方位的連結。我們美團希望能夠通過對應用場景下的使用者偏好和商家定位進行更為深度的理解,進

redis學習2redis的資料結構結構與物件

第1章 前言 redis這麼強大,那麼它底層是如何實現的呢?使用了哪些資料結構呢?本文就帶大家來剖析剖析 第2章 簡單動態字串(SDS)   redis的字串不是直接用c語言的字串,而是用了一種稱為簡單動態字串(SDS)的抽象型別,並將其作為預設字串。   redis中包含字串值的鍵

SQLmapper常用方法

resultMap 中定義實體類的名稱 方法名 方法 引數 返回值 分頁查詢 findByPage 實體類物件 List

手把手教你ExtJS從入門到放棄——篇六(示例2window新增子元件)

例2: 在window中新增子元件,並講解常用查詢元件的方式: 重點分析:該例項主要針對於元件的查詢進行詳細的講解,在以後的應用開發中,同學們應該學會各種方式去查詢所需要的元件,不要拘泥於某種特定形式,這樣會給開發思路帶來很多好處。 -------------------- 具體

Javamath類別的各種運算方法及其應用

import static java.lang.Math.*; Math 類別      Math 類別包括一些數字運算的方法,如指數、對數、開平方、三角函式等 Math 類別的資料域:             Double E = 2.7182818284590452

美團大腦知識圖譜的建模方法及其應用-CSDN公開課-專題視訊課程

美團大腦:知識圖譜的建模方法及其應用—681人已學習 課程介紹         人類社會已經進入了智慧化時代,作為一家提供生活化服務的高科技公司,美團點評正踏上智慧化升級與轉型的道路,各個業務的智慧化應