PHP 核心技術 --​​​​​​​​物件導向

A_aliane發表於2019-08-21

類的組合與繼承

  • 假設我們有兩個類,一個 person,另外一個是 family;在 family 類中我們建立 person 類中的物件,並且我們把這個物件視為 family 類的一個屬性,並呼叫它的方法處理問題,那麼這種複用方式也稱為組合。
  • 類與類之間還有一種父與子的關係,子類可以繼承父類的屬性和方法,我們稱之為繼承。在繼承裡,子類擁有父類的屬性和方法,同時子類也有自己的屬性和方法。
<?php
class person{
    public $name = 'Tom';
    public $gender;
    static $money = 10000;
    public function __construct(){
        echo '這裡是父類',PHP_EOL;
    }

    public function say(){
        echo $this->name,"\t is ",$this->gender,"\r\n";
    }
}

class family extends person{
    public $name;
    public $gender;
    public $age;
    static $money = 1000;
    public function __construct(){
        parent::__construct();
        echo '這裡是子類',PHP_EOL;
    }

    public function say(){
        parent::say();
        echo $this->name,"\t is \t",$this->gender,",and is \t ",$this->age,PHP_EOL;
    }

    public function cry(){
        echo parent::$money,PHP_EOL;
        echo '% >_< %',PHP_EOL;
        echo self::$money,PHP_EOL;
        echo "(*^_^*)";
    }
}

$poor = new family();
$poor->name = 'Lee';
$poor->gender = 'female';
$poor->age = 25;
$poor->say();
$poor->cry();

返回結果

這裡是父類
這裡是子類
Lee  is female
Lee  is     female,and is    25
10000
% >_< %
1000
(*^_^*)% 
  • 組合和繼承都是提高程式碼可重用行的手段。在設計物件模型時,可以按照語義識別類之間的組合關係和繼承關係。

例如:

  • 繼承是一種“是,像”的關係,組合則是一種“需要”的關係 【父親和兒子應該是繼承關係,父親和家庭應該是組合關係 】
  • 組合偏重與和區域性的關係,繼承偏重與父與子的關係。
    PHP 核心技術   --​​​​​​​​
  • 從方法複用的角度考慮,如果兩個類具有很多相同的程式碼和方法,我們就可以從這兩個類中抽象出一個父類,提供公共方法,然後兩個類作為子類,提供個性方法,這時繼承更好。

PHP 核心技術   --​​​​​​​​

  • 組合的限制很少,組合之間的類可以關係很小(體現為複用程式碼),設定沒有關係。

程式設計中

  • 繼承和組合的取捨往往都不是這麼直接明瞭,很難說出二者是“像”的關係還是需要的關係。甚至說把它拿到現實世界中建模,更加無法決定是繼承還是組合的關係了。這時,它該如何辦,有什麼標準?這個標準就是:低耦合

低耦合

  • 耦合是一個軟體結構內不同模組之間互聯程度的度量,也就是不同模組之間的依賴關係。
  • 低耦合是指模組和模組之間,儘可能地使模組間獨立存在;模組與模組之間的介面儘量少而簡單。現代的物件導向的思想不強調為真實世界建模,變得更加理性化一些,把目標放在解耦上。

解耦

  • 目的是為了解除模組與模組之間的依賴。
  • 繼承和組合二者在語義上難以區分,但是我們更傾向於使用組合。
  • 繼承存在的問題:
    • 繼承破壞封裝性;(鳥類為父類,而鴨子和鴕鳥作為子類,它們卻擁有飛翔的方法)
    • 繼承是緊耦合的,使得子類和父類捆綁在一起。組合僅是通過唯一介面和外部進行通訊,耦合度低於繼承。
    • 繼承擴充套件複雜,隨著繼承層數的增加和子類的增加,將涉及大量方法重寫。使用組合,可以根據型別約束,實現動態組合,減少程式碼。
    • 不恰當的使用繼承可能違反現實世界中的邏輯;

組合

<?php

class car{
    public function addoil(){
        echo "Add oil \r\n";
    }
}

class bmw extends car{
}
class benz{
    public $car;
    public function __construct(){
        $this->car = new car();
    }

    public function addoil(){
        $this->car->addoil();
    }
}
$bmw = new bmw();
$bmw->addoil();
$benz = new benz();
$benz->addoil();
  • 在建立組合物件時,組合需要一一建立區域性物件,這一點程度上增加了一些程式碼,而繼承不需要這一步,繼承擁有父類的方法,可以直接使用

