← 返回目录

第三章:函数与面向对象

函数、闭包、类与现代 PHP OOP

1 函数定义

基本函数与类型声明

PHP 支持标量类型声明(intfloatstringbool)以及返回值类型,类似 TypeScript / Python type hints。

function add(int $a, int $b): int
{
    return $a + $b;
}

// 联合类型(PHP 8.0+)
function format(int|float $value): string
{
    return number_format($value, 2);
}

// 交叉类型(PHP 8.1+)
function process(Countable&Iterator $collection): void
{
    // $collection 必须同时实现 Countable 和 Iterator
}

// 返回 never 类型(PHP 8.1+)— 函数永不正常返回
function abort(string $msg): never
{
    throw new RuntimeException($msg);
}

默认值与命名参数

function createUser(
    string $name,
    string $role = 'member',   // 默认值
    bool $active = true
): array {
    return compact('name', 'role', 'active');
}

// 命名参数(PHP 8.0+)— 可跳过中间参数、顺序无关
$user = createUser(name: '张三', active: false);
// ['name' => '张三', 'role' => 'member', 'active' => false]

可变参数与引用传递

// 可变参数(variadic)
function sum(int ...$numbers): int
{
    return array_sum($numbers);
}
echo sum(1, 2, 3, 4); // 10

// 展开操作符传参
$nums = [10, 20, 30];
echo sum(...$nums); // 60

// 引用传递 — 函数内修改会影响外部变量
function increment(int &$value): void
{
    $value++;
}
$count = 5;
increment($count);
echo $count; // 6

⚠️ PHP 8.4:隐式可空类型已弃用

在 PHP 8.4 中,当参数有类型声明且默认值为 null 时,必须显式使用 ?Type 或联合类型 Type|null,否则会产生弃用警告。

// ❌ PHP 8.4 弃用 — 隐式可空
function find(string $key, array $default = null): mixed { ... }

// ✅ 正确写法 — 显式可空
function find(string $key, ?array $default = null): mixed { ... }
// 或者
function find(string $key, array|null $default = null): mixed { ... }

2 匿名函数与箭头函数

匿名函数(闭包)

PHP 的匿名函数需要通过 use 关键字显式捕获外部变量(默认按值捕获)。

$greeting = '你好';

$sayHello = function (string $name) use ($greeting): string {
    return "$greeting, $name!";
};

echo $sayHello('世界'); // 你好, 世界!

// 按引用捕获 — 可修改外部变量
$counter = 0;
$inc = function () use (&$counter): void {
    $counter++;
};
$inc();
$inc();
echo $counter; // 2

// 作为回调使用
$numbers = [3, 1, 4, 1, 5];
usort($numbers, function (int $a, int $b): int {
    return $a - $b;
});

箭头函数(PHP 7.4+)

箭头函数是单表达式的简写闭包,自动按值捕获外部变量,无需 use

$multiplier = 3;

// 箭头函数 — 自动捕获 $multiplier
$triple = fn(int $x): int => $x * $multiplier;
echo $triple(5); // 15

// 在数组操作中非常好用
$prices = [100, 200, 300];
$withTax = array_map(fn($p) => $p * 1.13, $prices);
// [113.0, 226.0, 339.0]

// 箭头函数可嵌套
$addThenMultiply = fn($x) => fn($y) => ($x + $y) * 2;

🔄 跨语言对比:匿名函数

PHP

// 匿名函数
$fn = function($x) use ($y) {
    return $x + $y;
};
// 箭头函数
$fn = fn($x) => $x + $y;

JavaScript

// 箭头函数自动捕获
// const fn = (x) => x + y;
// 普通函数也自动捕获
// const fn = function(x) {
//     return x + y;
// };

Python

// lambda 仅限单表达式
// fn = lambda x: x + y
// def fn(x):
//     return x + y

关键差异:JS/Python 的闭包自动捕获外部变量;PHP 匿名函数必须用 use 显式声明,箭头函数则自动捕获(只读)。

3 命名空间

命名空间用于组织代码、避免类名冲突,类似 Java 的 package 或 Python 的模块系统。

声明与使用

