PHP 中 Class 的使用说明

1. 类(Class)的基础概念

在 PHP 中,类是面向对象编程(OOP)的核心概念,它是一种用于创建对象的蓝图或模板。类定义了对象的属性(数据)和方法(行为),而对象则是类的实例化。

类的主要特性包括:

  • 封装:将数据和方法组合在一个单元中
  • 继承:允许一个类基于另一个类构建
  • 多态:允许不同的类实现相同的方法但有不同的行为
  • 抽象:允许定义接口和抽象类

2. 类的定义与实例化

2.1 定义类

使用 class 关键字定义一个类:

<?php
// 定义一个简单的 Person 类
class Person {
    // 属性(成员变量)
    public $name;
    public $age;
    
    // 方法(成员函数)
    public function sayHello() {
        echo "Hello, my name is {$this->name}.";
    }
}
?>

2.2 实例化类(创建对象)

使用 new 关键字创建类的实例:

<?php
// 创建 Person 类的实例
$person = new Person();

// 访问对象的属性并赋值
$person->name = "张三";
$person->age = 30;

// 调用对象的方法
$person->sayHello(); // 输出: Hello, my name is 张三.
?>

2.3 构造函数

构造函数是一种特殊的方法,在创建对象时自动调用,用于初始化对象的属性:

<?php
class Person {
    public $name;
    public $age;
    
    // 构造函数
    public function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }
    
    public function sayHello() {
        echo "Hello, my name is {$this->name}.";
    }
}

// 创建对象时直接传递参数
$person = new Person("张三", 30);
$person->sayHello(); // 输出: Hello, my name is 张三.
?>

2.4 析构函数

析构函数在对象被销毁时自动调用,通常用于清理资源:

<?php
class FileHandler {
    private $file;
    
    public function __construct($filename) {
        $this->file = fopen($filename, "r");
    }
    
    // 析构函数
    public function __destruct() {
        if ($this->file) {
            fclose($this->file);
            echo "文件已关闭";
        }
    }
}

$handler = new FileHandler("example.txt");
// 当 $handler 不再被引用或脚本结束时,析构函数会自动调用
?>

3. 属性(Properties)

属性是类中用于存储数据的变量,也称为成员变量。

3.1 属性的类型

PHP 7.4+ 支持属性类型声明:

<?php
class Person {
    // 基本类型声明
    public string $name;
    public int $age;
    public float $height;
    public bool $isStudent;
    
    // 可为空类型(PHP 8.0+)
    public ?string $email;
    
    // 类类型声明
    public DateTime $registrationDate;
}
?>

3.2 属性的访问控制

PHP 提供了三种访问控制修饰符:

<?php
class Person {
    // 公共属性:可以在任何地方访问
    public $name;
    
    // 受保护属性:只能在类内部或子类中访问
    protected $email;
    
    // 私有属性:只能在定义它的类内部访问
    private $phone;
    
    // ...其他代码
}
?>

3.3 属性的默认值

可以为属性设置默认值:

<?php
class Person {
    public string $name = "Guest";
    public int $age = 0;
    public array $hobbies = [];
    public ?string $email = null;
}
?>

3.4 静态属性

静态属性属于类本身,而不是类的实例:

<?php
class Counter {
    // 静态属性
    public static int $count = 0;
    
    public function __construct() {
        self::$count++; // 使用 self:: 访问静态属性
    }
}

$c1 = new Counter();
$c2 = new Counter();

echo Counter::$count; // 输出: 2
?>

3.5 只读属性(PHP 8.1+)

只读属性只能在初始化时赋值一次:

<?php
class User {
    public readonly int $id;
    
    public function __construct(int $id) {
        $this->id = $id; // 只能在构造函数中赋值
    }
}

$user = new User(123);
// $user->id = 456; // 错误:不能修改只读属性
?>

4. 方法(Methods)

方法是类中定义的函数,用于执行特定操作。

4.1 方法的定义和调用

<?php
class Calculator {
    // 方法定义
    public function add(int $a, int $b): int {
        return $a + $b;
    }
}

$calc = new Calculator();
$result = $calc->add(5, 3); // 调用方法

echo $result; // 输出: 8
?>

4.2 方法的访问控制

与属性一样,方法也有三种访问控制修饰符:

<?php
class Person {
    private $name;
    
    // 公共方法
    public function setName(string $name): void {
        $this->name = $name;
    }
    
    // 受保护方法
    protected function validateName(string $name): bool {
        return strlen($name) > 0;
    }
    
