更新於 2024/12/19閱讀時間約 16 分鐘

2024年的PHP 8.4:Property Hooks 帶來的全新體驗

「嘿,你有聽說 PHP 8.4 的新特性了嗎?」同事David一臉興奮地問我。

我轉過頭,手上還拿著極韻白,「你是說 Property Hooks?就是那個讓我們在存取屬性時可以加上 get/set 邏輯的東西嗎?」

「對啊!」David笑得合不攏嘴,「終於不用再為了每個小驗證寫滿 GetterSetter 方法了,也不用 __get() __set() 那麼大刀闊斧的攔截全部undefined屬性。」

如果你曾經為了在程式中控制屬性存取邏輯而煩惱不已,又或者討厭為每個屬性寫一堆 getXxx()、setXxx(),那麼 PHP 8.4 的 Property Hooks 絕對是你想要的。這個新特性讓我們可以直接在屬性宣告中對「存取行為」加入特定邏輯,同時兼顧程式碼的乾淨度與可讀性。

接下來,我們用最直觀、易懂的方式,帶你體驗 Property Hooks 的世界!

先別急,我們會先從「傳統的Getter/Setter 寫法」開始比較,一步步看出有了 Property Hooks 後的差異性。

沒有 Property Hooks 時的傳統寫法

在 PHP 8.4 之前,若你想在屬性賦值或讀取時做點小動作(例如:寫入時驗證、讀取時格式化),往往需要這樣的模式:

class User {

private string $name;

public function __construct(string $name) {

$this->setName($name);
}

public function getName(): string {

// 讀取時可能要處理一些邏輯

// 例如轉成大寫
return strtoupper($this->name);
}

public function setName(string $value): void {

// 寫入前先驗證
if (strlen($value) < 2) {

throw new InvalidArgumentException("Name is too short!");
}

$this->name = $value;
}
}

$user = new User("Tom");
echo $user->getName(); // "TOM"
$user->setName("A"); // 丟出例外

這段程式碼有什麼問題?

  • 我們為了控制屬性的存取,增加了 Getter/Setter 方法,程式碼變長了。
  • 寫的時候還好,日後維護或增加屬性時可能很煩,一個屬性兩個方法,多了 10 個屬性,就是 20 個方法。

再看看另一種「魔術方法」的做法( __get() 與 __set()):

class User {

private string $name;

public function __construct(string $name) {

$this->name = $name;
}

public function __get($prop) {

if ($prop === 'name') {

// 路徑一:讀取 name
return strtoupper($this->name);
}

throw new Exception("Property $prop not found");
}

public function __set($prop, $value) {

if ($prop === 'name') {

if (strlen($value) < 2) {

throw new InvalidArgumentException("Name too short!");
}

$this->name = $value;

return;
}

throw new Exception("Property $prop not found");
}
}

這魔術方法的寫法,雖然不需要每個屬性分別寫 getXxx() 和 setXxx(),但實務上卻是大砲打小鳥。

它攔截所有未定義的屬性存取,是個「一刀切」的做法,不僅難以維護,也讓 IDE 或靜態分析工具難以推斷屬性實際存在與否。

有了 Property Hooks 後的世界

現在,看看 PHP 8.4 的 Property Hooks:

class User {

public string $name {

get => strtoupper($this->name); // 讀取時自動轉成大寫

set (string $value) {

if (strlen($value) < 2) {

throw new InvalidArgumentException("Name too short!");
}

$this->name = $value;
}
}

public function __construct(string $name) {

$this->name = $name; // 寫入時會觸發 set 掛鉤
}
}

$user = new User("Tom");
echo $user->name; // 読取時觸發 get 掛鉤,輸出 "TOM"
$user->name = "A"; // 寫入觸發 set 掛鉤,因長度不足拋出例外

在這裡,我們直接在屬性宣告中利用 { } 區塊,定義 get 與 set 的邏輯。

神奇之處在於:

  • 讀取 $user→name 時,自動走 get 區塊。
  • 寫入 $user→name = … 時,自動走 set 區塊。
  • 這個屬性本身就像帶有內建控制邏輯的「智慧屬性」。

看起來是不是清爽許多?

實際案例對比:沒有 Property Hooks vs. 有 Property Hooks

範例一:基本驗證

傳統做法:

class Product {

private int $price;

public function __construct(int $price) {

$this->setPrice($price);
}

public function setPrice(int $price) {

if ($price < 0) {

throw new InvalidArgumentException("Price can't be negative!");
}

$this->price = $price;
}

public function getPrice(): int {

return $this->price;
}
}

$product = new Product(100);
echo $product->getPrice(); // 100
$product->setPrice(-10); // Exception

使用 Property Hooks:

class Product {

public int $price {

set (int $value) {

if ($value < 0) {

throw new InvalidArgumentException("Price can't be negative!");
}

$this->price = $value;
}

// 未實作 get, 預設讀取直接取得 backing value
// 如果要在讀取時做點事,也可加上 get => ...;
}

public function __construct(int $price) {

$this->price = $price; // 觸發 set 區塊
}
}

$product = new Product(100);
echo $product->price; // 100 (直接讀取,未實作 get,則為預設讀取行為)
$product->price = -10; // Exception

程式碼明顯更精簡。

範例二:虛擬屬性 (Virtual Property)

Property Hooks 也能產生「虛擬屬性」,就像前端的 computed property。

所謂虛擬屬性,就是該屬性並沒有真正在物件中存儲資料,而是動態取得或寫入時處理其他屬性。

傳統作法 (無 Hooks):

class User {

private string $firstName;

private string $lastName;

public function __construct(string $first, string $last) {

$this->firstName = $first;
$this->lastName = $last;
}

public function getFullName(): string {

return $this->firstName.' '.$this->lastName;
}

public function setFullName(string $value): void {

[$f, $l] = explode(' ', $value, 2);

$this->firstName = $f;
$this->lastName = $l;
}
}

$user = new User("John", "Doe");
echo $user->getFullName(); // John Doe
$user->setFullName("Jane Smith");
echo $user->getFullName(); // Jane Smith

有 Hooks 之後:

class User {

private string $firstName;

private string $lastName;

public string $fullName {

get => $this->firstName.' '.$this->lastName;

set {

[$f, $l] = explode(' ', $value, 2);

$this->firstName = $f;
$this->lastName = $l;
}
}

public function __construct(string $first, string $last) {

$this->firstName = $first;
$this->lastName = $last;
}
}

$user = new User("John", "Doe");
echo $user->fullName; // John Doe (透過 get hooks 動態取得)
$user->fullName = "Jane Smith"; // set hooks 動態拆解名稱
echo $user->fullName; // Jane Smith

完全不需要額外的方法名稱,直接用 $user→fullName,存取像是正常屬性,卻暗藏邏輯!

Property Hooks 的其他優勢

  • 介面與抽象類別支援:PHP 8.4 也允許在介面或抽象類別中定義需要 get 或 set 的屬性契約,讓物件行為更一致。
  • 明確性:跟 __get() __set() 相比,Property Hooks 只對定義的屬性生效,不是大範圍的「隱形陷阱」。
  • 工具友善:靜態分析工具與 IDE 能更輕鬆解析屬性行為,因為掛鉤是明確定義在屬性上,而非動態攔截。

實務建議

使用 Property Hooks 不代表要棄守 Getter/Setter。在特定場合,傳統 Getter/Setter 仍有存在價值(例如需相容舊有程式碼或某些框架習慣)。但在新的專案中,如果你想讓屬性操作更自然、程式碼更精簡,同時又能確保寫入與讀取的規則一致,那 Property Hooks 絕對是新寵兒。

要注意的是,雖然看起來很方便,但別因此在每個屬性上都加一堆複雜邏輯。

保持適度、保持簡單,才是維護上的王道。

最後

Property Hooks 是 PHP 8.4 的亮點之一。它解決了以往實作屬性控制的繁瑣,讓程式碼更直觀。從前我們得寫好多 Getter/Setter,或使用魔術方法來處理存取邏輯;現在,我們可以直觀地將邏輯和屬性綁在一起,就像給屬性裝上小小的「智慧晶片」。

希望這篇文章透過大量的程式碼對比、淺顯易懂的解說,讓你對 Property Hooks 有更清晰的認識。下次在寫 PHP 8.4 的程式時,不妨試試看這項特性,相信你會愛上這種「所見即所得」的程式碼體驗!

加油,繼續探索 PHP 的新可能!

參考資料

https://wiki.php.net/rfc/property-hooks

https://laravel-news.com/php-8-4-0

https://laravel-news.com/php-property-hooks


分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.