// 文件: src/Models/User.php
namespace App\Models;

class User
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
    ) {}
}

// 文件: src/Services/UserService.php
namespace App\Services;

use App\Models\User;              // 导入类
use App\Models\{User, Product};   // 分组导入
use function App\Helpers\format;  // 导入函数
use const App\Config\VERSION;     // 导入常量

class UserService
{
    public function create(string $name, string $email): User
    {
        return new User($name, $email);
    }
}

PSR-4 自动加载

配合 Composer 的 PSR-4 规范,命名空间与目录结构一一对应,无需手动 require

// composer.json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

📁 目录结构对应关系

project/
├── composer.json
├── vendor/             # Composer 自动生成
│   └── autoload.php
└── src/                # 对应 App\ 命名空间
    ├── Models/
    │   ├── User.php    # App\Models\User
    │   └── Product.php # App\Models\Product
    ├── Services/
    │   └── UserService.php  # App\Services\UserService
    └── Helpers/
        └── functions.php
// 入口文件 index.php
require __DIR__ . '/vendor/autoload.php';

use App\Models\User;
use App\Services\UserService;

$service = new UserService();
$user = $service->create('张三', 'zhang@example.com');

💡 提示:运行 composer dump-autoload 生成/更新自动加载映射。在生产环境使用 composer dump-autoload --optimize 生成类映射以提升性能。

4 类与对象

基本类定义

class Product
{
    // 属性声明
    public string $name;
    protected float $price;
    private int $stock;

    // 静态属性
    private static int $instanceCount = 0;

    public function __construct(string $name, float $price, int $stock = 0)
    {
        $this->name = $name;
        $this->price = $price;
        $this->stock = $stock;
        self::$instanceCount++;
    }

    // 方法
    public function getTotal(int $qty): float
    {
        return $this->price * $qty;
    }

    // 静态方法
    public static function getInstanceCount(): int
    {
        return self::$instanceCount;
    }

    // 魔术方法
    public function __toString(): string
    {
        return "{$this->name} (¥{$this->price})";
    }
}

构造函数提升(PHP 8.0+)

构造函数参数前加可见性修饰符,自动声明并赋值属性——大幅减少样板代码。

// ❌ 传统写法 — 大量重复
class User
{
    private string $name;
    private string $email;
    private string $role;

    public function __construct(string $name, string $email, string $role = 'member')
    {
        $this->name = $name;
        $this->email = $email;
        $this->role = $role;
    }
}

// ✅ 构造函数提升 — 简洁等价
class User
{
    public function __construct(
        private string $name,
        private string $email,
        private string $role = 'member',
    ) {}
    // 自动生成 $this->name, $this->email, $this->role
}

只读属性(PHP 8.1+)与只读类(PHP 8.2+)

// 只读属性 — 初始化后不可修改
class Coordinate
{
    public function __construct(
        public readonly float $lat,
        public readonly float $lng,
    ) {}
}

$point = new Coordinate(39.9042, 116.4074);
echo $point->lat;      // 39.9042
// $point->lat = 0;    // ❌ Error: 只读属性不可修改

// 只读类(PHP 8.2+)— 所有属性自动 readonly
readonly class Money
{
    public function __construct(
        public float $amount,
        public string $currency,
    ) {}
}

💡 可见性总结:

  • public — 任何地方可访问(默认)
  • protected — 仅当前类和子类可访问
  • private — 仅当前类可访问
  • • PHP 8.4 新增 public(set) 非对称可见性:读和写可设置不同级别

5 继承与接口

抽象类与继承

PHP 是单继承语言(与 Java 相同),使用 extends 继承,abstract 定义抽象类。

abstract class Shape
{
    public function __construct(
        protected string $color = 'red',
    ) {}

    // 抽象方法 — 子类必须实现
    abstract public function area(): float;

    // 普通方法 — 可被继承
    public function describe(): string
    {
        return "一个 {$this->color} 的 " . static::class;
    }
}

class Circle extends Shape
{
    public function __construct(
        private float $radius,
        string $color = 'blue',
    ) {
        parent::__construct($color);
    }

    public function area(): float
    {
        return M_PI * $this->radius ** 2;
    }
}