    // 私有方法
    private function formatName(string $name): string {
        return ucfirst($name);
    }
}
?>

4.3 静态方法

静态方法属于类本身,不依赖于类的实例:

<?php
class Math {
    // 静态方法
    public static function square(int $num): int {
        return $num * $num;
    }
}

// 直接通过类名调用静态方法
$result = Math::square(5);
echo $result; // 输出: 25
?>

4.4 抽象方法

抽象方法是在抽象类中声明但不实现的方法,子类必须实现它:

<?php
abstract class Shape {
    // 抽象方法
    abstract public function calculateArea(): float;
}

class Rectangle extends Shape {
    private $width;
    private $height;
    
    public function __construct(float $width, float $height) {
        $this->width = $width;
        $this->height = $height;
    }
    
    // 实现抽象方法
    public function calculateArea(): float {
        return $this->width * $this->height;
    }
}
?>

4.5 魔术方法

PHP 提供了一系列魔术方法,这些方法以双下划线 __ 开头,在特定情况下自动调用:

<?php
class Person {
    private $data = [];
    
    // 属性访问魔术方法
    public function __get($name) {
        return $this->data[$name] ?? null;
    }
    
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
    
    public function __isset($name) {
        return isset($this->data[$name]);
    }
    
    public function __unset($name) {
        unset($this->data[$name]);
    }
    
    // 方法调用魔术方法
    public function __call($name, $arguments) {
        echo "调用了方法 $name ,参数:" . implode(', ', $arguments);
    }
    
    // 静态方法调用魔术方法
    public static function __callStatic($name, $arguments) {
        echo "调用了静态方法 $name ,参数:" . implode(', ', $arguments);
    }
    
    // 字符串转换魔术方法
    public function __toString() {
        return "Person object";
    }
}
?>

5. 继承(Inheritance)

继承允许一个类(子类)基于另一个类(父类)构建,从而重用代码:

5.1 基本继承

<?php
// 父类
class Animal {
    protected $name;
    
    public function __construct($name) {
        $this->name = $name;
    }
    
    public function getName() {
        return $this->name;
    }
    
    public function makeSound() {
        echo "Some generic sound";
    }
}

// 子类继承父类
class Dog extends Animal {
    // 重写父类的方法
    public function makeSound() {
        echo "Woof! Woof!";
    }
    
    // 子类特有的方法
    public function fetch() {
        echo "{$this->name} is fetching the ball";
    }
}

// 创建子类的实例
$dog = new Dog("Rex");
echo $dog->getName(); // 输出: Rex
$dog->makeSound(); // 输出: Woof! Woof!
$dog->fetch(); // 输出: Rex is fetching the ball
?>

5.2 调用父类的方法

使用 parent:: 关键字调用父类的方法:

<?php
class ParentClass {
    public function sayHello() {
        echo "Hello from Parent";
    }
}

class ChildClass extends ParentClass {
    public function sayHello() {
        // 调用父类的方法
        parent::sayHello();
        echo "Hello from Child";
    }
}

$child = new ChildClass();
$child->sayHello(); // 输出: Hello from ParentHello from Child
?>

5.3 多继承的替代方案(Trait)

PHP 不支持多继承,但可以使用 Trait 实现类似功能:

<?php
// 定义一个 Trait
 trait Logger {
    public function log($message) {
        echo "[LOG] $message\n";
    }
}

// 定义另一个 Trait
 trait Database {
    public function connect() {
        echo "Connected to database\n";
    }
}

// 使用多个 Trait
class User {
    use Logger, Database;
    
    public function register() {
        $this->connect();
        $this->log("User registered");
    }
}

$user = new User();
$user->register(); // 输出: Connected to database [LOG] User registered
?>

6. 多态(Polymorphism)

多态允许不同的类实现相同的接口或继承自同一个父类,但有不同的行为:

<?php
// 定义一个接口
interface Shape {
    public function calculateArea();
}

// 实现接口的类
class Circle implements Shape {
    private $radius;
    
    public function __construct($radius) {
        $this->radius = $radius;
    }
    
    public function calculateArea() {
        return pi() * pow($this->radius, 2);
    }
}

class Rectangle implements Shape {
    private $width;
    private $height;
    
    public function __construct($width, $height) {
        $this->width = $width;
        $this->height = $height;
    }
    
    public function calculateArea() {
        return $this->width * $this->height;
    }
}

// 多态的使用
function printArea(Shape $shape) {
    echo "Area: " . $shape->calculateArea() . "\n";
}

$circle = new Circle(5);
$rectangle = new Rectangle(4, 6);

