🐘 PHP 物件導向

🐘 PHP 物件導向
Photo by Ben Griffiths / Unsplash

魔術方法 (Magic Methods)

魔術方法是在類別中具有特殊名稱的方法,它們在特定的時機被自動呼叫,這些時機通常與類別的操作和生命週期相關。一個常用的魔術方法是建構子(Constructor)。

建構子 (Constructor)

建構子是一個特殊的方法,它在類別被實例化(物件被建立)時自動被呼叫。建構子通常用於初始化物件的屬性或進行一些初始設定。

class MyClass {
    public function __construct() {
        echo "物件已被建立!";
    }
}

$object = new MyClass(); // 輸出:物件已被建立!

Custom Methods

自訂方法是我們根據類別的需求自行定義的方法。其中的一個常見技巧是使用 return $this,這使得方法可以被鏈式呼叫(Method Chaining)。

鏈式呼叫(Method Chaining) 指在同一個物件上連續呼叫多個方法,而不需要每次呼叫都賦值給一個變數。

class Calculator {
    private $result = 0;

    public function add($value) {
        $this->result += $value;
        return $this; // 返回物件本身以便鏈式呼叫
    }

    public function subtract($value) {
        $this->result -= $value;
        return $this; // 返回物件本身
    }

    public function getResult() {
        return $this->result;
    }
}

$calculator = new Calculator();
$result = $calculator->add(10)->subtract(5)->getResult();
echo $result; // 輸出:5

在這個例子中,addsubtract 方法返回了物件本身 $this,這樣它們可以連續呼叫,而不需要每次呼叫都賦值給一個變數。

建構子引數的推廣 (Constructor Property Promotion)

在 PHP 8.0 及以上版本中,引入的新特性。

當使用建構子引數推廣之前,我們需要手動為每個類別的屬性指定建構子引數,然後在建構子中賦值。以下是一個使用前的範例:

class User {
    private string $name;
    private string $email;
    private ?string $phoneNumber;

    public function __construct(string $name, string $email, ?string $phoneNumber) {
        $this->name = $name;
        $this->email = $email;
        $this->phoneNumber = $phoneNumber;
    }
}

// 建立使用者物件
$user = new User('John Doe', 'john@example.com', '123-456-7890');

在上面的範例中,我們需要手動宣告建構子的引數,然後再將這些引數賦值給類別的屬性。

使用建構子引數推廣後的範例:

class User {
    public function __construct(
        private string $name,
        private string $email,
        private ?string $phoneNumber = null
    ) {
        // 建構子的內容
    }
}

// 建立使用者物件
$user = new User('John Doe', 'john@example.com', '123-456-7890');

在這個例子中,建構子的引數列表直接宣告瞭類別的屬性。$name$email 屬性是必須的,而 $phoneNumber 屬性是可選的(使用 ?string 表示可以為空)。在建構子中,不需要再手動賦值給這些屬性,PHP 會自動處理。這樣,我們可以簡化建構子的定義,使程式碼更加簡潔和易讀。

存取修飾符 (Access Modifiers)

public(公共)

public 修飾符表示該成員(屬性或方法)在類別內外部均可被訪問。這意味著無論是在類別內部、子類別、還是外部程式碼中,都可以直接訪問這個成員,它是預設值

class MyClass {
    public $publicProperty;

    public function publicMethod() {
        // 可以被任何地方訪問
    }
}

protected (受保護)

protected 修飾符表示該成員只能在定義它的類別內部子類別中被訪問。外部程式碼無法直接訪問受保護的成員。

class MyClass {
    protected $protectedProperty;

    protected function protectedMethod() {
        // 只能在類別內部和子類別中訪問
    }
}

private(私有)

private 修飾符表示該成員指能在定義它的類別內部被訪問,對於外部程式碼和子類別都是不可見的。

class MyClass {
    private $privateProperty;

    private function privateMethod() {
        // 只能在類別內部訪問
    }
}

Null-Safe Operator

PHP 8.0 版本中,引入了 Null-Safe Operator(空安全運算子),也稱為 Null-Safe Operator(?->),它是一種簡化程式碼的語法糖,用於在操作可能為 null 的物件時避免產生錯誤。

在舊版本的 PHP 中,如果你試圖在一個可能為 null 的變數上呼叫方法或訪問屬性,你必須先檢查該變數是否為 null,以避免產生 Fatal Error。例如:

if (isset($object)) {
    $value = $object->getValue();
} else {
    $value = null;
}

使用 Null-Safe Operator,你可以將上面的程式碼簡化為:

$value = $object?->getValue();

這樣,如果$object為 null,表示式的結果將會是 null,而不會丟擲錯誤。Null-Safe Operator 只能用於物件的方法呼叫和屬性訪問,不能用於陣列和函式呼叫。這個語法糖使得處理可能為 null 的物件更加方便和簡潔。

這裡是一個使用 Null-Safe Operator 的例子:

class MyClass {
    public function getValue(): ?string {
        // 返回一個可能為null的值
        return "Hello, World!";
    }
}

$object = null; // 或者一個MyClass的實例

// 使用Null-Safe Operator呼叫可能為null的物件方法
$value = $object?->getValue();

echo $value; // 輸出 "Hello, World!" 或者 null(如果$object為null的話)

在這個例子中,無論$object是一個 MyClass 的實例還是 null,都不會產生錯誤,因為 Null-Safe Operator 會自動處理 null 情況。