如何使用繼承:

  • 精心設計專門用於被繼承的類,繼承樹的抽象層應該比較穩定,一般不多於三層;
  • 對於不是專門用於被繼承的類,禁止其被繼承,也就是使用 final 修飾符。使用 final 修飾符即可防止重要方法被非法覆寫,又能給編輯器尋找優化的機會;
  • 優先考了用組合關係提高程式碼的可重用性;
  • 子類是一直特殊的型別,而不只是父類的一個角色;
  • 子類擴充套件,而不是覆蓋或者使父類的功能失效;
  • 底層程式碼多用組合,頂層/業務層程式碼多用繼承。底層用組合可以提供效率,避免物件臃腫。頂層程式碼用繼承可以提高靈活性,讓業務使用更方便。

既要組合的靈活,又要繼承的簡潔

  • 多重繼承,一個類可以同時繼承多個父類,組合兩個父類的功能;缺點:多重繼承過於靈活,並且會帶來“零星問題”,故為其使用帶來了不少困難,模型變得複雜起來。
  • traits php5.4引入的新的語法結構,可以方便我們實現物件的擴充套件,是除extend,implements外的另外一種擴充套件物件的方式,traits 即可以使單繼承模式的語言獲得多重繼承的靈活,又可以避免多重繼承帶來的種種問題

traits的用法

  • 通過在類中使用use關鍵字宣告要組合的Trait名稱,而具體某個Trait的宣告使用trait關鍵詞,Trait不能直接例項化
    <?php
    trait Drive {
    public $carName = 'BMW';
    public function driving() {
    echo "driving {$this->carName}\n";
    }
    }
    class Person {
    public function age() {
    echo "i am 18 years old\n";
    }
    }
    class Student extends Person {
    use Drive;
    public function study() {
    echo "Learn to drive \n";
    }
    }
    $student = new Student();
    $student->study();
    $student->age();
    $student->driving();
    Learn to drive
    i am 18 years old
    driving BMW
  • Student類通過繼承Person,有了age方法,通過組合Drive,有了driving方法和屬性carName。

如果Trait、基類和本類中都存在某個同名的屬性或者方法,最終會保留哪一個呢

<?php
trait Drive {
public function hello() {
echo "hello 周伯通\n";
}
public function driving() {
echo "周伯通不會開車\n";
}
}
class Person {
public function hello() {
echo "hello 大家好\n";
}
public function driving() {
echo "大家都會開車\n";
}
}
class Student extends Person {
use Drive;//trait 的方法覆蓋了基類Person中的方法,所以Person中的hello 和driving被覆蓋
public function hello() {
echo "hello 新學員\n";//當方法或屬性同名時,當前類中的方法會覆蓋 trait的 方法,所以此處hello會覆蓋trait中的hello
}
}
$student = new Student();
$student->hello();
$student->driving();
hello 新學員
周伯通不會開車
  • 當方法或屬性同名時,當前類中的方法會覆蓋 trait的 方法,而 trait 的方法又覆蓋了基類中的方法。

如果要組合多個Trait,通過逗號分隔 Trait名稱:

use Trait1, Trait2;
<?php
trait Hello {
public function sayHello() {
echo "Hello 我是周伯通\n";
}
}
trait World {
use Hello;
public function sayWorld() {
echo "hello world\n";
}
abstract public function getWorld();
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
public static function doSomething() {
echo "Doing something\n";
}
}
class HelloWorld {
use World;
public function getWorld() {
return 'do you get World ?';
}
}
$Obj = new HelloWorld();
$Obj->sayHello();
$Obj->sayWorld();
echo $Obj->getWorld() . "\n";
HelloWorld::doSomething();
$Obj->inc();
$Obj->inc();
Hello 我是周伯通
hello world
do you get World ?
Doing something
1
2

語言中得多型

  • 多型準確的含義是:同一類的物件收到相同訊息時,會得到不同的結果。而這個訊息是不可預測的。多型:顧名思義,就是多種狀態,也就是多種結果
  • 多型性是一種通過多種狀態或階段描述相同物件的程式設計方式。它真正的意義在於:實際開發中,只要關心一個介面或基類的程式設計,而不必關心一個物件所屬於的具體類

案例

  • 通過判斷傳入的物件所屬的類不同來呼叫其同名方法來實現'多型'
    <?php
    class employee{
    protected function working(){
    echo '本方法需要過載才能執行';
    }
    }
    class teacher extends employee{
    public function working(){
    echo '教書';
    }
    }
    class coder extends employee{
    public function working(){
    echo '敲程式碼';
    }
    }
    function doprint($obj){
    //get_class 獲取當前物件呼叫的類名
    if(get_class($obj)=='employee'){
    echo 'Error';
    }else{
    $obj->working();
    }
    }
    doprint(new teacher());
    doprint(new coder());
    doprint(new employee());
  • 通過介面實現多型
    <?php
    interface employee{
    public function working();
    }
    class teacher implements employee{
    public function working(){
    echo '教書';
    }
    }
    class coder implements employee{
    public function working(){
    echo '敲程式碼';
    }
    }
    function doprint(employee $i){
    $i->working();
    }
    $a=new teacher;
    $b=new coder;
    doprint($a);
    doprint($b);
  • 在這段程式碼中,doprint函式的引數為一個介面型別的變數,符合'同一型別,不同結果'這一條件,具備多型的一般特徵。