class Rectangle extends Shape
{
    public function __construct(
        private float $width,
        private float $height,
        string $color = 'green',
    ) {
        parent::__construct($color);
    }

    public function area(): float
    {
        return $this->width * $this->height;
    }
}

接口

接口定义契约,一个类可以实现多个接口。

interface Printable
{
    public function toString(): string;
}

interface JsonSerializable
{
    public function toJson(): string;
}

// 实现多个接口
class Invoice implements Printable, \JsonSerializable
{
    public function __construct(
        private string $id,
        private float $total,
    ) {}

    public function toString(): string
    {
        return "Invoice #{$this->id}: ¥{$this->total}";
    }

    // \JsonSerializable 接口要求的方法
    public function jsonSerialize(): mixed
    {
        return ['id' => $this->id, 'total' => $this->total];
    }

    public function toJson(): string
    {
        return json_encode($this->jsonSerialize());
    }
}

实战:策略模式

interface PaymentGateway
{
    public function charge(float $amount): bool;
    public function getName(): string;
}

class AlipayGateway implements PaymentGateway
{
    public function charge(float $amount): bool
    {
        echo "支付宝收款 ¥{$amount}\n";
        return true;
    }

    public function getName(): string { return '支付宝'; }
}

class WechatGateway implements PaymentGateway
{
    public function charge(float $amount): bool
    {
        echo "微信支付收款 ¥{$amount}\n";
        return true;
    }

    public function getName(): string { return '微信支付'; }
}

// 使用接口作为类型约束
class OrderService
{
    public function checkout(PaymentGateway $gateway, float $amount): void
    {
        if ($gateway->charge($amount)) {
            echo "通过 {$gateway->getName()} 支付成功\n";
        }
    }
}

$service = new OrderService();
$service->checkout(new AlipayGateway(), 99.9);
$service->checkout(new WechatGateway(), 199.0);

6 Trait(特征)

Trait 是 PHP 的代码复用机制,解决单继承限制。类似 "复制粘贴" 方法到类中,可理解为可组合的代码片段。

基本用法

trait HasTimestamps
{
    public ?string $createdAt = null;
    public ?string $updatedAt = null;

    public function setCreatedAt(): void
    {
        $this->createdAt = date('Y-m-d H:i:s');
    }

    public function setUpdatedAt(): void
    {
        $this->updatedAt = date('Y-m-d H:i:s');
    }
}

trait SoftDeletes
{
    public ?string $deletedAt = null;

    public function softDelete(): void
    {
        $this->deletedAt = date('Y-m-d H:i:s');
    }

    public function isDeleted(): bool
    {
        return $this->deletedAt !== null;
    }
}

// 在类中使用多个 Trait
class Article
{
    use HasTimestamps, SoftDeletes;

    public function __construct(
        public string $title,
        public string $content,
    ) {
        $this->setCreatedAt();
    }
}

$article = new Article('PHP 教程', '内容...');
echo $article->createdAt;  // 2026-03-31 12:00:00
$article->softDelete();
echo $article->isDeleted(); // true

冲突解决

当多个 Trait 含有同名方法时,使用 insteadofas 解决冲突。

trait Logger
{
    public function log(string $msg): void
    {
        echo "[LOG] $msg\n";
    }
}

trait Debugger
{
    public function log(string $msg): void
    {
        echo "[DEBUG] $msg\n";
    }
}

class App
{
    use Logger, Debugger {
        Logger::log insteadof Debugger;   // 优先使用 Logger 的 log
        Debugger::log as debugLog;         // 将 Debugger 的 log 重命名为 debugLog
    }
}

$app = new App();
$app->log('启动');       // [LOG] 启动
$app->debugLog('详情');  // [DEBUG] 详情

🔄 跨语言对比:代码复用机制

PHP Trait

trait Cacheable {
    public function cache(): void { }
}
class User {
    use Cacheable;
}

Python Mixin

// class CacheMixin:
//     def cache(self): ...
//
// class User(CacheMixin, Base):
//     pass
// (通过多继承实现)

Java default 方法

// interface Cacheable {
//     default void cache() { }
// }
// class User implements Cacheable
//     { }