printArea($circle); // 输出: Area: 78.539816339745
printArea($rectangle); // 输出: Area: 24
?>

7. 抽象类与接口

7.1 抽象类

抽象类是不能直接实例化的类,可以包含抽象方法和普通方法:

<?php
// 定义抽象类
abstract class Vehicle {
    protected $brand;
    
    public function __construct($brand) {
        $this->brand = $brand;
    }
    
    // 普通方法
    public function getBrand() {
        return $this->brand;
    }
    
    // 抽象方法
    abstract public function startEngine();
}

// 继承抽象类并实现抽象方法
class Car extends Vehicle {
    public function startEngine() {
        echo "{$this->brand} car engine started";
    }
}

// 不能直接实例化抽象类
// $vehicle = new Vehicle("Toyota"); // 错误

$car = new Car("Toyota");
$car->startEngine(); // 输出: Toyota car engine started
?>

7.2 接口

接口定义了一个类应该实现的方法,但不提供方法的具体实现:

<?php
// 定义接口
interface Logger {
    public function logInfo($message);
    public function logError($message);
    public function logWarning($message);
}

// 实现接口
class FileLogger implements Logger {
    private $logFile;
    
    public function __construct($logFile) {
        $this->logFile = $logFile;
    }
    
    public function logInfo($message) {
        $this->writeLog("INFO", $message);
    }
    
    public function logError($message) {
        $this->writeLog("ERROR", $message);
    }
    
    public function logWarning($message) {
        $this->writeLog("WARNING", $message);
    }
    
    private function writeLog($level, $message) {
        $timestamp = date("Y-m-d H:i:s");
        $logEntry = "[$timestamp] [$level] $message\n";
        file_put_contents($this->logFile, $logEntry, FILE_APPEND);
    }
}

// 使用实现了接口的类
$logger = new FileLogger("app.log");
$logger->logInfo("Application started");
$logger->logError("Database connection failed");
?>

7.3 抽象类与接口的区别

特性 抽象类 接口
方法实现 可以包含已实现的方法 只能包含方法声明,不能实现
属性 可以包含各种访问级别的属性 只能包含公共静态常量
继承 一个类只能继承一个抽象类 一个类可以实现多个接口
构造函数 可以有构造函数 不能有构造函数
访问控制 可以使用各种访问修饰符 方法默认为公共,不能使用其他修饰符

8. 面向对象设计原则

8.1 单一职责原则(SRP)

一个类应该只有一个责任:

<?php
// 好的设计:User 类只负责用户数据和行为
class User {
    private $id;
    private $name;
    private $email;
    
    // 只包含与用户相关的方法
    public function getName() { return $this->name; }
    public function setName($name) { $this->name = $name; }
    // ...
}

// 好的设计:UserRepository 类只负责数据库操作
class UserRepository {
    public function save(User $user) { /* 保存用户到数据库 */ }
    public function findById($id) { /* 从数据库查找用户 */ }
    // ...
}
?>

8.2 开闭原则(OCP)

软件实体应该对扩展开放,对修改关闭:

<?php
// 好的设计:使用接口和继承实现开闭原则
interface PaymentMethod {
    public function pay($amount);
}

class CreditCardPayment implements PaymentMethod {
    public function pay($amount) { /* 信用卡支付逻辑 */ }
}

class PayPalPayment implements PaymentMethod {
    public function pay($amount) { /* PayPal支付逻辑 */ }
}

// PaymentProcessor 对扩展开放,对修改关闭
class PaymentProcessor {
    public function processPayment(PaymentMethod $method, $amount) {
        return $method->pay($amount);
    }
}

// 新增支付方式不需要修改 PaymentProcessor
class BitcoinPayment implements PaymentMethod {
    public function pay($amount) { /* 比特币支付逻辑 */ }
}
?>

8.3 里氏替换原则(LSP)

子类应该能够替换其父类:

<?php
class Rectangle {
    protected $width;
    protected $height;
    
    public function setWidth($width) { $this->width = $width; }
    public function setHeight($height) { $this->height = $height; }
    public function getArea() { return $this->width * $this->height; }
}

// 正方形是一种特殊的矩形,但这个实现违反了里氏替换原则
class Square extends Rectangle {
    public function setWidth($width) {
        $this->width = $width;
        $this->height = $width;
    }
    
    public function setHeight($height) {
        $this->width = $height;
        $this->height = $height;
    }
}

// 更好的设计:使用组合而非继承
interface Shape {
    public function getArea();
}