總結

  • 多型指同一類物件在執行時的具體化。
  • php語言是弱型別的,實現多型更簡單,更靈活。
  • 型別轉換不是多型。
  • php中父類和子類看作'繼父'和'繼子'關係,他們存在繼承關係,但不存在血緣關係,因此子類無法向上轉型為父類,從而失去多型最典型的特徵。
  • 多型的本質就是 if -- else -- ,只不過實現的層次不同。

面向介面程式設計並不是一種新的程式設計正規化,在三大正規化中並沒有面向介面。這裡是俠義的介面,即 interface 關鍵字。廣義的介面可以是任何一個對外提供服務的出口,比如提供資料傳輸的USB介面,淘寶網對其他網站開發的支付寶介面。

介面的作用

定義

  • 介面定義一套規範,描述一個‘物’的功能,要求如果現實中的“物”想成為可用,就必須實現這些基本的功能。通俗的來說:對於實現我的所有類,看起來都應該像我這個樣子。
  • 採用一個特定介面的所有程式碼都必須知道對於那個介面會呼叫什麼方法;介面常用來作為類和類之間的一個'協議'。介面是抽象類的實體,介面中所有方法都是抽象的,沒有一個程式實體;介面除了可以包含方法外,還能包含常量。

案例

  • 我們用介面描述發動機,要求介面必須又“run”功能,至於如何實現(摩托車或者汽車),應該是什麼樣(前驅還是後驅),都不是介面所要關心得,因為介面為抽象而生。
  • 介面作為質檢總局,要判斷這輛車是否合格,按照“介面”得定義規則一條條驗證,這輛車不能“run”,那麼就為廢品,不能通過驗收;但是,如果,如果汽車實現了介面中原沒有不存在得方法music,並不認為有什麼錯誤,因為它不在我們得檢查範圍。
  • 介面就是一種契約,在程式中,介面的方法必須被全部實現,否在不能“通過驗收”。
  • 我們首先定義了一個機動車的介面,裡面含有所有類必須的方法也就是發動機的功能。
  • 然後,我們定義了一個實現這個介面並新增了飛翔方法的飛機類,還有一個實現介面並沒有實現飛翔的機動車類,最後,我們定義了一個機器檢測類對所有定義的類進行檢測(用型別約束指定要測試的是mobile這個介面),檢測定義的類是否有飛翔方法,沒有則報錯。

案例總結

  • 這種構念實際上是錯誤的,不符合介面的語義。但是在php裡,只關心是否實現這個方法,而並不關心介面語義是否正確。
  • 按理說,介面應該是起一個強制規範和契約的作用,但是這裡對介面的約束並沒有起效,也打破了契約,對監測站這個類(machine)的行為失去控制。正常來說,如果你打破了我們之間的契約,行為變得無法控制,這是非法的。這符合邏輯,也符合現實世界,在才真正起到介面作為規範的作用了。
  • 介面不僅規範了介面的實現者,還規範介面的執行者,不允許呼叫介面中本不存在的方法。當然這並不是說一個類如果實現了介面,就只能實現介面中才有的方法,而是說,如果針對的是介面,而不是具體的類,則只能按照介面的約定辦事。這樣的語法規定對介面的使用是有利的,讓程式更健壯。從這個角度來說,為了保證介面的語義,通常一個介面的實現類僅實現該介面所具有的方法,做到專一,當然也不是一場不變的。

案例的思考

  • 從上面的例子可以看出,php 裡介面作為規範和契約的作用打了折扣。根據這個例子,我們很自然的可以想到介面的使用場合,比如資料庫操作,快取實現等。而不用關心我們所面對的資料庫是MySql 還是 Oracle ,只關心面向Database介面進行具體業務邏輯相關的程式碼。
  • 在這裡Database 和 employee 一樣,針對這個介面實現就好了。快取功能也是一樣,我們不關心快取時記憶體快取還是檔案快取,或者時資料庫快取,只關注它是否實現了cache介面,只要它實現了 cache 介面,就實現了寫入快取和讀取快取中的資料以及清楚快取這幾個關鍵的功能點。

php介面的思考