核心区别:PHP Trait 不是类也不是接口,不能实例化,不参与类型体系(instanceof 不识别 Trait)。Python 用多继承 + MRO 解决,Java 用接口默认方法。

7 枚举 Enum PHP 8.1+

PHP 8.1 引入原生枚举,告别常量模拟。枚举是一等公民,支持方法、接口实现,是类型安全的利器。

纯枚举(Unit Enum)

enum Suit
{
    case Hearts;
    case Diamonds;
    case Clubs;
    case Spades;
}

$card = Suit::Hearts;
echo $card->name; // "Hearts"

// 枚举用于类型约束
function playCard(Suit $suit): void
{
    echo "打出 {$suit->name}\n";
}
playCard(Suit::Spades);

// 枚举比较
var_dump(Suit::Hearts === Suit::Hearts);   // true
var_dump(Suit::Hearts === Suit::Diamonds); // false

回退枚举(Backed Enum)

每个枚举成员关联一个 stringint 值,适合数据库存储和 API 序列化。

enum Status: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';

    // 枚举可以包含方法
    public function label(): string
    {
        return match($this) {
            self::Draft     => '草稿',
            self::Published => '已发布',
            self::Archived  => '已归档',
        };
    }

    public function color(): string
    {
        return match($this) {
            self::Draft     => 'gray',
            self::Published => 'green',
            self::Archived  => 'red',
        };
    }
}

$status = Status::Published;
echo $status->value;   // "published"
echo $status->name;    // "Published"
echo $status->label(); // "已发布"

// 从值构造枚举
$fromDb = Status::from('draft');          // Status::Draft
$maybe  = Status::tryFrom('unknown');     // null(不会抛异常)

// 获取所有枚举成员
$all = Status::cases();
// [Status::Draft, Status::Published, Status::Archived]

枚举实现接口

interface HasDescription
{
    public function description(): string;
}

enum Permission: int implements HasDescription
{
    case Read    = 1;
    case Write   = 2;
    case Execute = 4;

    public function description(): string
    {
        return match($this) {
            self::Read    => '读取权限',
            self::Write   => '写入权限',
            self::Execute => '执行权限',
        };
    }

    // 利用位运算检查权限组合
    public static function check(int $mask, self $perm): bool
    {
        return ($mask & $perm->value) !== 0;
    }
}

$userPerms = Permission::Read->value | Permission::Write->value; // 3
echo Permission::check($userPerms, Permission::Read);    // true
echo Permission::check($userPerms, Permission::Execute); // false

📌 枚举的限制

  • • 枚举不能有构造函数(无状态)
  • • 枚举不能被继承,也不能继承其他类
  • • 枚举可以实现接口、使用 Trait(但 Trait 不能含属性)
  • • 枚举成员是单例,不可 new、不可 clone

8 本章要点

🎯 函数

  • • 参数和返回值类型声明
  • • 命名参数跳过默认值
  • ...$args 可变参数
  • &$var 引用传递
  • • PHP 8.4 隐式可空已弃用

⚡ 闭包

  • • 匿名函数需 use 捕获变量
  • • 箭头函数 fn() => 自动捕获
  • • 箭头函数仅限单表达式
  • • 常用于 array_map 等回调

📦 命名空间

  • namespace + use 组织代码
  • • PSR-4:命名空间映射目录
  • • Composer 自动加载
  • composer dump-autoload

🏗️ 类与对象

  • • 构造函数参数提升(8.0+)
  • readonly 属性(8.1+)
  • readonly class(8.2+)
  • • 三级可见性 + 非对称可见性(8.4)

🔗 继承与接口

  • • 单继承 extends
  • • 多接口 implements
  • abstract 抽象类
  • • Trait 弥补多继承缺失

🏷️ 枚举

  • • 纯枚举 vs 回退枚举
  • from() / tryFrom() 构造
  • • 枚举可含方法、实现接口
  • • 类型安全替代魔术常量

掌握了函数与 OOP,你已经可以编写结构化的 PHP 应用了!

下一章我们将学习 数据库操作,使用 PDO 连接 MySQL 实现 CRUD。