前言
PHP 8.0的构造器属性提升(Constructor Property Promotion)是我最喜欢的新特性之一。它大幅减少了样板代码,让类定义更加简洁。作为一个经常需要创建数据传输对象(DTO)和值对象的开发者,这个特性为我节省了大量时间。
传统方式 vs 属性提升
传统的冗长写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class User { private string $name; private string $email; private int $age; private bool $isActive; private ?string $avatar; public function __construct( string $name, string $email, int $age, bool $isActive = true, ?string $avatar = null ) { $this->name = $name; $this->email = $email; $this->age = $age; $this->isActive = $isActive; $this->avatar = $avatar; } public function getName(): string { return $this->name; } public function getEmail(): string { return $this->email; } }
|
使用属性提升的简洁写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class User { public function __construct( private string $name, private string $email, private int $age, private bool $isActive = true, private ?string $avatar = null ) {} public function getName(): string { return $this->name; } public function getEmail(): string { return $this->email; } }
|
可见性修饰符的使用
不同的可见性级别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Product { public function __construct( public readonly string $id, // 公共只读属性 public string $name, // 公共可写属性 protected float $price, // 受保护属性 private array $metadata = [] // 私有属性 ) {} public function getPrice(): float { return $this->price; } protected function updatePrice(float $newPrice): void { $this->price = $newPrice; } }
|
混合使用传统属性和提升属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Order { private array $items = []; private DateTime $createdAt; public function __construct( public readonly string $id, public string $customerName, private string $status = 'pending' ) { $this->createdAt = new DateTime(); } public function addItem(OrderItem $item): void { $this->items[] = $item; } public function getStatus(): string { return $this->status; } }
|
实际应用场景
1. 数据传输对象(DTO)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class CreateUserRequest { public function __construct( public readonly string $name, public readonly string $email, public readonly string $password, public readonly ?int $age = null, public readonly array $roles = ['user'] ) {} public function toArray(): array { return [ 'name' => $this->name, 'email' => $this->email, 'password' => $this->password, 'age' => $this->age, 'roles' => $this->roles ]; } }
$request = new CreateUserRequest( name: '张三', email: 'zhang@example.com', password: 'secret123', age: 25 );
|
2. 值对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Money { public function __construct( private readonly float $amount, private readonly string $currency = 'CNY' ) { if ($amount < 0) { throw new InvalidArgumentException('Amount cannot be negative'); } } public function getAmount(): float { return $this->amount; } public function getCurrency(): string { return $this->currency; } public function add(Money $other): Money { if ($this->currency !== $other->currency) { throw new InvalidArgumentException('Currency mismatch'); } return new Money($this->amount + $other->amount, $this->currency); } public function format(): string { return number_format($this->amount, 2) . ' ' . $this->currency; } }
$price = new Money(99.99, 'CNY'); $tax = new Money(10.00, 'CNY'); $total = $price->add($tax); echo $total->format();
|
3. 配置对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class DatabaseConfig { public function __construct( private readonly string $host, private readonly string $database, private readonly string $username, private readonly string $password, private readonly int $port = 3306, private readonly string $charset = 'utf8mb4', private readonly array $options = [] ) {} public function getDsn(): string { return "mysql:host={$this->host};port={$this->port};dbname={$this->database};charset={$this->charset}"; } public function getCredentials(): array { return [ 'username' => $this->username, 'password' => $this->password ]; } public function getOptions(): array { return array_merge([ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ], $this->options); } }
$config = new DatabaseConfig( host: 'localhost', database: 'myapp', username: 'root', password: 'secret', options: [PDO::ATTR_PERSISTENT => true] );
|
4. API响应对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class ApiResponse { public function __construct( private readonly mixed $data, private readonly int $statusCode = 200, private readonly string $message = 'Success', private readonly array $headers = [], private readonly ?array $errors = null ) {} public function toArray(): array { $response = [ 'status_code' => $this->statusCode, 'message' => $this->message, 'data' => $this->data ]; if ($this->errors !== null) { $response['errors'] = $this->errors; } return $response; } public function toJson(): string { return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE); } public function getHeaders(): array { return array_merge([ 'Content-Type' => 'application/json', 'X-Response-Time' => microtime(true) ], $this->headers); } }
$response = new ApiResponse( data: ['users' => $users], statusCode: 200, message: '用户列表获取成功' );
|
5. 事件对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class UserRegisteredEvent { public function __construct( public readonly User $user, public readonly DateTime $occurredAt, public readonly string $source = 'web', public readonly array $metadata = [] ) {} public function getEventName(): string { return 'user.registered'; } public function getPayload(): array { return [ 'user_id' => $this->user->getId(), 'email' => $this->user->getEmail(), 'source' => $this->source, 'occurred_at' => $this->occurredAt->format('Y-m-d H:i:s'), 'metadata' => $this->metadata ]; } }
class EventDispatcher { public function dispatch(UserRegisteredEvent $event): void { $this->sendWelcomeEmail($event->user); $this->logUserRegistration($event); $this->triggerWelcomeWorkflow($event); } }
|
与其他PHP 8特性结合
1. 与命名参数结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class EmailMessage { public function __construct( private readonly string $to, private readonly string $subject, private readonly string $body, private readonly ?string $from = null, private readonly array $cc = [], private readonly array $bcc = [], private readonly bool $isHtml = false, private readonly array $attachments = [] ) {} }
$email = new EmailMessage( to: 'user@example.com', subject: '欢迎注册', body: '<h1>欢迎加入我们!</h1>', from: 'noreply@myapp.com', isHtml: true );
|
2. 与联合类型结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class SearchFilter { public function __construct( private readonly string|array $keywords, private readonly string|null $category = null, private readonly int|float $minPrice = 0, private readonly int|float $maxPrice = PHP_FLOAT_MAX, private readonly bool $inStock = true ) {} public function getKeywords(): array { return is_string($this->keywords) ? explode(' ', $this->keywords) : $this->keywords; } public function toSqlConditions(): array { $conditions = []; if (!empty($this->getKeywords())) { $keywords = implode('%', $this->getKeywords()); $conditions[] = "title LIKE '%{$keywords}%'"; } if ($this->category) { $conditions[] = "category = '{$this->category}'"; } $conditions[] = "price BETWEEN {$this->minPrice} AND {$this->maxPrice}"; if ($this->inStock) { $conditions[] = "stock > 0"; } return $conditions; } }
|
3. 与属性结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #[Entity(table: 'products')] class Product { public function __construct( #[Column(type: 'string', length: 255)] public readonly string $name, #[Column(type: 'decimal', precision: 10, scale: 2)] private float $price, #[Column(type: 'text', nullable: true)] private ?string $description = null, #[Column(type: 'boolean', default: true)] private bool $isActive = true, #[Column(type: 'datetime')] private DateTime $createdAt = new DateTime() ) {} }
|
高级用法和技巧
1. 验证和转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class UserProfile { public function __construct( private readonly string $name, private readonly string $email, private readonly int $age, private readonly array $interests = [] ) { $this->validateName($name); $this->validateEmail($email); $this->validateAge($age); } private function validateName(string $name): void { if (strlen($name) < 2 || strlen($name) > 50) { throw new InvalidArgumentException('Name must be between 2 and 50 characters'); } } private function validateEmail(string $email): void { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format'); } } private function validateAge(int $age): void { if ($age < 0 || $age > 150) { throw new InvalidArgumentException('Age must be between 0 and 150'); } } }
|
2. 工厂方法模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Rectangle { public function __construct( private readonly float $width, private readonly float $height ) { if ($width <= 0 || $height <= 0) { throw new InvalidArgumentException('Width and height must be positive'); } } public static function square(float $side): self { return new self($side, $side); } public static function fromArray(array $data): self { return new self( width: $data['width'] ?? throw new InvalidArgumentException('Missing width'), height: $data['height'] ?? throw new InvalidArgumentException('Missing height') ); } public function getArea(): float { return $this->width * $this->height; } public function getPerimeter(): float { return 2 * ($this->width + $this->height); } }
$rect1 = new Rectangle(10, 20); $square = Rectangle::square(15); $rect2 = Rectangle::fromArray(['width' => 8, 'height' => 12]);
|
3. 不可变对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class ImmutablePoint { public function __construct( private readonly float $x, private readonly float $y ) {} public function getX(): float { return $this->x; } public function getY(): float { return $this->y; } public function withX(float $x): self { return new self($x, $this->y); } public function withY(float $y): self { return new self($this->x, $y); } public function move(float $deltaX, float $deltaY): self { return new self($this->x + $deltaX, $this->y + $deltaY); } public function distanceTo(ImmutablePoint $other): float { $dx = $this->x - $other->x; $dy = $this->y - $other->y; return sqrt($dx * $dx + $dy * $dy); } }
$point1 = new ImmutablePoint(0, 0); $point2 = $point1->move(3, 4); $point3 = $point2->withX(10); echo $point1->distanceTo($point3);
|
性能考虑
构造器属性提升在性能上与传统方式基本相同:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
class BenchmarkTest { public function __construct( private string $prop1, private string $prop2, private int $prop3, private bool $prop4 ) {} }
|
注意事项和限制
1. 不能与抽象构造器一起使用
1 2 3 4 5 6 7
| abstract class AbstractClass { abstract public function __construct(string $name); }
|
2. 接口中的构造器
1 2 3 4 5 6 7 8 9 10 11 12
| interface UserInterface { public function __construct(string $name, string $email); }
class User implements UserInterface { public function __construct( private string $name, private string $email ) {} }
|
3. 继承中的注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class BaseUser { public function __construct( protected string $name, protected string $email ) {} }
class AdminUser extends BaseUser { public function __construct( string $name, string $email, private array $permissions = [] ) { parent::__construct($name, $email); } }
|
最佳实践
1. 合理使用可见性
1 2 3 4 5 6 7 8
| class GoodExample { public function __construct( public readonly string $id, // 公共只读:外部需要访问但不能修改 private string $password, // 私有:敏感信息 protected array $metadata = [] // 受保护:子类可能需要访问 ) {} }
|
2. 参数验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class ValidatedUser { public function __construct( private readonly string $email, private readonly int $age ) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } if ($age < 0 || $age > 150) { throw new InvalidArgumentException('Invalid age'); } } }
|
3. 文档注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
class UserDTO { public function __construct( public readonly string $name, public readonly string $email, public readonly int $age, public readonly array $roles = ['user'] ) {} }
|
总结
构造器属性提升是PHP 8.0中最实用的语法糖之一,它显著减少了样板代码,让类定义更加简洁。在实际项目中,我发现它特别适用于:
- 数据传输对象(DTO)
- 值对象(Value Objects)
- 配置对象
- 事件对象
- 不可变对象
使用时要注意:
- 合理选择属性可见性
- 在构造器中进行必要的验证
- 保持良好的文档注释
- 考虑对象的不可变性设计
这个特性让PHP代码更加现代化和简洁,强烈推荐在新项目中使用!