簡介

  • 由於 php 是弱型別,且強調靈活。並不推薦大規模的使用介面。而僅在部分"核心"程式碼中使用介面,因為php中的介面已經失去很多介面應該具有的語義。從語義上考慮,可以更多的使用抽象類。
  • php5 對物件導向的特性做了許多加強,其中有一個 SPL (標準php庫) 的嘗試。
  • SPL 中實現一些介面,其中最主要的就是 Iterator 迭代器介面,通過這個介面,就能使物件能夠用於foreach結構,從而在使用形式上比較統一。
  • 介面都是對多重繼承的一種變相的實現,在繼承的時候,我們也可以使用traits來實現。

總結

  • 介面作為一種規範和契約存在。作為規範,介面應性該保證可用性;作為契約,介面應該保證可控性。
  • 介面只是一個宣告,一旦使用 interface 關鍵字,就必須實現它。可以由自己來實現(外部介面),也可以由系統實現(內部介面)。介面本身什麼都不做,但是它可以告訴我們它能做什麼。
  • php 中的介面存在兩個不足,一是沒有契約限制,二是確實足夠多的內部介面.
  • 介面的各種應用很靈活,設計模式中也有很大一部分是圍繞介面展開的。

補充

抽象類

  • 抽象類是指在 class 前加了 abstract 關鍵字且存在抽象方法(在類方法function關鍵字前加了abstract關鍵字)的類。
  • 抽象類不能被直接例項化。抽象類中只定義(或部分實現)子類需要的方法。子類可以通過繼承抽象類並通過實現抽象類中的所有抽象方法,使抽象類具體化。
  • 如果子類需要例項化,前提是它實現了抽象類中的所有抽象方法。如果子類沒有全部實現抽象類中的所有抽象方法,那麼該子類也是一個抽象類,必須在class前面加上abstract關鍵字,並且不能被例項

抽象類和介面的區別

相同點

  • 兩者都是抽象類,都不能例項化。
  • interface實現類及abstract class的子類都必須要實現已經宣告的抽象方法。

不同點

  • interface需要實現,要用implements,而abstract class需要繼承,要用extends。
  • 一個類可以實現多個interface,但一個類只能繼承一個abstract class。
  •  interface強調特定功能的實現,而abstract class強調所屬關係。
  • 儘管interface實現類及abstract class的子類都必須要實現相應的抽象方法,但實現的形式不同。interface中的每一個方法都是抽象方法,都只是宣告的 (declaration, 沒有方法體),實現類必須要實現。而abstract class的子類可以有選擇地實現。這個選擇有兩點含義:a) abstract class中並非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子類必須實現。那些沒有abstract的方法,在 abstract class中必須定義方法體;b) abstract class的子類在繼承它時,對非抽象方法既可以直接繼承,也可以覆蓋;而對抽象方法,可以選擇實現,也可以留給其子類來實現,但此類必須也宣告為抽象類。既是抽象類,當然也不能例項化。
  • abstract class是interface與class的中介。abstract class在interface及class中起到了承上啟下的作用。一方面,abstract class是抽象的,可以宣告抽象方法,以規範子類必須實現的功能;另一方面,它又可以定義預設的方法體,供子類直接使用或覆蓋。另外,它還可以定義自己的例項變數,以供子類通過繼承來使用。
  • 介面中的抽象方法前不用也不能加abstract關鍵字,預設隱式就是抽象方法,也不能加final關鍵字來防止抽象方法的繼承。而抽象類中抽象方法前則必須加上abstract表示顯示宣告為抽象方法。
  • 介面中的抽象方法預設是public的,也只能是public的,不能用private,protected修飾符修飾。而抽象類中的抽象方法則可以用public,protected來修飾,但不能用private。

interface的應用場合

  • 類與類之間需要特定的介面進行協調,而不在乎其如何實現。
  • 作為能夠實現特定功能的標識存在,也可以是什麼介面方法都沒有的純粹標識。
  • 需要將一組類視為單一的類,而呼叫者只通過介面來與這組類發生聯絡。
  • 需要實現特定的多項功能,而這些功能之間可能完全沒有任何聯絡。

