PHP 面向对象基础
面向对象编程(OOP)是 PHP 现代开发的核心范式,通过类和对象组织代码。相比过程式编程,OOP 让代码更易于维护、扩展和复用,是构建大型应用的基础。PHP 5 引入了完整的 OOP 支持,PHP 7/8 进一步增强了类型系统和语法糖,使得 PHP 的面向对象能力已经相当成熟。
类与对象
类是对象的蓝图,定义了对象的属性(数据)和方法(行为)。通过 new 关键字可以从类创建对象实例,每个实例都有独立的属性值。构造函数 __construct() 在对象创建时自动调用,用于初始化属性。
class User { public string $name; public int $age; private string $password;password_hash()public function __construct(string $name, int $age, string $password) { $this->name = $name; $this->age = $age; $this->password = password_hash($password, PASSWORD_DEFAULT); }
public function greet(): string { return "你好,我是 {$this->name},今年 {$this->age} 岁。"; }
public function verifyPassword(string $input): bool { return password_verify($input, $this->password); } }
$user = new User("张三", 25, "secret123"); echo $user->greet(); echo $user->verifyPassword("secret123"); // true ``
密码存储时使用
而不是 MD5 或 SHA1,这是现代 PHP 开发的安全规范。password_hash()使用 bcrypt 算法并自动加盐,password_verify()负责验证,两者配合使用可以有效防止彩虹表攻击。public访问修饰符
访问修饰符控制属性和方法的可见范围,是封装性的核心体现。合理使用访问修饰符可以隐藏实现细节,只暴露必要的接口。
修饰符 类内 子类 外部 public ✅ ✅ ✅ protected ✅ ✅ ❌ private ✅ ❌ ❌ Getter / Setter
直接将属性设为
会失去对赋值的控制,通过 Getter/Setter 方法可以在读写时加入验证逻辑,保证数据的合法性。`php class Product { private float $price;`public function getPrice(): float { return $this->price; }
public function setPrice(float $price): void { if ($price < 0) throw new InvalidArgumentException("价格不能为负"); $this->price = $price; } }
$p = new Product(); $p->setPrice(99.9); echo $p->getPrice(); // 99.9
`继承
继承允许子类复用父类的属性和方法,并可以覆盖(override)父类的方法来实现不同的行为。PHP 只支持单继承,一个类只能有一个直接父类。
php class Animal { public function __construct(protected string $name) {}`public function speak(): string { return "{$this->name} 发出声音"; } }
class Dog extends Animal { public function speak(): string { return "{$this->name} 说:汪汪!"; } }
class Cat extends Animal { public function speak(): string { return "{$this->name} 说:喵喵!"; } }
$dog = new Dog("旺财"); echo $dog->speak(); // 旺财 说:汪汪!
parent::方法名()子类中可以用
调用父类的方法,这在需要扩展父类行为而不是完全替换时非常有用。`抽象类与接口
抽象类和接口都是定义"契约"的方式,但用途不同。抽象类适合有共同实现逻辑的场景,接口适合定义纯粹的行为规范。一个类可以实现多个接口,但只能继承一个抽象类。
php abstract class Shape { abstract public function area(): float;`public function describe(): string { return "这个形状面积是 " . $this->area(); } }
interface Drawable { public function draw(): string; }
class Circle extends Shape implements Drawable { public function __construct(private float $radius) {}
public function area(): float { return M_PI $this->radius * 2; }
public function draw(): string { return "画一个半径为 {$this->radius} 的圆"; } }
$c = new Circle(5); echo $c->area(); // 78.539... echo $c->describe(); echo $c->draw();
类名::静态属性与方法
静态成员属于类本身而不是某个实例,通过
或self::访问。静态方法常用于工厂方法、单例模式、工具函数等场景。`php class Counter { private static int $count = 0;`public static function increment(): void { self::$count++; }
public static function getCount(): int { return self::$count; } }
Counter::increment(); Counter::increment(); echo Counter::getCount(); // 2
self::注意
和static::的区别:self::始终指向定义该方法的类,而static::支持晚期静态绑定,在继承场景下指向实际调用的类。`Trait(代码复用)
PHP 只支持单继承,Trait 是解决代码复用问题的补充机制。Trait 类似于"混入"(Mixin),可以将一组方法"注入"到多个不相关的类中,避免重复代码。
php trait Timestampable { private ?DateTime $createdAt = null; private ?DateTime $updatedAt = null;`public function touch(): void { $now = new DateTime(); if (!$this->createdAt) $this->createdAt = $now; $this->updatedAt = $now; }
public function getCreatedAt(): ?DateTime { return $this->createdAt; } }
class Article { use Timestampable;
public function __construct(public string $title) { $this->touch(); } }
$article = new Article("PHP 教程"); echo $article->getCreatedAt()->format('Y-m-d');
use TraitA, TraitB;一个类可以同时使用多个 Trait:
。当多个 Trait 有同名方法时,需要用insteadof和as关键字解决冲突。`魔术方法
PHP 提供了一系列以双下划线开头的魔术方法,在特定操作触发时自动调用。合理使用魔术方法可以让类的行为更加灵活和直观。
php class MagicClass { private array $data = [];`public function __get(string $name): mixed { return $this->data[$name] ?? null; }
public function __set(string $name, mixed $value): void { $this->data[$name] = $value; }
public function __toString(): string { return json_encode($this->data, JSON_UNESCAPED_UNICODE); } }
$obj = new MagicClass(); $obj->name = "张三"; $obj->age = 25; echo $obj->name; // 张三 echo $obj; // {"name":"张三","age":25}
`接口与多态
多态是 OOP 的核心特性之一,通过接口或抽象类实现。不同的类实现同一接口,调用方只需面向接口编程,无需关心具体实现。
php interface Logger { public function log(string $level, string $message): void; }class FileLogger implements Logger { public function log(string $level, string $message): void { file_put_contents('app.log', "[$level] $message\n", FILE_APPEND); } }
class ConsoleLogger implements Logger { public function log(string $level, string $message): void { echo "[$level] $message\n"; } }
function processOrder(int $orderId, Logger $logger): void { $logger->log('info', "处理订单 #$orderId"); }
processOrder(1001, new FileLogger()); processOrder(1002, new ConsoleLogger());
常见问题
Q:抽象类和接口有什么区别,应该如何选择? A:抽象类可以包含具体方法实现和属性,适合有共同逻辑的"is-a"关系;接口只能定义方法签名,适合定义能力契约的"can-do"关系。如果多个类有共同的实现逻辑,用抽象类;如果只是约定行为规范,用接口。一个类可以实现多个接口,但只能继承一个抽象类。
Q:self:: 和 static:: 有什么区别? A:self:: 在编译时绑定,始终指向定义该方法的类;static:: 支持晚期静态绑定,在运行时指向实际调用的类。在继承场景中,工厂方法和单例模式中应优先使用 static::。
Q:Trait 和继承有什么区别,什么时候用 Trait? A:继承表达"是一种"的关系,Trait 表达"具有某种能力"的横切关注点。当多个不相关的类需要共享同一段代码时,用 Trait 比继承更合适。例如 Article、User、Product 都需要时间戳功能,但它们之间没有继承关系,此时用 Timestampable Trait 最为合适。
Q:PHP 的魔术方法 __get 和 __set 会影响性能吗? A:会有一定影响,因为每次访问不存在的属性都会触发方法调用,比直接访问已声明的属性慢。在性能敏感的场景应避免依赖魔术方法。实际项目中,__get/__set` 常用于实现动态属性、ORM 的延迟加载等场景,使用时需权衡灵活性和性能。