Namespace

在 PHP 中,名稱空間(Namespace)是一種用來解決在不同類別之間命名衝突的機制。名稱空間可以是可選的,但在大型應用程式中,良好的組織和結構是非常重要的。以下是有關 PHP 名稱空間的一些基本概念和最佳實踐:

在程式檔案的最前面,你可以使用namespace宣告定義一個名稱空間。例如:

declare(strict_types=1);
namespace APP; // after strict_types

class MyClass {
    // class implementation
}

如果你的應用程式需要更深的組織結構,你可以使用巢狀名稱空間(Nested Namespace)。例如,如果你有一個銀行相關的類別,你可以這樣定義:

namespace APP\Bank;

class BankAccount {
    // class implementation
}

當你在不同的名稱空間中使用類別時,你可以使用use關鍵字。例如,如果你要使用APP名稱空間中的Account類別,你可以這樣做:

# method1
$myAccount = new App\Account();

# method2
use APP\Account;

$myAccount = new Account();

在某些情況下(當前在 namespace APP 下),你可能需要使用 global namespace 中的類別。你可以使用斜線(\)字首來表示全域性名稱空間。例如:

namespace App;

# method1
new \DateTime();

# method2
use DateTime;
new DateTime();

有時候,你可能會想要為一個長命名的類別使用別名(Alias)。這樣可以使你的程式碼更簡潔易讀。例如:

use DateTime as DT;

new DT();

如果你需要引入多個名稱空間中的類別,你可以使用逗號(,)分隔它們。例如:

use APP\Account;
use APP\SocialMedia;

// 改寫
use APP\{Account, SocialMedia};

使用名稱空間和use語句,讓你可以更好地組織你的程式碼,避免命名衝突,使程式碼更易於維護和擴充套件。

Autoloading Classes

使用自動載入機制(例如 spl_autoload_register 函式)主要是為了方便管理多個類別,特別是當每個類別都儲存在獨立的檔案中時。這樣,你可以根據類別的名稱空間類別名稱來自動載入相應的檔案。

然而,如果在同一個檔案中既有類別定義又有函式定義,那麼最好還是在需要的時候手動引入這些檔案。這是因為自動載入機制主要是為了類別而設計的,而不是用來處理函式。如果在自動載入的過程中引入了一個檔案,裡面除了類別定義還包含其他程式碼(如函式定義),可能會導致不必要的程式碼複雜性和不易讀懂的程式碼。

因此,最佳的做法是:

  1. 將每個類別儲存在獨立的檔案中。 這樣可以使用自動載入機制,方便地根據類別的名稱空間和類別名稱來自動載入相應的檔案。

  2. 如果檔案中既有類別定義又有函式定義,需要時手動引入。 如果檔案中有其他不屬於類別的程式碼(例如函式或常數),最好在需要使用這些程式碼的地方手動引入相應的檔案。這樣可以確保程式碼的可讀性和可維護性。

以下是一個範例,演示瞭如何使用自動載入機制來載入類別,並在需要的時候手動引入檔案中的函式:

// require_once 'APP/Account.php';
// require_once 'APP/SocialMedia.php';
// require_once 'APP/..';

spl_autoload_register(function($class){
    $formattedClass = str_replace("\\", "/", $class);
    $path = "{$formattedClass}.php";
    require_once $path;
});

// 使用自動載入機制載入類別
$account = new \APP\Account();

// 手動引入檔案中的函式
require_once 'path/to/file-with-functions.php';

// 現在你可以使用檔案中定義的函式
myFunction();

常數 (Constants)

在 PHP 中,常數是在類別中定義的不可改變的值,這些值在整個類別中都是固定的。在類別中定義常數使用 const 關鍵字,不可使用 define() 函式

class Account {
    const INTEREST_RATE = 0.05;

    // 其他類別成員和方法
}

// 存取常數的方式
echo Account::INTEREST_RATE;

在這個範例中,INTEREST_RATEAccount 類別的常數,它的值不可更改,可以在整個應用程式中使用,提高了程式碼的可讀性和一致性。

靜態成員 (Static Members)

靜態屬性 (Static Properties)

靜態屬性是指在整個類別中共用的屬性,可以被所有實例共享。然而,靜態屬性的值可以被修改,因此在使用時需要小心 (能不要用就不要用)。

class Account {
    public static $count = 0;

    public function __construct() {
        self::$count++;
    }

    public static function getCount() {
        return self::$count;
    }
}

// 使用靜態屬性
$account1 = new Account();
$account2 = new Account();
echo Account::$count; // 輸出:2

在這個例子中,$count 是一個靜態屬性,用來記錄 Account 類別的實例數量。每次建立一個新的 Account 物件時,$count 的值會自動增加。

靜態方法 (Static Methods)

靜態方法是可以在不建立類別實例的情況下直接呼叫的方法。靜態方法通常用於 utility 類別,這些方法不依賴於特定的實例,而是執行通用的任務。這搭配上前面提到的 Autoloading Class 方法時會相當方便。

class Utility {
    public static function calculateArea($radius) {
        return pi() * $radius * $radius;
    }
}

// 呼叫靜態方法
$area = Utility::calculateArea(5); // 計算半徑為5的圓面積

在這個例子中,calculateArea 是一個靜態方法,可以直接透過 Utility::calculateArea() 的方式呼叫,而不需要建立 Utility 物件。