abstract class 的應用場合

  • 在既需要統一的介面,又需要例項變數或預設的方法的情況下,就可以使用它
  • 定義了一組介面,但又不想強迫每個實現類都必須實現所有的介面。可以用abstract class定義一組方法體,甚至可以是空方法體,然後由子類選擇自己所感興趣的方法來覆蓋
  • 某些場合下,只靠純粹的介面不能滿足類與類之間的協調,還必需類中表示狀態的變數來區別不同的關係。abstract的中介作用可以很好地滿足這一點。
  • 規範了一組相互協調的方法,其中一些方法是共同的,與狀態無關的,可以共享的,無需子類分別實現;而另一些方法卻需要各個子類根據自己特定的狀態來實現特定的功能。
    <?php
    interface mobile{
    public function run();//驅動方法
    }
    class plain implements mobile{
    public function run(){
        echo '我是飛機';
    }
    public function fly(){
        echo '飛翔';
    }
    }
    class car implements mobile{
    public function working(){
        echo '我是汽車';
    }
    }
    class machine{
    function demo(mobile $a){
        //mobile介面沒有這個方法
        //尋找可以飛翔的機器/類
        $a->fly(); 
    }
    }
    $obj=new machine;
    $obj->demo(new plain()); //執行成功
    $obj->demo(new car()); //執行失敗
    abstract class A
    {
    /** 抽象類中可以定義變數 */
    protected $value1 = 0;
    private $value2 = 1;
    public $value3 = 2;
    /** 也可以定義非抽象方法 */
    public function my_print()
    {
        echo "hello,world/n";
    }
    /**
     * 大多數情況下,抽象類至少含有一個抽象方法。抽象方法用abstract關鍵字宣告,其中不能有具體內容。
     * 可以像宣告普通類方法那樣宣告抽象方法,但是要以分號而不是方法體結束。也就是說抽象方法在抽象類中不能被實現,也就是沒有函式體“{some codes}”。
     */
    abstract protected function abstract_func1();
    abstract protected function abstract_func2();
    }
    abstract class B extends A
    {
    public function abstract_func1()
    {
       echo "implement the abstract_func1 in class A/n";
    }
    /** 這麼寫在zend studio 8中會報錯*/
    //abstract protected function abstract_func2();
    }
    class C extends B
    {
    public function abstract_func2()
    {
       echo "implement the abstract_func2 in class A/n";
    }
    }
    Class B extends A{};
  • 物件導向程式設計中物件被賦予了自省的能力,而自省的過程就是反射
  • 反射,直觀理解就是根據到達地找到出發地和來源。比方說,我給你一個光禿禿的物件,我可以僅僅通過這個物件就能知道它所屬的類,擁有的哪些方法。
  • 反射指在 PHP 執行狀態中,擴充分享 PHP 程式,匯出和提出關於類,方法,屬性,引數等詳細資訊,包括註釋。這種動態獲取資訊已經動態呼叫物件方法的功能稱為反射。

如何使用反射Api

案例

<?php
class person{
    public $name;
    public $gender;
    public function say(){
        echo $this->name,"\tis",$this->gender,"\r\n";
    }
    public function __set($name, $value)
    {
        echo "Setting $name to $value \r\n";
        $this->name=$value;
    }
    public function __get($name)
    {
        if (!isset($this->name)) {
            echo "未設定";
            $this->$name="正在設定預設值";
        }
        return $this->name;
    }
}
$student=new person();
$student->name ='Tom';
$student->gender="male";
$student->age=24;
  • 現在,要獲取這個 student 物件的方法和屬性
    //獲取物件屬性列表
    $reflect=new ReflectionObject($student);
    $props =$reflect->getProperties();
    foreach ($props as $prop) {
    print $prop -> getName() ."\n";
    }
    //獲取物件方法列表
    $m=$reflect->getMethods();
    foreach ($m as $prop) {
    print $prop->getName()."\n";
    }
  • 也可以不用反射Api ,使用class函式,返回物件屬性的關聯陣列及相關資訊
    //返回物件屬性的關聯陣列
    var_dump(get_object_vars($student));
    //類屬性
    var_dump(get_class_vars($student));
    //返回由類的方法名組成的陣列
    var_dump(get_class_methods(get_class($student)));
  • 假如這個物件是從其他頁面傳過來的,獲取它屬於哪個類
    //獲取物件屬性列表所屬的類
    echo get_class($student);
  • 反射Api的功能顯然更強大,甚至能還原這個類的原型,包括方法的訪問許可權
    <?php
    //反射獲取類的原型
    $obj =new ReflectionClass('person');
    $className= $obj->getName();
    $Methods = $Properties = array();
    foreach ($obj->getProperties() as $v) {
    $Properties[$v->getName()]=$v;
    }
    foreach ($obj->getMethods() as  $v) {
    $Methods[$v->getName()]=$v;
    }
    echo "class {$className}\n{\n";
    //ksort  按照鍵名對關聯陣列進行升序排序
    is_array($Properties) && ksort($Properties);
    foreach ($Properties as $k => $v) {
    echo "\t";
    echo $v->isPublic()?'public':'',$v->isPrivate()?'private':'',
    $v->isProtected()?'protected':'',
    $v->isStatic()?'statoc':'';
    echo "\t{$k}\n";
    }
    echo "\n";
    if(is_array($Methods)) ksort($Methods);
    foreach ($Methods as $k => $v) {
    echo "\tfunction {$k}(){}\n";
    }
    echo "}\n";
  • 輸出

    class oersin{
    public gender 
    public name
    
    function __get(){}
    function __set(){}
    function say(){}
    }

