「嘿,你有聽說 PHP 8.4 的新特性了嗎?」同事David一臉興奮地問我。
我轉過頭,手上還拿著極韻白,「你是說 Property Hooks?就是那個讓我們在存取屬性時可以加上 get/set 邏輯的東西嗎?」
「對啊!」David笑得合不攏嘴,「終於不用再為了每個小驗證寫滿 Getter、Setter 方法了,也不用 __get()
、 __set()
那麼大刀闊斧的攔截全部undefined屬性。」
如果你曾經為了在程式中控制屬性存取邏輯而煩惱不已,又或者討厭為每個屬性寫一堆 getXxx()、setXxx(),那麼 PHP 8.4 的 Property Hooks 絕對是你想要的。這個新特性讓我們可以直接在屬性宣告中對「存取行為」加入特定邏輯,同時兼顧程式碼的乾淨度與可讀性。
接下來,我們用最直觀、易懂的方式,帶你體驗 Property Hooks 的世界!
先別急,我們會先從「傳統的Getter/Setter 寫法」開始比較,一步步看出有了 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"); // 丟出例外
這段程式碼有什麼問題?
再看看另一種「魔術方法」的做法( __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 或靜態分析工具難以推斷屬性實際存在與否。
現在,看看 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 的邏輯。
神奇之處在於:
看起來是不是清爽許多?
傳統做法:
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
程式碼明顯更精簡。
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,存取像是正常屬性,卻暗藏邏輯!
__get()
、 __set()
相比,Property Hooks 只對定義的屬性生效,不是大範圍的「隱形陷阱」。使用 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