PHP 現代特性速查 寫出更簡潔安全的代碼(第一篇)
PHP 現代特性速查 寫出更簡潔安全的代碼(第一篇)
基礎你肯定掌握了。這個三部曲寫給每天寫 PHP 的人,幫你把代碼寫得更清楚、bug 更少、跑得更快。上篇講那些能改變 API、DTO 和調用方式的現代特性。
默認你在用 PHP 8.x+。例子都很短,直接扔進 Laravel service 或普通 PHP 文件就能跑。
原文鏈接 PHP 現代特性速查 寫出更簡潔安全的代碼(第一篇)
Attributes — 聲明式、可發現的元數據(PHP 8.0)
替代了什么:脆弱的 docblock 和注解解析。
示例
#[Route(path: '/users', methods: ['GET'])]
class UserController { /* ... */ }
效果:反射讀 attribute,啟動時自動注冊路由。IDE 能看到這些元數據,靜態分析也能識別。
建議:attributes 適合框架接線(路由、驗證、序列化),保持簡單——復雜配置還是老實用 DTO。
Named Arguments — 自解釋的函數調用(PHP 8.0)
替代了什么:容易搞錯順序的長參數列表。
示例
function connect(string $host, int $port, bool $tls = false) { /* ... */ }
// 更清晰,順序無關
connect(port: 5432, host: 'db.internal', tls: true);
效果:調用像配置文件一樣好讀;加可選參數也不會破壞兼容性。
建議:工廠方法和 HTTP 客戶端配置最適合——配合 readonly DTO 做不可變配置。
Constructor Property Promotion — 減少 service 和 DTO 的樣板代碼(PHP 8.0)
替代了什么:重復的屬性聲明和賦值。
示例
class Mailer {
public function __construct(
private LoggerInterface $log,
private CacheInterface $cache
) {}
}
效果:構造函數簡潔,打字少了拼寫錯誤也少,屬性自動帶類型和注入。
建議:小 service 和 DTO 用這個——構造函數參數多了還是老實寫工廠或 builder。
類型化屬性(Typed Properties)— 盡早強制契約(PHP 7.4)
替代了什么:松散的 @var docblock 和混亂的 mixed 類型。
示例
class Order {
public int $id;
public ?DateTimeImmutable $shippedAt = null;
}
效果:賦錯類型立刻拋 TypeError——領域 bug 早發現。
建議:配合靜態分析(PHPStan/Psalm)在 CI 階段就把問題攔住。
聯合類型(Union Types)— 明確、靈活的 API(PHP 8.0)
替代了什么:模糊的 mixed 和不明確的類型提示。
示例
function find(string|int $id): ?User { /* ... */ }
效果:函數簽名明確寫出接受什么類型;靜態工具能驗證調用。
建議:union 要有意義——別動不動就 string|int|float|bool,除非真需要。
交叉類型(Intersection Types)— 更嚴格的多能力契約(PHP 8.1)
替代了什么:運行時檢查對象是否實現多個接口。
示例
function process(Reader&Logger $obj) {
// $obj 同時是 Reader 和 Logger
}
效果:編譯時就能保證對象有你要的能力。
建議:裝飾器和適配器最適合,確保它們同時滿足多個接口。
Enums — 用領域安全的值替代魔法字符串(PHP 8.1)
替代了什么:容易出錯的常量和字符串。
示例
enum PaymentStatus: string {
case PENDING = 'pending';
case PAID = 'paid';
case FAILED = 'failed';
}
$status = PaymentStatus::PAID;
效果:match 能窮舉所有情況、重構更安全、日志也更清楚。
建議:持久化用 backed enums(string/int);常見的領域邏輯直接寫在 enum 方法里。
只讀屬性和只讀類(Readonly)— 不可變 DTO(屬性:PHP 8.1;類:PHP 8.2)
替代了什么:手寫的不可變對象和意外的變更 bug。
示例
readonly class UserDTO {
public function __construct(
public int $id,
public string $email
) {}
}
效果:構造完就不能改了——事件、配置、API 響應都適合。
建議:跨進程傳的數據(隊列、事件)優先用 readonly。
一等公民可調用對象(First-class Callables)— 簡潔、零樣板的回調(PHP 8.1)
替代了什么:冗長的匿名函數或基于字符串的 callable。
示例
$upper = strtoupper(...);
$names = array_map($upper, ['alice','bob']);
效果:管道好讀,開銷小。
建議:配合 array_map/array_filter 用,意圖清楚。不需要捕獲變量時別用閉包。
組合使用:真實例子
假設寫個控制器動作,把這些特性組合起來:
#[Route(path: '/orders', methods: ['POST'])]
final class CreateOrderAction {
public function __construct(
private OrderService $service,
private LoggerInterface $logger
) {}
public function __invoke(CreateOrderDTO $dto): JsonResponse {
$order = $this->service->create($dto);
$this->logger->info('order.created', ['id' => $order->id]);
return new JsonResponse(['id' => $order->id], 201);
}
}
這段代碼把 attributes(路由)、constructor promotion(注入)、readonly DTO(不可變)、typed properties 和 enums(領域安全)組合在一起,代碼簡潔、一看就懂、還更安全。
高級技巧 — 老手怎么組合這些特性
驗證管道:DTO + attributes + 靜態分析,能在編譯期驗證的就別等運行時。
領域值對象:少用松散數組,多用小 readonly 值對象——類型化屬性能幫你擋住下游的 bug。
Match + Enums:別用布爾標志和字符串 switch 了,enum 配 match 窮舉,編譯器幫你找漏掉的分支。
交叉類型給適配器:適配器要同時實現 Cacheable&Loggable 才能傳給基礎設施。
一等公民可調用對象優化 map:沒有閉包分配開銷,快一點也清楚一點。
什么時候別用這些特性(注意事項)
attributes 太多會拖慢反射 — 做接線可以,別在字段上堆一堆元數據。
Named arguments 在 DI 容器里可能出問題 — 調用時用它提升可讀性沒問題,寫庫給別人用就別這么干。
到處 readonly 開發時很煩 — DTO 和事件用 readonly 有意義,service 對象就別折騰了。
升級路線
還在 PHP < 7.4:先升到 7.4,類型化屬性值得。
想提升生產力:直接上 8.1(enums、只讀屬性、一等公民可調用對象、交叉類型)。
8.2 加了只讀類和其他改進——有用但不急。
最后
這些特性不是玩具,它們改變你建模數據、強制約束、理解系統的方式。用好了,attributes、enums、類型化屬性能讓代碼更穩、團隊更快。
代碼庫還在用魔法字符串、靠 docblock 或到處傳可變數組?選一個特性(enums 或 readonly DTO)先遷移一個模塊試試。效果好了,后面就好辦了。
準備用哪個特性?說說你的痛點,我幫你找投入小、收益大的改法。

浙公網安備 33010602011771號