PHP 手冊中關於反射的 API 更有幾十個,可以說,反射完整地描述了一個類或者物件的原型。反射不用於用於類和物件,還可以用於函式,擴充套件模組,異常等。

反射的作用

反射可以用於文件生成,因此可以用它對檔案裡的類進行掃描,逐個生成描述文件。【探究類的內部結構】

使用反射做hook實現外掛功能/動態代理

<?php
class mysql{
    function connect($db){
        echo "連線到資料庫${db[0]}\r\n";
    }
}

class sqlproxy{
    private $target;
    function __construct($tar)
    {
        $this->target[]=new $tar();
    }
    function __call($name, $args)
    {
        foreach ($this->target as $obj) {
            $r=new ReflectionClass($obj);
            if ($method= $r->getMethod($name)) {
                if ($method->isPublic() && !$method->isAbstract()) {
                    echo "方法前攔截記錄LOG\r\n";
                    $method->invoke($obj,$args);
                    echo "方法後攔截\r\n";
                }
            }
        }
    }
}
$obj=new sqlproxy('mysql');
$obj->connect('member');
  • 這裡,真正的操作類是mysql類,但是sqlproxy 類實現了根據動態傳入引數,代替實際的類執行,並且在方法執行前後進行攔截,並且動態的改變類中的方法和屬性,這就是簡單的動態代理。

總結

  • 平時開發中,真正用到反射的地方並不多:一個是對物件進行調式,另一個是獲取類的資訊,在MVC 和外掛開發中,使用反射很常見,但是反射的消耗很大,在可以找到代替方案的情況下,就不要濫用。
  • PHP 有Token 函式,可以通過這個機制實現一些反射功能。從簡單靈活的角度講,使用已反射API 是可取的。
  • 很多時候,善於反射能保持程式碼的優雅和簡潔,但反射也會破壞類的封裝性,因為反射可以使原本不應該暴露的方法或屬性被強制暴露出來,這既是優點也是缺點。

名稱空間

  • 簡介:是一種封裝事務的方法
  • 案例:用來給目錄下的相關角色分組,在平時一個目錄下不能存在相同的檔名,也不能在目錄外來訪問這個目錄裡的檔案,名稱空間的引用就是為了解決此類問題
  • 主要解決的問題:使用者編寫的程式碼和php內部的類/函式/常量/第三方類/常量之間的名字相沖突;為很長的識別符號名稱(類名),建立別名,提高程式碼的可讀性

自動載入

目的

  • 為了能使用一個類,需要先載入這個了類所在的檔案。

說明

  • bool spl_autoload_register ([ callback $autoload_function ] ) 註冊 __autoload() 函式
  • 將函式註冊到 SPL __autoload 函式棧中。如果該棧中的函式尚未啟用,則啟用它們。
  • 如果在你的程式中已經實現了 autoload 函式,它必須顯式註冊到 autoload 棧中。因為 spl_autoload_register() 函式會將Z end Engine 中的__autoload函式取代為 spl_autoload() 或 spl_autoload_call()。

誕生

  • php5.1.2 之前,只有 include/require 載入;
  • php5.1.2 之後,提供了自動載入的函式 spl_autoload_register() 函式

引數

  • autoload_function 欲註冊的自動裝載函式。沒有提供,則自動使用 autoload 的預設實現函式 spl_autoload();
  • throw 設定了 autoload_function 無法成功註冊時,spl_autoload_register() 是否丟擲異常;
  • prepend 如果是 true,spl_autoload_register() 會新增函式到佇列之首,而非佇列之尾。(當一個專案裡有多個自動載入器時,這個引數就會很有用)

注意

如果使用了名稱空間,那麼由於在使用名稱空間的時候,class類名中帶了名稱空間限制符,在解析並載入時需要注意並特殊處理。

  •  正是有了名稱空間和自動載入,才讓php發展出了composer包機制,從而可以在任意的引入第三方類庫,促使php走向更大規模的企業協作開發。

__autoload

  • 這是一個自動載入函式,在PHP5中,當我們例項化一個未定義的類時,就會觸發此函式。
    PHP 核心技術   --​​​​​​​​物件導向
  • 執行index.php後正常輸出hello world。在index.php中,由於沒有包含printit.class.php,在例項化printit時,自動呼叫__autoload函式,引數$class的值即為類名printit,此時printit.class.php就被引進來了。

