PHP 面向对象
项目大了之后,面向过程(一堆函数 + 关联数组)会越来越难维护。面向对象编程(OOP)用"类"和"对象"组织代码,把数据和行为绑在一起,大幅提升代码复用性、可测试性、可维护性。现代 PHP 框架(Laravel、Symfony)全部基于 OOP。
1. 类与对象:基本概念
- 类(class):模板/蓝图,定义"长什么样"。
- 对象(object):类的实例,具体存在。
new创建。 - 属性(property):对象里的变量。
- 方法(method):对象里的函数。
- $this:方法内指"当前对象"的伪变量。
<?php
// 定义类
class User {
// 属性(支持类型声明和默认值)
public string $name;
public int $age;
private string $email; // 私有:仅类内部可访问
// 构造函数(__construct 是魔术方法)
public function __construct(string $name, int $age) {
$this->name = $name; // $this 指当前对象
$this->age = $age;
}
// 方法
public function greet(): string {
return "你好,我是 {$this->name}";
}
}
// 实例化(new)
$user = new User("小明", 25);
echo $user->name; // 小明(访问 public 属性)
echo $user->greet(); // 你好,我是小明
// echo $user->email; // 报错:private 不可外部访问2. 可见性:public / private / protected
可见性控制"谁能访问",是封装的核心:
- public:任何地方都能访问(类内、类外、子类)。
- private:仅本类内部能访问(子类、外部都不行)。
- protected:本类 + 子类(外部不行)。
<?php
class BankAccount {
public string $owner; // 公开:任何地方都能访问
private float $balance; // 私有:仅本类内部
protected int $id; // 受保护:本类 + 子类
public function __construct(string $owner, float $balance = 0) {
$this->owner = $owner;
$this->balance = $balance;
}
// getter / setter:控制对 private 属性的访问
public function getBalance(): float {
return $this->balance;
}
public function deposit(float $amount): void {
if ($amount <= 0) {
throw new InvalidArgumentException("金额必须为正");
}
$this->balance += $amount;
}
// private 方法:仅类内部使用
private function log(string $msg): void {
error_log("[{$this->owner}] {$msg}");
}
}
$acc = new BankAccount("小明", 100);
echo $acc->owner; // 小明(public)
// echo $acc->balance; // 报错(private)
echo $acc->getBalance(); // 100(通过方法访问)
$acc->deposit(50);
echo $acc->getBalance(); // 150最佳实践:属性默认 private 或 protected,通过 getter/setter 暴露——这样你能在 setter 里加校验(如金额必须为正)、能在 getter 里加日志。这是封装的核心价值。
3. 继承:extends
继承让子类复用父类的代码,并扩展或重写:
<?php
// 父类
class Animal {
protected string $name;
public function __construct(string $name) {
$this->name = $name;
}
public function eat(): string {
return "{$this->name} 在吃东西";
}
}
// 子类用 extends 继承
class Dog extends Animal {
public function bark(): string {
return "{$this->name}: 汪汪!"; // 可以访问 protected $name
}
// 重写父类方法
public function eat(): string {
return parent::eat() . "(狗粮)"; // parent:: 调用父类方法
}
}
$dog = new Dog("旺财");
echo $dog->eat(); // 旺财 在吃东西(狗粮)
echo $dog->bark(); // 旺财: 汪汪!几个要点:
- PHP 是单继承:一个类只能 extends 一个父类。
- parent:::显式调用父类方法或构造函数。
- 重写:子类方法签名必须和父类兼容(参数兼容,返回类型遵循协变规则)。
- final 关键字:禁止重写方法或禁止继承类。
- 子类构造函数不会自动调用父类的:需要在子类
__construct里写parent::__construct(...)。
4. 静态成员与常量
static 成员属于类本身,不是某个对象——所有实例共享。常量(const)则是不可修改的固定值:
<?php
class Counter {
// static 属性:属于类本身,所有实例共享
private static int $count = 0;
// 常量(const):不可修改
public const MAX = 100;
public function __construct() {
self::$count++; // self:: 引用本类的 static
}
// static 方法:无需实例化就能调用
public static function getCount(): int {
return self::$count;
}
}
new Counter();
new Counter();
echo Counter::getCount(); // 2(类名::方法)
echo Counter::MAX; // 100(类名::常量)
// self vs static(后期静态绑定)
// self:: 引用定义时的类
// static:: 引用调用时的类(用于多态)关键概念:
- self:::引用定义时的类(在哪个类写就指哪个)。
- static:::引用调用时的类(后期静态绑定 late static binding),用于多态。
- static 方法常用作工厂方法:
User::create(...)。 - 类常量全大写,PHP 8.1+ 还可以用
enum替代。
5. 接口与抽象类
接口和抽象类都是定义"契约"的工具,用于解耦和实现多态:
<?php
// 接口:定义"能做什么",不实现
interface Swimmer {
public function swim(): string; // 只有签名,无实现
}
interface Flyer {
public function fly(): string;
}
// 抽象类:不能实例化,只能被继承
abstract class Vehicle {
abstract protected function maxSpeed(): int; // 子类必须实现
public function info(): string {
return "最高时速:" . $this->maxSpeed();
}
}
// 一个类可以同时继承 + 实现多个接口
class FlyingCar extends Vehicle implements Swimmer, Flyer {
protected function maxSpeed(): int { return 300; }
public function swim(): string { return "水上漂"; }
public function fly(): string { return "飞上天"; }
}
$car = new FlyingCar();
echo $car->info(); // 最高时速:300
echo $car->fly(); // 飞上天对比:
- 接口(interface):只声明方法签名,无实现。一个类可以 implement 多个接口(PHP 单继承的补充)。
- 抽象类(abstract):可以有具体方法,不能实例化,只能被继承。适合共享代码 + 强制契约。
- 选择原则:"是什么"用抽象类,"能做什么"用接口。比如 Animal 是抽象类,Flyer/Swimmer 是接口。
6. PHP 8 新特性
<?php
class Article {
public function __construct(
public string $title, // 构造函数属性提升(PHP 8+)
public string $content,
) {}
// __get:访问不存在/私有的属性时调用
public function __get(string $name): mixed {
if ($name === "summary") {
return mb_substr($this->content, 0, 50);
}
return null;
}
// __toString:对象被当字符串使用时调用
public function __toString(): string {
return $this->title;
}
// __clone:对象被 clone 时调用
public function __clone(): void {
// 深拷贝内部对象
}
}
$art = new Article("标题", "内容...");
echo $art->summary; // 调用 __get
echo $art; // 调用 __toString,输出 "标题"- 构造函数属性提升(PHP 8+):
public string $name直接在参数列表声明属性,省去手写赋值。 - 命名参数:
new User(name: "小明", age: 25)。 - match 表达式:switch 的严格版。
- enum 枚举(PHP 8.1+):类型安全的状态值。
- 属性 Attributes(PHP 8+):替代注解,被广泛用于框架路由、依赖注入。
- readonly 属性(PHP 8.1+):初始化后不可修改,适合值对象。
7. 魔术方法
PHP 内置一组双下划线开头的魔术方法,在特定时机自动调用:
__construct— 构造,创建对象时。__destruct— 析构,对象销毁时。__get/__set— 访问不存在/不可见的属性。__call/__callStatic— 调用不存在/不可见的方法。__toString— 对象被当字符串使用。__clone— 对象被 clone。__invoke— 对象被当函数调用($obj())。
8. 命名空间与自动加载
真实项目用 namespace 避免类名冲突,用PSR-4 自动加载自动加载类文件:
- 声明:在文件顶部用
namespace关键字声明命名空间(命名空间用反斜杠分隔,如 App 后接反斜杠再接 Service)。 - 导入:用
use关键字导入其他类(类名同样用反斜杠分隔)。 - PSR-4 规则:命名空间对应目录结构,如
UserService类放在对应目录下的同名 .php 文件里。 - Composer 的 autoload 一键搞定,详见框架文档。
小结
这一章你掌握了 PHP OOP 的核心:类与对象、可见性、继承、static、接口/抽象类、PHP 8 新特性、魔术方法、命名空间。下一篇PDO 数据库——把数据存进 MySQL。
← 上一篇 PHP 会话管理
下一篇 PHP 数据库(PDO) →