提筆寫初體驗總不知道從何提及,直接聊PHP中的函數、PHP網絡技術、數據庫操做、PHP模板等感受又不是初體驗。最後仍是決定從PHP的面向對象、PHP的魔術方法、PHP的反射、PHP中的異常和錯誤這4個方面簡單介紹一下。php
這裏咱們就不給面向對象下定義了,不過咱們仍是要說一下類和對象的。類是對象的抽象組織,對象是類的具體存在。接下來咱們就拿PHP爲例,來探討一下對象的「形」與「本」的問題。程序員
在PHP中,每一個類的定義都是以關鍵字class開頭,後面是類名和一對花括號,括號中包含類成員和方法的定義。以下是一個簡單類的定義: 數據庫
class Person { public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function sayHello(){ echo "My name is $this->name and I am $this->age years old. I am a $this->sex.\r\n"; echo self::$information; echo "<br>"; } } $person = new Person(); $person->name = 'Lisi'; $person->sayHello(); echo serialize($person); //輸出結果以下: //My name is Lisi and I am 23 years old. I am a male. I come from the earth //O:6:"Person":3:{s:4:"name";s:4:"Lisi";s:11:"Personage";i:23;s:11:"Personsex";s:4:"male";}
當把類對象序列化輸出時,能夠看出類對象在存儲時相似於數組的形式。那麼類對象與數組從本質上又有什麼區別與聯繫呢?接下來從對象「本」來分析一下PHP對對象的底層實現。以下是PHP源碼中對變量的定義:數組
#變量定義 typedef struct _zval_struct zeal; //存儲變量的類型 struct _zval_struct { /* Variable information */ zvalue_value value; /* 存儲變量的值 */ zend_uint refcount; /* 變量引用數 */ zend_uchar type; /* 變量類型 */ zend_uchar is_ref; /* 變量是否被引用 */ }; typedef union _zvalue_value { long lval; /* 長整型 */ double dval; /* double型 */ struct { char *val; int len; } str; /* String型 */ HashTable *ht; /* array型 */ zend_object_value obj; /* 對象型 */ } zvalue_value; #對象底層實現 typedef struct _zend_object { zend_class_entry *ce; //類入口,存儲該對象的類結構,至關於類指針 HashTable *properties; //對象屬性組成的HashTable HashTable *guards; /* 阻止遞歸調用 */ } zend_object; typedef struct _zend_guard { zend_bool in_get; zend_bool in_set; zend_bool in_unset; zend_bool in_isset; zend_bool dummy; /* sizeof(zend_guard) must not be equal to sizeof(void*) */ } zend_guard; //因爲zend_class_entry源碼較多且複雜,所以此處省略。
經過上面的代碼咱們也對PHP如何存儲對象有了初步的認識,那對象與數組又是什麼關係呢?經過PHP的源碼可得,zvalue_object結構中有一個HashTable的類型,它就是存儲數組的。PHP對象的結構體中不只有HashTable(用於存儲類對象特有的屬性),並且還有對象所屬類的入口等,以下是PHP對象的組成:網絡
其中PHP源碼中zend_class_entry結構體中存儲的就是類的指針,該結構體中包含類常量、靜態屬性、標準方法、魔術方法、自定義方法等。而屬性數組存儲的是類對象的屬性。接下來咱們仍是以如上的Person類爲例,談一談對象與數組:ide
class Person { public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } } $person = new Person(); $person_arr = array("name"=>"zhangsan", "age"=>"23", "sex"=>"male"); echo serialize($person); echo "<br>"; //輸出對象序列化結果並換行符 echo serialize($person_arr); echo "<br>"; //輸出數組序列化結果並換行 $object = (object)$person_arr; //將數組轉化爲對象 echo serialize($object); //輸出數組序轉化爲對象的序列化結果 /*輸出結果以下: O:6:"Person":3:{s:4:"name";s:8:"zhangsan";s:11:"Personage";i:23;s:11:"Personsex";s:4:"male";} a:3:{s:4:"name";s:8:"zhangsan";s:3:"age";s:2:"23";s:3:"sex";s:4:"male";} O:8:"stdClass":3:{s:4:"name";s:8:"zhangsan";s:3:"age";s:2:"23";s:3:"sex";s:4:"male";} 結果解釋以下: O表明的是對象,a表明的是數組, O後的數字(6和8)表明該對象所屬類名的長度,緊挨在數字後面的就是類名 類名後面的數字爲類對象屬性的個數,如上有3個,分別爲name,age,sex 大括號中的爲對象或數組的屬性名和屬性值的鍵值對,其中s表明字符串 最後須要說明的是當把數組轉化爲對象時,由於沒有與數組轉換成對象對應的類,所以PHP中一個稱爲"孤兒"的類stdClass類就會收留這個對象 */
可能細心的你在對象組成的那張圖中看到了魔術方法,可是上一節中並無對zend_class_entry中的內容作任何介紹。那麼什麼又是魔術方法呢?魔術方法就是以兩個下劃線「__」開頭、具備一些特殊做用的方法。其實如上的Person類中,咱們不經意間就使用了魔術方法__construct(),這個魔術方法就是構造方法。用於在建立類對象時對屬性進行賦值。接下來咱們將介紹幾個常見的魔術方法讓你們對魔術方法有個初步瞭解。函數
class Person { public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __sleep() { // TODO: Implement __sleep() method return array("name","age"); //該方法指定僅序列化對象的name和age屬性 } public function __wakeup() { // TODO: Implement __wakeup() method. var_dump($this->name); //打印輸出對象的name屬性的值 var_dump($this->sex); //打印輸出對象的sex屬性的值,因爲sex沒有被序列化,所以輸出null } } $serResult = serialize(new Person()); //序列化一個Person類對象,該方法完成前先調用Person類的__sleep() echo $serResult; echo "<br>"; //輸出對象序列化結果並換行符 $unSerResult = unserialize($serResult); //將序列化結果反序列化,該方法完成前調用Person類的__wakeup方法 echo $unSerResult->name; echo "<br>"; //由反序列化獲得的Person類對象調用對象的name屬性 echo var_dump($unSerResult); echo "<br>"; //輸出反序列化獲得的Person類對象 /*輸出結果以下: O:6:"Person":2:{s:4:"name";s:8:"zhangsan";s:11:"Personage";i:23;} string(8) "zhangsan" NULL zhangsan object(Person)#1 (3) { ["name"]=> string(8) "zhangsan" ["age":"Person":private]=> int(23) ["sex":"Person":private]=> NULL } */
1 class Person { 2 public $name; 3 private $age; 4 private $sex; 5 public static $information = "I come from the earth"; 6 public function __construct($name="zhangsan", $age=23, $sex="male") { 7 $this->sex = $sex; 8 $this->age = $age; 9 $this->name = $name; 10 } 11 public function __destruct() { 12 // TODO: Implement __destruct() method. 13 echo "The object will be destructed"; 14 } 15 } 16 17 $person = new Person(); 18 unset($person); //unset()方法釋放$person對象 19 echo $person->name; //該行試圖打印$person對象的name屬性值,因爲$person對象已經被銷燬了,所以該行會報錯 20 /* 21 輸出結果以下: 22 The object will be destructed 23 Notice: Undefined variable: person in /Users/zhangshilei/PhpstormProjects/untitled/demo/Person.php on line 19 24 25 Notice: Trying to get property of non-object in /Users/zhangshilei/PhpstormProjects/untitled/demo/Person.php on line 19 26 */
class Person { public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __get($field) { // TODO: Implement __get() method. echo "the get method is executed<br>"; return $this->$field; } public function __set($key, $value) { // TODO: Implement __set() method. echo "The set method is executed<br>"; $this->$key = $value; } } $person = new Person(); $person->name="Lisi"; echo $person->name; echo "<br>"; $person->age = 25; //age做爲$person的私有屬性,若是沒有__set()方法此句會報錯 echo $person->age; //沒有__get()方法,此句會報錯,若是__get()方法中沒有return語句,該句沒有返回值 /*輸出結果以下: Lisi The set method is executed the get method is executed 25 結果解釋以下: 不知道你們注意到了沒有,設置公有屬性name的值沒有調用__set()方法,讀取公有屬性name的值沒有調用__get()方法 只有在設置和讀取私有屬性age的值才調用了__get()和__set()方法 */
class Person { public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __call($name, $arguments) { // TODO: Implement __call() method. echo "被調用方法的名稱爲:$name<br>"; echo "傳入該方法的參數爲:", var_dump($arguments),"<br>"; } public static function __callStatic($name, $arguments) { // TODO: Implement __callStatic() method. echo "被調用靜態方法的名稱爲:$name<br>"; echo "傳入該方法的參數爲:", var_dump($arguments),"<br>"; } } $person = new Person(); $person->shopping("clothes","fruit","snack"); Person::travel("Beijing","Pair","London","Prussia"); /* 輸出結果爲: 被調用方法的名稱爲:shopping 傳入該方法的參數爲:array(3) { [0]=> string(7) "clothes" [1]=> string(5) "fruit" [2]=> string(5) "snack" } 被調用靜態方法的名稱爲:travel 傳入該方法的參數爲:array(4) { [0]=> string(7) "Beijing" [1]=> string(4) "Pair" [2]=> string(6) "London" [3]=> string(7) "Prussia" } */
class Person { public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __toString() { // TODO: Implement __toString() method. return "My name is $this->name.<br> I am $this->age years old and I am a $this->sex."; } } $person = new Person(); echo $person; /* 輸出結果以下: My name is zhangsan. I am 23 years old and I am a male. */
關於PHP的魔術方法咱們就簡單介紹到這裏,由上咱們能夠看出從構造方法上,PHP相比於還稍有欠缺,但PHP中有__set()和__get(),使得動態增長對象的屬性字段變得更加方便,而對於Java來講要實現相似的效果,就不得不借助反射API或直接修改編譯後字節碼的方式實現了。Java中有反射機制,那麼PHP中呢?接下來讓咱們來看一看PHP中的反射實現。ui
反射,直觀的理解就是根據到達地找到出發地和來源。好比給出一個對象就能夠找到對象所屬的類、擁有哪些方法。反射能夠在PHP運行狀態中,擴展分析PHP程序,導出或提取出關於類、方法、屬性、參數等的詳細信息,這種動態獲取信息以及動態調用對象方法的功能稱爲反射API。this
class Person { public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function __sleep() { // TODO: Implement __sleep() method return array("name","age"); //該方法指定僅序列化對象的name和age屬性 } public function __wakeup() { // TODO: Implement __wakeup() method. var_dump($this->name); //打印輸出對象的name屬性的值 var_dump($this->sex); //打印輸出對象的sex屬性的值,因爲sex沒有被序列化,所以輸出null } public function __destruct() { // TODO: Implement __destruct() method. echo "The object will be destructed"; } public function __get($field) { // TODO: Implement __get() method. echo "the get method is executed<br>"; return $this->$field; } public function __set($key, $value) { // TODO: Implement __set() method. echo "The set method is executed<br>"; $this->$key = $value; } public function __call($name, $arguments) { // TODO: Implement __call() method. echo "被調用方法的名稱爲:$name<br>"; echo "傳入該方法的參數爲:", var_dump($arguments),"<br>"; } public static function __callStatic($name, $arguments) { // TODO: Implement __callStatic() method. echo "被調用方法的名稱爲:$name<br>"; echo "傳入該方法的參數爲:", var_dump($arguments),"<br>"; } public function __toString() { // TODO: Implement __toString() method. return "My name is $this->name.<br> I am $this->age years old and I am a $this->sex."; } public function sayHello($other){ echo "My name is $this->name, nice to meet you $other"; } } $person = new Person(); $reflect = new ReflectionObject($person); $props = $reflect->getProperties(); //獲取對象的屬性列表(全部屬性) foreach ($props as $prop){ //打印類中屬性列表 echo $prop->getName(),"\r"; } echo "<br>"; $methods = $reflect->getMethods(); //獲取類的方法列表 foreach ($methods as $method){ echo $method->getName(),"\r"; } echo "<br>"; //返回對象屬性的關聯數組 var_dump(get_object_vars($person)); echo "<br>"; //返回類的公有屬性的關聯數組 var_dump(get_class_vars(get_class($person))); echo "<br>"; //獲取類的方法名組成的數組 var_dump(get_class_methods(get_class($person))); /*輸出結果以下: name age sex information __construct __sleep __wakeup __destruct __get __set __call __callStatic __toString sayHello array(1) { ["name"]=> string(8) "zhangsan" } array(2) { ["name"]=> NULL ["information"]=> string(21) "I come from the earth" } array(10) { [0]=> string(11) "__construct" [1]=> string(7) "__sleep" [2]=> string(8) "__wakeup" [3]=> string(10) "__destruct" [4]=> string(5) "__get" [5]=> string(5) "__set" [6]=> string(6) "__call" [7]=> string(12) "__callStatic" [8]=> string(10) "__toString" [9]=> string(8) "sayHello" } */
如上代碼中介紹的是經過對象獲取類的方法和屬性字段,而反射不只僅能夠用於類和對象,還能夠用於函數、擴展模塊、異常等。既然反射能夠探知類的內部結構,那麼就能夠利用反射機制實現插件的功能,也能夠利用反射機制實現動態代理。接下來舉個簡單的例子看看如何經過反射機制實現動態代理。加密
class Person { public $name; private $age; private $sex; public static $information = "I come from the earth"; public function __construct($name="zhangsan", $age=23, $sex="male") { $this->sex = $sex; $this->age = $age; $this->name = $name; } public function sayHello($other){ echo "My name is $this->name, nice to meet you ".implode("",$other)."<br>"; } } class Dynamicproxy{ private $target; public function __construct($className) { //爲動態代理傳入類名稱,則自動生成類對象 $this->target = new $className(); } public function __call($name, $arguments) { // TODO: Implement __call() method. $reflect = new ReflectionObject($this->target); if($method = $reflect->getMethod($name)){ //獲取名稱爲$name的類方法 if($method->isPublic() && !$method->isAbstract()){ echo "Before the method ".$method->getName()." we should do something<br>"; $method->invoke($this->target,$arguments); echo "After the method ".$method->getName()." we should do something<br>"; } } } } $objProxy = new Dynamicproxy("Person"); $objProxy->sayHello("Janny"); /* 輸出結果以下: Before the method sayHello we should do something My name is zhangsan, nice to meet you Janny After the method sayHello we should do something */
如上的代碼中真正實現sayHello()動做的是Person類中的sayHello()方法,而Dynamicproxy僅是一個代理類,其中並無定義sayHello()方法,而是經過__call()方法動態調用類Person的sayHello()方法。在DynamicProxy類中能夠作sayHello()方法的先後攔截,而且能夠動態的改變類中的方法和屬性。不少時候,善用反射能夠保持代碼的優雅和簡潔,但反射也會破壞類的封裝性,由於反射可使本不該該暴露的方法或屬性被強制暴露了出來。
在語言級別一般有許多錯誤處理模式,但這些模式每每創建在約定俗稱的基礎上,也就是錯誤都是可預知的。不一樣的語言對異常和錯誤的定義也是不同的,在PHP中,遇到任何自身錯誤都會觸發一個錯誤,而不是拋出異常。也就是說PHP一旦遇到非正常代碼,一般都會觸發錯誤,而不是拋出異常。所以若是想使用異常處理不可預料的問題,是辦不到的。好比,想在文件不存在或數據庫沒法創建鏈接時觸發異常,是不可行的。PHP會把這些做爲錯誤拋出,而不是做爲異常捕獲。仍是回到PHP的錯誤處理上,PHP中的錯誤級別大體分爲如下幾類:
function showError(){ $date = '2017-06-05'; if(ereg("([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})",$date,$regs)){ //deprecated級別錯誤 echo $regs[3].$regs[2].$regs[1]; }else{ echo "Invalid date format: $date"; } if($value > 5){ //notice級別的錯誤 echo "It is amazing, the variable $value is not init",PHP_EOL; } $a = array('o'=>2,4,6,8); echo $a[o]; //notice級別的錯誤 $sum = array_sum($a,3); //warning級別的錯誤,傳入參數不正確 echo fun(); //fetal error echo "the code is after fetal error"; //該句不執行 //echo "prase error:",$55; } showError();
接下來咱們看一看針對上邊介紹的各個級別的錯誤PHP是如何處理的。PHP中提供了set_error_handler()函數來處理錯誤,固然該函數也不是能夠託管全部種類的錯誤,如E_ERROR、E_PARSE、E_CORE_ERROR等錯誤,這些錯誤會以原始的方式顯示。固然也能夠經過restore_error_handler()取消接管:
function DivisionError($errno, $errmsg, $errfile, $errline){ // 獲取當前錯誤時間 $dt = date("Y-m-d H:i:s (T)"); $errortype = array ( E_ERROR => 'Error', E_WARNING => 'Warning', E_PARSE => 'Parsing Error', E_NOTICE => 'Notice', E_CORE_ERROR => 'Core Error', E_CORE_WARNING => 'Core Warning', E_COMPILE_ERROR => 'Compile Error', E_COMPILE_WARNING => 'Compile Warning', E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', E_STRICT => 'Runtime Notice', E_RECOVERABLE_ERROR => 'Catchable Fatal Error' ); $user_errors = array(E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE); $err = "錯誤時間".$dt."<br>"; $err .= "錯誤等級".$errno ."<br>"; $err .= "錯誤等級名稱" . $errortype[$errno] . "<br>"; $err .= "錯誤消息" . $errmsg . "<br>"; $err .= "錯誤發生所在文件" . $errfile . "<br>"; $err .= "錯誤所在行" . $errline . "<br>"; echo $err; } set_error_handler("DivisionError"); //當拋出錯誤時,直接交給DivisionError處理 function showError(){ $date = '2017-06-05'; if(ereg("([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})",$date,$regs)){ //deprecated級別錯誤 echo $regs[1].$regs[2].$regs[3]; }else{ echo "Invalid date format: $date"; } if($value > 5){ //notice級別的錯誤 echo "It is amazing, the variable $value is not init",PHP_EOL; } $a = array('o'=>2,4,6,8); echo $a[o]; //notice級別的錯誤 $sum = array_sum($a,3); echo fun(); //fetal error echo "the code is after fetal error"; //該句不執行 //echo "prase error:",$55; } showError();
如上這種「曲折迂迴」的處理方式也存在問題:必須依靠程序員本身來掌控對異常的處理,對於異常高發區、敏感區,若是處理很差就會出現業務數據不一致的問題,可是優勢就是能夠得到程序運行的上下文信息,以進行鍼對性補救。
對於代碼中存在的異常,須要認爲的進行拋出,接下來咱們經過自定義一個異常類來處理拋出的異常,
class DivisionException extends Exception{ //自定義異常處理類 public function __toString(){ //覆寫父類的__toString(),規定輸出格式 $errorMessage = '錯誤發生於文件'.$this->getFile().'第'.$this->getLine().'行<br>' .'錯誤緣由爲'.$this->getMessage(); return $errorMessage; } } class Calculate { private $num1; private $num2; private $operater; public function __construct($num1, $num2, $operater) { $this->operater = $operater; $this->num1 = $num1; $this->num2 = $num2; } public function getResult() { try { if ($this->operater == "+") { return $this->num1 + $this->num2; } else if ($this->operater == "-") { return $this->num1 - $this->num2; } else if ($this->operater == "*") { return $this->num1 * $this->num2; } else { if($this->num2 == 0){ //若是除數爲0則拋出異常 throw new DivisionException("除數不能爲0"); } return $this->num1 / $this->num2; } } catch (DivisionException $exception){ echo $exception; } } } $calculate = new Calculate(10,0,"/"); echo $calculate->getResult(); /** 輸出結果以下: 錯誤發生於文件/Users/zhangshilei/PhpstormProjects/untitled/demo/Person.php第98行 錯誤緣由爲除數不能爲0 */
初體驗就爲你們介紹到這裏吧,之後有機會在深刻的去了解PHP函數、PHP與網絡、PHP與數據庫等等的內容吧。