PHP 现代特性速查 写出更简洁安全的代码(第一篇)
基础你肯定掌握了。这个三部曲写给每天写 PHP 的人,帮你把代码写得更清楚、bug 更少、跑得更快。上篇讲那些能改变 API、DTO 和调用方式的现代特性。
默认你在用 PHP 8.x+。例子都很短,直接扔进 Laravel service 或普通 PHP 文件就能跑。
原文链接 PHP 现代特性速查 写出更简洁安全的代码(第一篇)
Attributes --- 声明式、可发现的元数据(PHP 8.0)
替代了什么:脆弱的 docblock 和注解解析。
示例
            
            
              php
              
              
            
          
          #[Route(path: '/users', methods: ['GET'])]
class UserController { /* ... */ }
        效果:反射读 attribute,启动时自动注册路由。IDE 能看到这些元数据,静态分析也能识别。
建议:attributes 适合框架接线(路由、验证、序列化),保持简单------复杂配置还是老实用 DTO。
Named Arguments --- 自解释的函数调用(PHP 8.0)
替代了什么:容易搞错顺序的长参数列表。
示例
            
            
              php
              
              
            
          
          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)
替代了什么:重复的属性声明和赋值。
示例
            
            
              php
              
              
            
          
          class Mailer {
    public function __construct(
        private LoggerInterface $log,
        private CacheInterface $cache
    ) {}
}
        效果:构造函数简洁,打字少了拼写错误也少,属性自动带类型和注入。
建议:小 service 和 DTO 用这个------构造函数参数多了还是老实写工厂或 builder。
类型化属性(Typed Properties)--- 尽早强制契约(PHP 7.4)
替代了什么:松散的 @var docblock 和混乱的 mixed 类型。
示例
            
            
              php
              
              
            
          
          class Order {
    public int $id;
    public ?DateTimeImmutable $shippedAt = null;
}
        效果:赋错类型立刻抛 TypeError------领域 bug 早发现。
建议:配合静态分析(PHPStan/Psalm)在 CI 阶段就把问题拦住。
联合类型(Union Types)--- 明确、灵活的 API(PHP 8.0)
替代了什么:模糊的 mixed 和不明确的类型提示。
示例
            
            
              php
              
              
            
          
          function find(string|int $id): ?User { /* ... */ }
        效果:函数签名明确写出接受什么类型;静态工具能验证调用。
建议 :union 要有意义------别动不动就 string|int|float|bool,除非真需要。
交叉类型(Intersection Types)--- 更严格的多能力契约(PHP 8.1)
替代了什么:运行时检查对象是否实现多个接口。
示例
            
            
              php
              
              
            
          
          function process(Reader&Logger $obj) {
    // $obj 同时是 Reader 和 Logger
}
        效果:编译时就能保证对象有你要的能力。
建议:装饰器和适配器最适合,确保它们同时满足多个接口。
Enums --- 用领域安全的值替代魔法字符串(PHP 8.1)
替代了什么:容易出错的常量和字符串。
示例
            
            
              php
              
              
            
          
          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。
示例
            
            
              php
              
              
            
          
          readonly class UserDTO {
    public function __construct(
        public int $id,
        public string $email
    ) {}
}
        效果:构造完就不能改了------事件、配置、API 响应都适合。
建议:跨进程传的数据(队列、事件)优先用 readonly。
一等公民可调用对象(First-class Callables)--- 简洁、零样板的回调(PHP 8.1)
替代了什么:冗长的匿名函数或基于字符串的 callable。
示例
            
            
              php
              
              
            
          
          $upper = strtoupper(...);
$names = array_map($upper, ['alice','bob']);
        效果:管道好读,开销小。
建议 :配合 array_map/array_filter 用,意图清楚。不需要捕获变量时别用闭包。
组合使用:真实例子
假设写个控制器动作,把这些特性组合起来:
            
            
              php
              
              
            
          
          #[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)先迁移一个模块试试。效果好了,后面就好办了。
准备用哪个特性?说说你的痛点,我帮你找投入小、收益大的改法。