class Rectangle implements Shape {
    // ...
}

class Square implements Shape {
    // ...
}
?>

8.4 接口隔离原则(ISP)

不应该强迫客户端依赖于它们不使用的方法:

<?php
// 不好的设计:一个大而全的接口
interface Worker {
    public function work();
    public function eat();
    public function sleep();
}

// 好的设计:将接口拆分为更小、更具体的接口
interface Workable {
    public function work();
}

interface Eatable {
    public function eat();
}

interface Sleepable {
    public function sleep();
}

// 客户端可以只实现需要的接口
class HumanWorker implements Workable, Eatable, Sleepable {
    // 实现所有方法
}

class RobotWorker implements Workable {
    // 只需要实现 work() 方法
    public function work() { /* 工作逻辑 */ }
}
?>

8.5 依赖倒置原则(DIP)

高层模块不应该依赖于低层模块,两者都应该依赖于抽象:

<?php
// 不好的设计:高层模块直接依赖低层模块
class UserService {
    private $mysqlRepository; // 直接依赖具体类
    
    public function __construct() {
        $this->mysqlRepository = new MySQLUserRepository();
    }
}

// 好的设计:依赖于抽象接口
interface UserRepository {
    public function findById($id);
    public function save(User $user);
}

class MySQLUserRepository implements UserRepository {
    // 实现接口方法
}

class MongoDBUserRepository implements UserRepository {
    // 实现接口方法
}

class UserService {
    private $repository; // 依赖于抽象接口
    
    // 通过构造函数注入依赖
    public function __construct(UserRepository $repository) {
        $this->repository = $repository;
    }
}
?>

9. 高级类特性

9.1 类的自动加载

使用 spl_autoload_register() 实现类的自动加载:

<?php
// 自动加载函数
function my_autoloader($class) {
    // 将命名空间分隔符转换为目录分隔符
    $path = str_replace('\\', DIRECTORY_SEPARATOR, $class);
    
    // 加载对应的类文件
    require_once __DIR__ . '/classes/' . $path . '.php';
}

// 注册自动加载函数
spl_autoload_register('my_autoloader');

// 现在可以直接使用类,不需要手动引入
$user = new App\Models\User();
?>

9.2 命名空间

命名空间可以避免类名冲突:

<?php
// 定义命名空间
namespace App\Models;

class User {
    // 类定义
}

// 在其他文件中使用命名空间
namespace App\Controllers;

// 使用 use 语句引入类
use App\Models\User;

class UserController {
    public function index() {
        $user = new User();
        // ...
    }
}

// 也可以使用完全限定名称
$user = new \App\Models\User();
?>

9.3 反射

反射 API 允许在运行时检查类、接口、方法和属性:

<?php
class Person {
    public $name;
    private $age;
    
    public function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }
    
    public function sayHello() {
        echo "Hello, my name is {$this->name}.";
    }
}

// 创建反射类
$reflection = new ReflectionClass('Person');

// 获取类的信息
echo "类名: " . $reflection->getName() . "\n";

echo "公共方法: " . "\n";
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
    echo " - " . $method->getName() . "\n";
}

echo "属性: " . "\n";
foreach ($reflection->getProperties() as $property) {
    echo " - " . $property->getName() . "\n";
}

// 创建实例
$person = $reflection->newInstanceArgs(["张三", 30]);
$person->sayHello(); // 输出: Hello, my name is 张三.
?>

9.4 对象序列化

序列化将对象转换为字符串,反序列化则将字符串恢复为对象:

<?php
class Person {
    public $name;
    public $age;
    
    public function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }
    
    public function sayHello() {
        echo "Hello, my name is {$this->name}.";
    }
}

// 创建对象
$person = new Person("张三", 30);

// 序列化对象
$serialized = serialize($person);
echo $serialized; // 输出序列化的字符串

// 将序列化的字符串存储到文件
file_put_contents("person.txt", $serialized);

// 从文件读取序列化的字符串
$serialized = file_get_contents("person.txt");

// 反序列化对象
$unserialized = unserialize($serialized);
$unserialized->sayHello(); // 输出: Hello, my name is 张三.
?>

10. 实际应用示例

10.1 基础的 MVC 结构

<?php
// 模型(Model)
class UserModel {
    private $db;
    
    public function __construct($db) {
        $this->db = $db;
    }
    
    public function getUserById($id) {
        // 从数据库获取用户
    }
    
    public function saveUser($userData) {
        // 保存用户到数据库
    }
}