spl_autoload_register()

PHP 核心技術   --​​​​​​​​物件導向

  • autoload 換成 loadprint 函式。但是 loadprint 不會像autoload 自動觸發,這時spl_autoload_register() 就起作用了,它告訴PHP碰到沒有定義的類就執行 loadprint()。

spl_autoload_register() 呼叫靜態方法

PHP 核心技術   --​​​​​​​​物件導向

備註

SPL是Standard PHP Library(標準PHP庫)的縮寫。它是PHP5引入的一個擴充套件庫,其主要功能包括autoload機制的實現及包括各種Iterator介面或類。SPL autoload機制的實現是通過將函式指標autoload_func指向自己實現的具有自動裝載功能的函式來實現的。SPL有兩個不同的函式spl_autoload, spl_autoload_call,通過將autoload_func指向這兩個不同的函式地址來實現不同的自動載入機制。

PHP 核心技術   --​​​​​​​​物件導向

如果同時用spl_autoload_register註冊了一個類的方法和__autoload函式,那麼,會根據註冊的先後,如果在第一個註冊的方法或函式里載入了類檔案,就不會再執行第二個被註冊的類的方法或函式。反之就會執行第二個被註冊的類的方法或函式。

PHP 核心技術   --​​​​​​​​物件導向

php 中錯誤級別

php錯誤就是會使指令碼執行不正常得情況

  • deprecated 是最低階得錯誤,表示'不推薦,不建議'。如再 php5 中使用ereg系列得正則匹配函式就會報此類錯誤。在種錯誤一般由於使用不推薦得,過時的函式或語法造成的。雖然不影響PHP 的正常流程,但一般情況下建議修正。
  • notice 這種錯誤一般告訴你語法中存在不當的地方。如使用變數但是未定義就會報此錯。最常見的,陣列索引是字元時沒有加引號,php 就視為一個常量,先查詢常量,找不到再視為變數。雖然 PHP 是指令碼語言,語法要求不嚴,但是仍然建議對變數進行初始化。這種錯誤不影響 PHP 正常流程。
  • warning 是級別計較高的錯誤,在語法中出現很不恰當的情況時才會出現此錯誤,比如函式引數不匹配,這種級別的錯誤會導致得不到預期結果,故需要修改程式碼。
  • fetal error 是更高階別的錯誤,是致命錯誤,直接導致 PHP 流程結束,後面的程式碼不再執行。
    prase error 最高階別錯誤,是語法解析錯誤。

PHP 的錯誤處理機制

PHP 有一套錯誤處理機制,可以使用 set_error_handle 接管 PHP 錯誤處理,也可以使用 trigger_error函式主動丟擲一個錯誤。

set_error_hadler(error_function,error_types) 函式
說明

  • 設定使用者自定義的錯誤處理函式。函式用於建立執行期間的使用者的自己的錯誤處理方法。它需要先建立一個錯誤處理函式,然後設定錯誤級別。

引數

  • error_function:規定發生錯誤時執行的函式。必需
  • error_types:規定在哪個錯誤報告級別會顯示使用者定義的錯誤。可選

案例

如果在指令碼執行前發生錯誤,由於在那時自定義程式還沒有註冊,因此就不會用到這個自定義錯誤處理程式,這先實現一個自定義的錯誤處理函式。

function customError($error,$errstr,$errfile,$errline){
    echo "錯誤程式碼:</b>[{$error}]${errstr}\r\n";
    echo "錯誤所在的程式碼行:{$errline}檔案{$errfile}\r\n";
    echo "PHP版本",PHP_VERSION,"(",PHP_OS,")\r\n";
}
//E_ALL 所有的錯誤 E_STRICT 
set_error_handler("customError",E_ALL | E_STRICT);
$a=array('o'=>2,3,6,7);
echo $a[o];
  • 在這個函式裡,可以對錯誤的詳情進行格式化輸出,也可以做任何想要做的事情,比如判斷當前環境和許可權給出不同的錯誤提示。也可以使用error_log 函式將錯誤記入 log 檔案,可以細化處理,針對 $error 的不同進行對應的處理。
  • 自定義的錯誤處理函式一定要有這四個輸入變數 $error $errstr $errfile $errline
  • error 是一組常量,代表錯誤的等級,同時也有一組整數和其對應,但一般使用字串表示,這樣語義更好一點。比如E_WARNING,其二進位制掩碼為4,表示警告資訊。
  • 接下來,就是將這個函式作為回撥函式傳遞給set_error_handler。這樣就能接管php 原生的錯誤處理函式了,要注意的是,這種託管方式並不能託管所有種類的錯誤,如E_ERROR,E_PARSE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,E_COMPILE_WARNING,已經E_STRICT中的部分。這些錯誤將會以原始的方式顯示或不顯示。

