PHP 面向对象
Classes, constructor promotion, inheritance, interfaces, abstract classes, traits, statics and enums.
Classes and constructor promotion
Before PHP 8 a value object needed property declarations, a constructor and assignments. Now a visibility modifier on the parameter does it all.
<?php
class Point
{
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
public function distanceTo(Point $other): float {
return sqrt(($this->x - $other->x) ** 2 + ($this->y - $other->y) ** 2);
}
}
$p = new Point(0, 0);
$q = new Point(3, 4);
echo $q->x, "\n";
echo $p->distanceTo($q), "\n";3
5readonly (8.1) allows a single assignment — ideal for value objects. A second write throws an Error.
<?php
class Point
{
public function __construct(public readonly float $x) {}
}
$p = new Point(1);
try {
$p->x = 2;
} catch (Error $e) {
echo "赋值失败: " . $e->getMessage(), "\n";
}赋值失败: Cannot modify readonly property Point::$xInheritance and abstract classes
<?php
interface Shape
{
public function area(): float;
}
abstract class Base implements Shape
{
public function __construct(protected string $name) {}
abstract public function area(): float;
public function describe(): string {
return sprintf("%s 面积 %.2f", $this->name, $this->area());
}
}
class Circle extends Base
{
public function __construct(private float $r) {
parent::__construct("圆");
}
public function area(): float {
return M_PI * $this->r ** 2;
}
}
class Rect extends Base
{
public function __construct(private float $w, private float $h) {
parent::__construct("矩形");
}
public function area(): float {
return $this->w * $this->h;
}
}
foreach ([new Circle(2), new Rect(3, 4)] as $s) {
echo $s->describe(), "\n";
}
var_dump(new Circle(1) instanceof Shape);圆 面积 12.57
矩形 面积 12.00
bool(true)| Feature | Interface | Abstract class |
|---|---|---|
| Instantiate | 不能 | 不能 |
| Method bodies | 不能有(可有 default 常量) | 可以有具体方法 |
| Properties | 不能声明 | 可以 |
| How many | 一个类可实现多个 | 只能继承一个 |
| Purpose | 约定能力 | 复用部分实现 |
trait
PHP is single-inheritance. To share code between unrelated classes, use a trait — a paste-able block of methods.
<?php
trait Loggable
{
public function log(string $msg): void {
echo "[" . static::class . "] $msg\n";
}
}
trait Timestampable
{
protected int $createdAt = 0;
public function touch(): void {
$this->createdAt = 1700000000;
}
public function createdAt(): int {
return $this->createdAt;
}
}
class Order
{
use Loggable, Timestampable;
}
class Invoice
{
use Loggable;
}
$o = new Order();
$o->log("创建订单");
$o->touch();
echo $o->createdAt(), "\n";
(new Invoice())->log("开票");[Order] 创建订单
1700000000
[Invoice] 开票$this and static::class refer to the using class. A trait cannot be used with instanceof — it is not a type.Statics and constants
<?php
class Counter
{
private static int $count = 0;
public const MAX = 10;
public static function increment(): int {
return ++self::$count;
}
public static function current(): int {
return self::$count;
}
}
Counter::increment();
Counter::increment();
echo Counter::current(), "\n";
echo Counter::MAX, "\n";
// 后期静态绑定
class A
{
public static function create(): static {
return new static();
}
public function who(): string {
return static::class;
}
}
class B extends A {}
echo B::create()->who(), "\n";
echo A::create()->who(), "\n";2
10
B
Aself:: binds to the defining class at compile time; static:: binds at runtime to the called class. Factories returning subclass instances need new static().
Enums
PHP 8.1 enums are typed constant sets — usable in type declarations, and they can carry methods.
<?php
enum Status: string
{
case Active = 'active';
case Banned = 'banned';
public function label(): string {
return match ($this) {
Status::Active => '正常',
Status::Banned => '封禁',
};
}
public static function fromLabel(string $label): self {
foreach (self::cases() as $case) {
if ($case->label() === $label) {
return $case;
}
}
throw new ValueError("未知状态: $label");
}
}
echo Status::Active->label(), "\n";
echo Status::from('banned')->label(), "\n";
echo Status::fromLabel('正常')->value, "\n";
print_r(array_column(Status::cases(), 'value'));正常
封禁
active
Array
(
[0] => active
[1] => banned
)Status::from() throws ValueError when missing; tryFrom() returns null. Enum cases are singletons — identity comparison works.
Visibility
| Modifier | Visible from |
|---|---|
| public | 任何地方 |
| protected | 本类与子类 |
| private | 仅本类(子类也看不到) |
| final | 可修饰类(不可继承)或方法(不可覆写) |
| readonly | 属性赋值一次后锁定(8.1+) |
| static | 属于类而非实例 |
private members are invisible to subclasses, even via parent::. Use protected. Private methods also skip dynamic dispatch — a subclass method of the same name is never called.类与构造器属性提升
PHP 8 之前写一个值对象要声明属性、写构造器、逐个赋值。现在直接在构造器参数上加可见性修饰符即可。
<?php
class Point
{
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
public function distanceTo(Point $other): float {
return sqrt(($this->x - $other->x) ** 2 + ($this->y - $other->y) ** 2);
}
}
$p = new Point(0, 0);
$q = new Point(3, 4);
echo $q->x, "\n";
echo $p->distanceTo($q), "\n";3
5readonly(PHP 8.1)表示属性赋值一次后不可修改,天然适合值对象。试图改会抛 Error。
<?php
class Point
{
public function __construct(public readonly float $x) {}
}
$p = new Point(1);
try {
$p->x = 2;
} catch (Error $e) {
echo "赋值失败: " . $e->getMessage(), "\n";
}赋值失败: Cannot modify readonly property Point::$x继承与抽象类
<?php
interface Shape
{
public function area(): float;
}
abstract class Base implements Shape
{
public function __construct(protected string $name) {}
abstract public function area(): float;
public function describe(): string {
return sprintf("%s 面积 %.2f", $this->name, $this->area());
}
}
class Circle extends Base
{
public function __construct(private float $r) {
parent::__construct("圆");
}
public function area(): float {
return M_PI * $this->r ** 2;
}
}
class Rect extends Base
{
public function __construct(private float $w, private float $h) {
parent::__construct("矩形");
}
public function area(): float {
return $this->w * $this->h;
}
}
foreach ([new Circle(2), new Rect(3, 4)] as $s) {
echo $s->describe(), "\n";
}
var_dump(new Circle(1) instanceof Shape);圆 面积 12.57
矩形 面积 12.00
bool(true)| 对比项 | 接口 | 抽象类 |
|---|---|---|
| 能否实例化 | 不能 | 不能 |
| 方法体 | 不能有(可有 default 常量) | 可以有具体方法 |
| 属性 | 不能声明 | 可以 |
| 可继承数量 | 一个类可实现多个 | 只能继承一个 |
| 用途 | 约定能力 | 复用部分实现 |
Traits
PHP 单继承,想在多个不相干的类里复用同一段代码,用 trait。trait 像「可粘贴的方法块」。
<?php
trait Loggable
{
public function log(string $msg): void {
echo "[" . static::class . "] $msg\n";
}
}
trait Timestampable
{
protected int $createdAt = 0;
public function touch(): void {
$this->createdAt = 1700000000;
}
public function createdAt(): int {
return $this->createdAt;
}
}
class Order
{
use Loggable, Timestampable;
}
class Invoice
{
use Loggable;
}
$o = new Order();
$o->log("创建订单");
$o->touch();
echo $o->createdAt(), "\n";
(new Invoice())->log("开票");[Order] 创建订单
1700000000
[Invoice] 开票$this 指向使用它的类实例,static::class 也是。但 trait 不能被 instanceof 判断——它不是类型,只是代码复用的语法糖。静态成员与常量
<?php
class Counter
{
private static int $count = 0;
public const MAX = 10;
public static function increment(): int {
return ++self::$count;
}
public static function current(): int {
return self::$count;
}
}
Counter::increment();
Counter::increment();
echo Counter::current(), "\n";
echo Counter::MAX, "\n";
// 后期静态绑定
class A
{
public static function create(): static {
return new static();
}
public function who(): string {
return static::class;
}
}
class B extends A {}
echo B::create()->who(), "\n";
echo A::create()->who(), "\n";2
10
B
Aself:: 在编译期绑定到定义所在的类,static:: 在运行期绑定到实际调用的类。工厂方法里想返回子类实例,必须用 new static()。
枚举
PHP 8.1 的枚举是有类型的常量集合,比一堆 const 强得多:可以用在类型声明里,可以带方法。
<?php
enum Status: string
{
case Active = 'active';
case Banned = 'banned';
public function label(): string {
return match ($this) {
Status::Active => '正常',
Status::Banned => '封禁',
};
}
public static function fromLabel(string $label): self {
foreach (self::cases() as $case) {
if ($case->label() === $label) {
return $case;
}
}
throw new ValueError("未知状态: $label");
}
}
echo Status::Active->label(), "\n";
echo Status::from('banned')->label(), "\n";
echo Status::fromLabel('正常')->value, "\n";
print_r(array_column(Status::cases(), 'value'));正常
封禁
active
Array
(
[0] => active
[1] => banned
)Status::from() 找不到会抛 ValueError,tryFrom() 返回 null。枚举实例是单例,Status::Active === Status::Active 恒为 true。
可见性与继承规则
| 修饰符 | 可见范围 |
|---|---|
| public | 任何地方 |
| protected | 本类与子类 |
| private | 仅本类(子类也看不到) |
| final | 可修饰类(不可继承)或方法(不可覆写) |
| readonly | 属性赋值一次后锁定(8.1+) |
| static | 属于类而非实例 |
private 成员在子类里完全不可见,包括 parent:: 调用。子类需要访问就用 protected。另外 private 方法不参与动态派发,即使子类有同名方法也不会被调用。