// 视图(View)
class UserView {
    public function renderUserProfile($user) {
        // 渲染用户资料页面
        echo "<h1>{$user['name']}</h1>";
        echo "<p>Email: {$user['email']}</p>";
    }
    
    public function renderUserForm() {
        // 渲染用户表单
        echo "<form>...</form>";
    }
}

// 控制器(Controller)
class UserController {
    private $model;
    private $view;
    
    public function __construct($model, $view) {
        $this->model = $model;
        $this->view = $view;
    }
    
    public function showProfile($id) {
        $user = $this->model->getUserById($id);
        $this->view->renderUserProfile($user);
    }
    
    public function showEditForm($id) {
        $user = $this->model->getUserById($id);
        $this->view->renderUserForm($user);
    }
    
    public function updateUser($id, $userData) {
        $this->model->saveUser($userData);
        // 重定向到用户资料页
    }
}

// 使用示例
$db = new PDO("mysql:host=localhost;dbname=test", "root", "password");
$userModel = new UserModel($db);
$userView = new UserView();
$userController = new UserController($userModel, $userView);

// 路由决定调用哪个控制器方法
$userController->showProfile(1);
?>

10.2 单例模式

<?php
// 单例模式确保一个类只有一个实例
class Database {
    // 保存单例实例的静态属性
    private static $instance = null;
    
    // 数据库连接
    private $connection;
    
    // 私有构造函数防止外部实例化
    private function __construct() {
        $this->connection = new PDO("mysql:host=localhost;dbname=test", "root", "password");
    }
    
    // 防止克隆对象
    private function __clone() {}
    
    // 防止反序列化
    private function __wakeup() {}
    
    // 获取单例实例的公共静态方法
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        
        return self::$instance;
    }
    
    // 获取数据库连接
    public function getConnection() {
        return $this->connection;
    }
}

// 使用单例
$db1 = Database::getInstance();
$db2 = Database::getInstance();

// $db1 和 $db2 是同一个实例
var_dump($db1 === $db2); // 输出: bool(true)

// 使用连接
$conn = $db1->getConnection();
?>

10.3 工厂模式

<?php
// 产品接口
interface Vehicle {
    public function drive();
}

// 具体产品
class Car implements Vehicle {
    public function drive() {
        echo "Driving a car";
    }
}

class Motorcycle implements Vehicle {
    public function drive() {
        echo "Riding a motorcycle";
    }
}

// 工厂类
class VehicleFactory {
    // 工厂方法
    public static function createVehicle($type) {
        switch ($type) {
            case 'car':
                return new Car();
            case 'motorcycle':
                return new Motorcycle();
            default:
                throw new Exception("Unsupported vehicle type");
        }
    }
}

// 使用工厂创建对象
$car = VehicleFactory::createVehicle('car');
$car->drive(); // 输出: Driving a car

$motorcycle = VehicleFactory::createVehicle('motorcycle');
$motorcycle->drive(); // 输出: Riding a motorcycle
?>

11. 类的最佳实践

11.1 命名规范

  • 类名使用大驼峰命名法(PascalCase):User, DatabaseConnection
  • 方法名和属性名使用小驼峰命名法(camelCase):getUserById(), $firstName
  • 常量名使用全大写加下划线:MAX_CONNECTIONS, DEFAULT_TIMEOUT
  • 私有属性和方法可以使用下划线前缀:$_privateVar, _privateMethod()

11.2 代码组织

  • 将每个类放在单独的文件中
  • 使用命名空间组织类
  • 遵循 PSR 标准(特别是 PSR-1、PSR-4)
  • 使用自动加载而非手动引入

11.3 可维护性

  • 保持类的职责单一
  • 编写清晰的注释和文档
  • 使用类型声明提高代码的可靠性
  • 避免深度嵌套的继承层次
  • 优先使用组合而非继承
  • 合理使用设计模式

11.4 安全性

  • 不要在类中硬编码敏感信息
  • 使用访问控制修饰符保护类的内部状态
  • 对输入进行验证和过滤
  • 避免魔术方法的滥用
  • 注意序列化安全

12. 总结

PHP 的类和面向对象编程提供了强大的工具,使代码更有组织性、可重用性和可维护性。通过合理使用类、继承、接口、抽象类和设计模式,您可以构建复杂且灵活的应用程序。

本指南涵盖了 PHP 类的核心概念和高级特性,包括类的定义与实例化、属性与方法、访问控制、继承、多态、抽象类与接口、设计原则和实际应用示例。通过不断学习和实践,您可以进一步提升您的面向对象编程技能。

更多推荐