案例二

在 PHP 異常中,異常處理機制是有限的,無法自動丟擲異常,必須手動進行,並且內建異常有限。PHP 把許多異常看作錯誤,這樣可以把這些“異常”像錯誤一樣用set_error_handle 接管,進而主動丟擲異常。

function customError($errno,$errstr,$errfile,$errline){
    //自定義錯誤處理時,手動丟擲異常
    throw new Exception($level.'|'.${errstr});
}
set_error_handler("customError",E_ALL|E_STRICT);
try{
    $a=5/0;
}
catch(Exception $e)
{
    echo "錯誤資訊:",$e->getMessage();
}
  • 這樣就能捕獲到異常和非致命得到錯誤。

  • 存在的問題:必須依靠程式設計師自己來掌握自己來掌握對異常的處理,對於異常高發區,敏感區,如果處理的不好,就會導致前面所提到的業務資料不一致的問題。

  • 優點:可以獲得程式執行時的上下文資訊,以進行鍼對性的補救。

fetal error 這樣的錯誤雖然捕獲不到,也無法在發生此錯誤後恢復流程處理,但是還是可以使用一些特殊方法對這種錯誤進行處理的。這需要用到一個函式--register_shutdown_function,此函式會在PhP 程式終止或者 die 時觸發一個函式,給 PHP 來一個短暫的”迴光返照“。

PHP7 對異常機制的改進

  • PHP 早期只有錯誤機制,在 PHP5 引入異常機制後,導致錯誤機制和異常機制並行,並且異常機制並不完善。
  • 實際上從 PHP5 開始, PHP 就開始了對 PHP 異常機制進行完善。比如,從PHP5.5 開始,PHP的異常引入了 finally 語法,可以像 Java 一樣在 finally 語句塊中進行最終的異常處理。
  • PHP7 實現了一個全域性的 Throwable 介面,原來 Exception 和部分 Error 都實現了這個介面(interface),以介面的方式定義了異常的繼承結構。
  • PHP7 改變了大多數錯誤的報告方式。不同於傳統 (PHP5) 的錯誤報告機制,使得大多數錯誤會作為 Error 異常丟擲。
  • 這種 Error 異常可以像 Exception 異常一樣被第一個匹配的 try/catch 塊捕獲。如果沒有匹配的catch 塊,則呼叫異常處理函式(事先通過set_exception_handler()註冊)進行處理。如果尚未註冊異常處理函式,則按傳統方式處理:被報告為一個致命錯誤(Fatal Error )。也就是說,PHP7 中更多的 Error 變為可捕獲的 Exception返回給開發者,如果不進行捕獲則為 Error,如果捕獲就變為一個可在程式內處理的 Exception。PHP7 被處理更加方便,讓開發者對程式的掌握能力更強。因為在預設情況下,Error 會直接導致程式中斷,而PHP7 則提供捕獲並且處理的能力,讓程式繼續執行下去,為程式設計師提供更靈活的選擇。

案例

  • 函式不存在的情況,之前時需要使用function_exist來判斷,如果不判斷就直接呼叫,以錯誤的形式給出。
  • 嘗試捕獲函式不存在的異常,PHP 將函式不存在作為錯誤丟擲了,並沒有作為異常捕獲。
    try{
    hell();
    }catch(Exception $e){
    echo $e->getMessage();
    echo '捕獲到了嘛?';
    }finally{
    echo 'finally..';
    }
  • 使用Throwable捕獲函式不存在的異常,可以捕獲undefined function 這種錯誤並將其轉化為異常處理了。
    try{
    hello();
    }catch(Throwable $e){
    echo $e->getMessage(),PHP_EOL;
    echo '捕獲到了嘛?',PHP_EOL;
    }finally{
    echo 'finally..';
    }
  • 嘗試捕獲除零異常
    try{
    5/0;
    }catch(Throwable $e){
    echo $e->getMessage(),PHP_EOL;
    echo '捕獲到了嘛?',PHP_EOL;
    }finally{
    echo 'finally..';
    }
  • 只有當對0進行取模的時候才會丟擲 DivisionByZeroError 錯誤,我們把Throwable 換成 DivisionByZeroError 也是一樣的結果。事實上,對應於除0這種操作,PHP 並不會丟擲異常,而是返回一個 INF 值,並且同時出發一條 E_WARNING。

可以看出,php7 的異常體系已經比較接近java了,但是還不夠完善,只有部分錯誤實現了Throwable介面。

相關文章