写点什么

PHP 面向对象编程高级教程

作者:白月书生
  • 2025-06-18
    浙江
  • 本文字数:56325 字

    阅读完需:约 185 分钟

前言

本教程是 PHP 面向对象编程入门教程的进阶版,适合已经掌握基本面向对象概念的开发者。我们将深入探讨 PHP 中更高级的面向对象编程特性,并通过实际案例展示如何在项目中应用这些概念。

目录

  1. 高级 OOP 概念

  2. 抽象类和抽象方法

  3. 接口和多重实现

  4. 静态属性和方法

  5. 后期静态绑定

  6. 魔术方法

  7. 特性(Traits)

  8. 基本使用

  9. 解决多继承问题

  10. 特性中的冲突解决

  11. 命名空间和自动加载

  12. 命名空间基础

  13. PSR-4 标准

  14. Composer 自动加载

  15. 设计模式

  16. 单例模式

  17. 工厂模式

  18. 观察者模式

  19. 依赖注入

  20. 反射 API 和元编程

  21. 反射类和方法

  22. 动态创建对象

  23. 注解的使用

  24. 实际项目案例

  25. 简单 MVC 框架实现

  26. API 开发中的 OOP 应用

高级 OOP 概念

抽象类和抽象方法

抽象类是一种不能被实例化的类,它的主要目的是为子类提供一个通用的模板。抽象类可以包含抽象方法(没有实现的方法)和普通方法。


<?php/** * 抽象支付处理器类 * 定义了所有支付处理器必须实现的方法 */abstract class PaymentProcessor {    /**     * 支付处理器名称     * @var string     */    protected $name;        /**     * 支付处理器描述     * @var string     */    protected $description;        /**     * 构造函数     *      * @param string $name 处理器名称     * @param string $description 处理器描述     */    public function __construct(string $name, string $description) {        $this->name = $name;        $this->description = $description;    }        /**     * 获取处理器信息     *      * @return array 处理器信息     */    public function getInfo(): array {        return [            'name' => $this->name,            'description' => $this->description        ];    }        /**     * 处理支付     * 抽象方法,必须由子类实现     *      * @param float $amount 支付金额     * @param array $paymentDetails 支付详情     * @return bool 支付是否成功     */    abstract public function processPayment(float $amount, array $paymentDetails): bool;        /**     * 退款处理     * 抽象方法,必须由子类实现     *      * @param string $transactionId 交易ID     * @param float $amount 退款金额     * @return bool 退款是否成功     */    abstract public function processRefund(string $transactionId, float $amount): bool;        /**     * 验证支付详情     * 抽象方法,必须由子类实现     *      * @param array $paymentDetails 支付详情     * @return bool 验证是否通过     */    abstract protected function validatePaymentDetails(array $paymentDetails): bool;}
/** * 信用卡支付处理器 * 继承自抽象支付处理器类 */class CreditCardProcessor extends PaymentProcessor { /** * 支持的信用卡类型 * @var array */ private $supportedCardTypes = ['visa', 'mastercard', 'amex']; /** * 构造函数 */ public function __construct() { parent::__construct('信用卡支付', '通过信用卡处理支付'); } /** * 实现处理支付方法 * * @param float $amount 支付金额 * @param array $paymentDetails 支付详情 * @return bool 支付是否成功 */ public function processPayment(float $amount, array $paymentDetails): bool { // 验证支付详情 if (!$this->validatePaymentDetails($paymentDetails)) { echo "支付详情验证失败\n"; return false; } // 模拟支付处理 echo "使用信用卡处理 {$amount} 元的支付...\n"; // 获取卡信息(实际应用中会有更多安全措施) $cardNumber = $paymentDetails['card_number']; $cardType = $this->getCardType($cardNumber); $lastFour = substr($cardNumber, -4); echo "使用 {$cardType} 卡 (尾号 {$lastFour}) 支付成功!\n"; return true; } /** * 实现退款处理方法 * * @param string $transactionId 交易ID * @param float $amount 退款金额 * @return bool 退款是否成功 */ public function processRefund(string $transactionId, float $amount): bool { echo "处理交易 {$transactionId} 的退款,金额:{$amount} 元...\n"; echo "退款成功!\n"; return true; } /** * 实现验证支付详情方法 * * @param array $paymentDetails 支付详情 * @return bool 验证是否通过 */ protected function validatePaymentDetails(array $paymentDetails): bool { // 检查必要字段 $requiredFields = ['card_number', 'expiry_date', 'cvv', 'cardholder_name']; foreach ($requiredFields as $field) { if (!isset($paymentDetails[$field]) || empty($paymentDetails[$field])) { echo "缺少必要字段: {$field}\n"; return false; } } // 验证卡号(简化版) $cardNumber = $paymentDetails['card_number']; if (!is_numeric($cardNumber) || strlen($cardNumber) < 13 || strlen($cardNumber) > 19) { echo "无效的卡号\n"; return false; } // 验证卡类型 $cardType = $this->getCardType($cardNumber); if (!in_array($cardType, $this->supportedCardTypes)) { echo "不支持的卡类型: {$cardType}\n"; return false; } // 验证过期日期(简化版) $expiryDate = $paymentDetails['expiry_date']; if (!preg_match('/^(0[1-9]|1[0-2])\/([0-9]{2})$/', $expiryDate)) { echo "无效的过期日期格式,应为MM/YY\n"; return false; } // 验证CVV(简化版) $cvv = $paymentDetails['cvv']; if (!is_numeric($cvv) || strlen($cvv) < 3 || strlen($cvv) > 4) { echo "无效的CVV\n"; return false; } return true; } /** * 根据卡号获取卡类型 * * @param string $cardNumber 卡号 * @return string 卡类型 */ private function getCardType(string $cardNumber): string { // 简化的卡类型检测 $firstDigit = substr($cardNumber, 0, 1); $firstTwoDigits = substr($cardNumber, 0, 2); if ($firstDigit === '4') { return 'visa'; } elseif ($firstTwoDigits >= '51' && $firstTwoDigits <= '55') { return 'mastercard'; } elseif ($firstTwoDigits === '34' || $firstTwoDigits === '37') { return 'amex'; } else { return 'unknown'; } }}
/** * PayPal支付处理器 * 继承自抽象支付处理器类 */class PayPalProcessor extends PaymentProcessor { /** * API凭证 * @var array */ private $apiCredentials; /** * 构造函数 * * @param string $clientId PayPal客户端ID * @param string $secret PayPal密钥 */ public function __construct(string $clientId, string $secret) { parent::__construct('PayPal', '通过PayPal处理支付'); $this->apiCredentials = [ 'client_id' => $clientId, 'secret' => $secret ]; } /** * 实现处理支付方法 * * @param float $amount 支付金额 * @param array $paymentDetails 支付详情 * @return bool 支付是否成功 */ public function processPayment(float $amount, array $paymentDetails): bool { // 验证支付详情 if (!$this->validatePaymentDetails($paymentDetails)) { echo "支付详情验证失败\n"; return false; } // 模拟PayPal API调用 echo "连接到PayPal API...\n"; echo "使用PayPal处理 {$amount} 元的支付...\n"; echo "支付成功!交易ID: " . $this->generateTransactionId() . "\n"; return true; } /** * 实现退款处理方法 * * @param string $transactionId 交易ID * @param float $amount 退款金额 * @return bool 退款是否成功 */ public function processRefund(string $transactionId, float $amount): bool { echo "连接到PayPal API...\n"; echo "处理交易 {$transactionId} 的退款,金额:{$amount} 元...\n"; echo "退款成功!\n"; return true; } /** * 实现验证支付详情方法 * * @param array $paymentDetails 支付详情 * @return bool 验证是否通过 */ protected function validatePaymentDetails(array $paymentDetails): bool { // 检查必要字段 if (!isset($paymentDetails['email']) || !filter_var($paymentDetails['email'], FILTER_VALIDATE_EMAIL)) { echo "无效的PayPal邮箱地址\n"; return false; } return true; } /** * 生成交易ID * * @return string 交易ID */ private function generateTransactionId(): string { return 'PP-' . strtoupper(uniqid()); }}
// 使用示例// 注意:抽象类不能直接实例化// $processor = new PaymentProcessor(); // 这会产生错误
// 创建信用卡处理器$creditCardProcessor = new CreditCardProcessor();$paymentDetails = [ 'card_number' => '4111111111111111', 'expiry_date' => '12/25', 'cvv' => '123', 'cardholder_name' => '张三'];$creditCardProcessor->processPayment(100.50, $paymentDetails);
echo "\n";
// 创建PayPal处理器$paypalProcessor = new PayPalProcessor('client_id_here', 'secret_here');$paypalDetails = [ 'email' => 'customer@example.com'];$paypalProcessor->processPayment(100.50, $paypalDetails);
// 处理退款$transactionId = 'TX123456';$paypalProcessor->processRefund($transactionId, 50.25);
复制代码

接口和多重实现

接口定义了一个类必须实现的方法,但不包含方法的具体实现。一个类可以实现多个接口,从而实现多重继承的效果。


<?php/** * 可记录接口 * 定义可记录对象必须实现的方法 */interface Loggable {    /**     * 获取日志数据     *      * @return array 日志数据     */    public function getLogData(): array;        /**     * 记录日志     *      * @param string $message 日志消息     * @return bool 是否成功记录     */    public function log(string $message): bool;}
/** * 可序列化接口 * 定义可序列化对象必须实现的方法 */interface Serializable { /** * 序列化对象 * * @return string 序列化后的字符串 */ public function serialize(): string; /** * 反序列化对象 * * @param string $data 序列化的数据 * @return void */ public function unserialize(string $data): void;}
/** * 用户类 * 实现了Loggable和Serializable接口 */class User implements Loggable, Serializable { /** * 用户ID * @var int */ private $id; /** * 用户名 * @var string */ private $username; /** * 电子邮件 * @var string */ private $email; /** * 创建时间 * @var string */ private $createdAt; /** * 构造函数 * * @param int $id 用户ID * @param string $username 用户名 * @param string $email 电子邮件 */ public function __construct(int $id, string $username, string $email) { $this->id = $id; $this->username = $username; $this->email = $email; $this->createdAt = date('Y-m-d H:i:s'); } /** * 获取用户信息 * * @return array 用户信息 */ public function getInfo(): array { return [ 'id' => $this->id, 'username' => $this->username, 'email' => $this->email, 'created_at' => $this->createdAt ]; } /** * 实现Loggable接口的getLogData方法 * * @return array 日志数据 */ public function getLogData(): array { return [ 'entity' => 'user', 'id' => $this->id, 'username' => $this->username, 'time' => date('Y-m-d H:i:s') ]; } /** * 实现Loggable接口的log方法 * * @param string $message 日志消息 * @return bool 是否成功记录 */ public function log(string $message): bool { $logData = $this->getLogData(); $logEntry = date('Y-m-d H:i:s') . " [{$logData['entity']}:{$logData['id']}] {$message}\n"; // 在实际应用中,这里会将日志写入文件或数据库 echo "记录日志: {$logEntry}"; return true; } /** * 实现Serializable接口的serialize方法 * * @return string 序列化后的字符串 */ public function serialize(): string { return json_encode([ 'id' => $this->id, 'username' => $this->username, 'email' => $this->email, 'created_at' => $this->createdAt ]); } /** * 实现Serializable接口的unserialize方法 * * @param string $data 序列化的数据 * @return void */ public function unserialize(string $data): void { $userData = json_decode($data, true); $this->id = $userData['id'] ?? 0; $this->username = $userData['username'] ?? ''; $this->email = $userData['email'] ?? ''; $this->createdAt = $userData['created_at'] ?? date('Y-m-d H:i:s'); }}
/** * 订单类 * 实现了Loggable接口 */class Order implements Loggable { /** * 订单ID * @var string */ private $id; /** * 用户ID * @var int */ private $userId; /** * 订单金额 * @var float */ private $amount; /** * 订单状态 * @var string */ private $status; /** * 创建时间 * @var string */ private $createdAt; /** * 构造函数 * * @param int $userId 用户ID * @param float $amount 订单金额 */ public function __construct(int $userId, float $amount) { $this->id = $this->generateOrderId(); $this->userId = $userId; $this->amount = $amount; $this->status = 'pending'; $this->createdAt = date('Y-m-d H:i:s'); } /** * 生成订单ID * * @return string 订单ID */ private function generateOrderId(): string { return 'ORD-' . strtoupper(uniqid()); } /** * 更新订单状态 * * @param string $status 新状态 * @return bool 是否成功更新 */ public function updateStatus(string $status): bool { $validStatuses = ['pending', 'paid', 'shipped', 'completed', 'cancelled']; if (!in_array($status, $validStatuses)) { echo "无效的订单状态: {$status}\n"; return false; } $oldStatus = $this->status; $this->status = $status; $this->log("订单状态从 {$oldStatus} 更新为 {$status}"); return true; } /** * 获取订单信息 * * @return array 订单信息 */ public function getInfo(): array { return [ 'id' => $this->id, 'user_id' => $this->userId, 'amount' => $this->amount, 'status' => $this->status, 'created_at' => $this->createdAt ]; } /** * 实现Loggable接口的getLogData方法 * * @return array 日志数据 */ public function getLogData(): array { return [ 'entity' => 'order', 'id' => $this->id, 'user_id' => $this->userId, 'amount' => $this->amount, 'time' => date('Y-m-d H:i:s') ]; } /** * 实现Loggable接口的log方法 * * @param string $message 日志消息 * @return bool 是否成功记录 */ public function log(string $message): bool { $logData = $this->getLogData(); $logEntry = date('Y-m-d H:i:s') . " [{$logData['entity']}:{$logData['id']}] {$message}\n"; // 在实际应用中,这里会将日志写入文件或数据库 echo "记录日志: {$logEntry}"; return true; }}
/** * 日志管理器类 * 处理所有可记录对象的日志 */class LogManager { /** * 记录对象的活动 * * @param Loggable $entity 可记录的对象 * @param string $action 执行的操作 * @return bool 是否成功记录 */ public function logActivity(Loggable $entity, string $action): bool { return $entity->log("执行操作: {$action}"); } /** * 批量记录多个对象的活动 * * @param array $entities 可记录的对象数组 * @param string $action 执行的操作 * @return array 记录结果 */ public function logBulkActivity(array $entities, string $action): array { $results = []; foreach ($entities as $entity) { if ($entity instanceof Loggable) { $results[] = $this->logActivity($entity, $action); } else { echo "警告: 对象不是Loggable类型,无法记录日志\n"; $results[] = false; } } return $results; }}
// 使用示例// 创建用户$user = new User(1, 'johndoe', 'john@example.com');
// 创建订单$order = new Order(1, 299.99);
// 使用日志管理器$logManager = new LogManager();$logManager->logActivity($user, '登录');$logManager->logActivity($order, '创建');
// 更新订单状态$order->updateStatus('paid');
// 序列化用户对象$serializedUser = $user->serialize();echo "序列化的用户数据: {$serializedUser}\n";
// 创建新用户并从序列化数据恢复$newUser = new User(0, '', '');$newUser->unserialize($serializedUser);print_r($newUser->getInfo());
// 批量记录日志$entities = [$user, $order];$logManager->logBulkActivity($entities, '数据备份');
复制代码

静态属性和方法

静态属性和方法属于类本身,而不是类的实例。它们可以在不创建类实例的情况下访问。


<?php/** * 配置管理器类 * 使用静态属性和方法管理应用程序配置 */class ConfigManager {    /**     * 配置数据     * @var array     */    private static $config = [];        /**     * 是否已初始化     * @var bool     */    private static $initialized = false;        /**     * 初始化配置     *      * @param string $configFile 配置文件路径     * @return bool 是否成功初始化     */    public static function initialize(string $configFile): bool {        if (self::$initialized) {            return true;        }                if (!file_exists($configFile)) {            throw new Exception("配置文件不存在: {$configFile}");        }                try {            self::$config = json_decode(file_get_contents($configFile), true);            self::$initialized = true;            return true;        } catch (Exception $e) {            echo "配置初始化失败: " . $e->getMessage() . "\n";            return false;        }    }        /**     * 获取配置值     *      * @param string $key 配置键名     * @param mixed $default 默认值     * @return mixed 配置值     */    public static function get(string $key, $default = null) {        if (!self::$initialized) {            throw new Exception("配置管理器未初始化");        }                return self::$config[$key] ?? $default;    }        /**     * 设置配置值     *      * @param string $key 配置键名     * @param mixed $value 配置值     * @return void     */    public static function set(string $key, $value): void {        if (!self::$initialized) {            throw new Exception("配置管理器未初始化");        }                self::$config[$key] = $value;    }        /**     * 检查配置是否存在     *      * @param string $key 配置键名     * @return bool 是否存在     */    public static function has(string $key): bool {        return isset(self::$config[$key]);    }        /**     * 获取所有配置     *      * @return array 配置数组     */    public static function all(): array {        return self::$config;    }}
/** * 计数器类 * 演示静态属性的使用 */class Counter { /** * 计数器值 * @var int */ private static $count = 0; /** * 计数器历史记录 * @var array */ private static $history = []; /** * 增加计数 * * @param int $value 增加值 * @return int 新的计数值 */ public static function increment(int $value = 1): int { self::$count += $value; self::$history[] = [ 'action' => 'increment', 'value' => $value, 'timestamp' => date('Y-m-d H:i:s') ]; return self::$count; } /** * 减少计数 * * @param int $value 减少值 * @return int 新的计数值 */ public static function decrement(int $value = 1): int { self::$count -= $value; self::$history[] = [ 'action' => 'decrement', 'value' => $value, 'timestamp' => date('Y-m-d H:i:s') ]; return self::$count; } /** * 获取当前计数 * * @return int 当前计数值 */ public static function getCount(): int { return self::$count; } /** * 重置计数 * * @return void */ public static function reset(): void { self::$count = 0; self::$history[] = [ 'action' => 'reset', 'value' => 0, 'timestamp' => date('Y-m-d H:i:s') ]; } /** * 获取历史记录 * * @return array 历史记录数组 */ public static function getHistory(): array { return self::$history; }}
/** * 数据库连接类 * 使用静态属性实现单例模式 */class Database { /** * 数据库实例 * @var Database|null */ private static $instance = null; /** * 数据库连接 * @var PDO|null */ private $connection = null; /** * 查询计数 * @var int */ private static $queryCount = 0; /** * 私有构造函数,防止外部实例化 */ private function __construct() { try { $config = ConfigManager::get('database'); $dsn = "mysql:host={$config['host']};dbname={$config['database']}"; $this->connection = new PDO( $dsn, $config['username'], $config['password'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); } catch (PDOException $e) { throw new Exception("数据库连接失败: " . $e->getMessage()); } } /** * 获取数据库实例 * * @return Database 数据库实例 */ public static function getInstance(): Database { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * 执行SQL查询 * * @param string $sql SQL语句 * @param array $params 参数数组 * @return PDOStatement|false 查询结果 */ public function query(string $sql, array $params = []) { try { $stmt = $this->connection->prepare($sql); $stmt->execute($params); self::$queryCount++; return $stmt; } catch (PDOException $e) { throw new Exception("查询执行失败: " . $e->getMessage()); } } /** * 获取查询计数 * * @return int 查询计数 */ public static function getQueryCount(): int { return self::$queryCount; } /** * 重置查询计数 * * @return void */ public static function resetQueryCount(): void { self::$queryCount = 0; } /** * 私有克隆方法,防止克隆实例 */ private function __clone() {} /** * 私有反序列化方法,防止反序列化实例 */ private function __wakeup() {}}
// 使用示例try { // 初始化配置 ConfigManager::initialize('config.json'); // 设置和获取配置 ConfigManager::set('app_name', 'My App'); ConfigManager::set('debug', true); echo "应用名称: " . ConfigManager::get('app_name') . "\n"; echo "调试模式: " . (ConfigManager::get('debug') ? '开启' : '关闭') . "\n"; // 使用计数器 Counter::increment(5); echo "当前计数: " . Counter::getCount() . "\n"; Counter::decrement(2); echo "当前计数: " . Counter::getCount() . "\n"; // 查看计数器历史 print_r(Counter::getHistory()); // 使用数据库单例 $db = Database::getInstance(); $db->query("SELECT * FROM users"); $db->query("SELECT * FROM orders"); echo "执行的查询数量: " . Database::getQueryCount() . "\n"; } catch (Exception $e) { echo "错误: " . $e->getMessage() . "\n";}
复制代码

观察者模式

观察者模式定义了对象之间的一对多依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。


<?php/** * 观察者接口 */interface Observer {    public function update(Subject $subject, $data = null): void;}
/** * 主题接口 */interface Subject { public function attach(Observer $observer): void; public function detach(Observer $observer): void; public function notify($data = null): void;}
/** * 订单类 * 实现主题接口 */class Order implements Subject { private $observers = []; private $id; private $status; private $items = []; public function __construct(string $id) { $this->id = $id; $this->status = 'pending'; } public function attach(Observer $observer): void { $this->observers[] = $observer; } public function detach(Observer $observer): void { $key = array_search($observer, $this->observers, true); if ($key !== false) { unset($this->observers[$key]); } } public function notify($data = null): void { foreach ($this->observers as $observer) { $observer->update($this, $data); } } public function addItem(string $item, float $price, int $quantity = 1): void { $this->items[] = [ 'item' => $item, 'price' => $price, 'quantity' => $quantity ]; $this->notify([ 'action' => 'item_added', 'item' => $item, 'price' => $price, 'quantity' => $quantity ]); } public function updateStatus(string $status): void { $oldStatus = $this->status; $this->status = $status; $this->notify([ 'action' => 'status_changed', 'old_status' => $oldStatus, 'new_status' => $status ]); } public function getId(): string { return $this->id; } public function getStatus(): string { return $this->status; } public function getItems(): array { return $this->items; } public function getTotal(): float { $total = 0; foreach ($this->items as $item) { $total += $item['price'] * $item['quantity']; } return $total; }}
/** * 邮件通知观察者 */class EmailNotifier implements Observer { public function update(Subject $subject, $data = null): void { if (!($subject instanceof Order)) { return; } if ($data['action'] === 'status_changed') { $this->sendStatusChangeEmail($subject, $data); } } private function sendStatusChangeEmail(Order $order, array $data): void { echo "发送邮件通知: 订单 {$order->getId()} 状态从 {$data['old_status']} 变更为 {$data['new_status']}\n"; }}
/** * 库存管理观察者 */class InventoryManager implements Observer { public function update(Subject $subject, $data = null): void { if (!($subject instanceof Order)) { return; } if ($data['action'] === 'item_added') { $this->updateInventory($data); } elseif ($data['action'] === 'status_changed' && $data['new_status'] === 'cancelled') { $this->restoreInventory($subject); } } private function updateInventory(array $data): void { echo "更新库存: 减少 {$data['item']} 库存 {$data['quantity']} 个\n"; } private function restoreInventory(Order $order): void { echo "恢复库存: 订单 {$order->getId()} 已取消,恢复所有商品库存\n"; foreach ($order->getItems() as $item) { echo " - 增加 {$item['item']} 库存 {$item['quantity']} 个\n"; } }}
/** * 日志记录观察者 */class Logger implements Observer { public function update(Subject $subject, $data = null): void { if (!($subject instanceof Order)) { return; } $orderId = $subject->getId(); $action = $data['action'] ?? 'unknown'; echo "记录日志: 订单 {$orderId} 执行了 {$action} 操作\n"; echo " - 详情: " . json_encode($data) . "\n"; }}
// 使用示例$order = new Order('ORD-12345');
// 添加观察者$emailNotifier = new EmailNotifier();$inventoryManager = new InventoryManager();$logger = new Logger();
$order->attach($emailNotifier);$order->attach($inventoryManager);$order->attach($logger);
// 添加商品$order->addItem('笔记本电脑', 5999.00);$order->addItem('鼠标', 99.00, 2);
// 更新状态$order->updateStatus('processing');$order->updateStatus('shipped');
// 移除库存观察者$order->detach($inventoryManager);
// 取消订单$order->updateStatus('cancelled');
复制代码

依赖注入

依赖注入是一种设计模式,它允许一个对象接收它所依赖的其他对象。这种模式有助于解耦代码,使其更易于测试和维护。


<?php/** * 用户仓库接口 */interface UserRepositoryInterface {    public function findById(int $id): ?array;    public function findByEmail(string $email): ?array;    public function create(array $data): int;    public function update(int $id, array $data): bool;    public function delete(int $id): bool;}
/** * MySQL用户仓库实现 */class MySqlUserRepository implements UserRepositoryInterface { private $db; public function __construct(PDO $db) { $this->db = $db; } public function findById(int $id): ?array { $stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]); $user = $stmt->fetch(PDO::FETCH_ASSOC); return $user ?: null; } public function findByEmail(string $email): ?array { $stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$email]); $user = $stmt->fetch(PDO::FETCH_ASSOC); return $user ?: null; } public function create(array $data): int { $stmt = $this->db->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?)"); $stmt->execute([$data['name'], $data['email'], $data['password']]); return (int) $this->db->lastInsertId(); } public function update(int $id, array $data): bool { $fields = []; $values = []; foreach ($data as $key => $value) { $fields[] = "{$key} = ?"; $values[] = $value; } $values[] = $id; $stmt = $this->db->prepare("UPDATE users SET " . implode(', ', $fields) . " WHERE id = ?"); return $stmt->execute($values); } public function delete(int $id): bool { $stmt = $this->db->prepare("DELETE FROM users WHERE id = ?"); return $stmt->execute([$id]); }}
/** * 用户服务类 */class UserService { private $repository; private $logger; public function __construct(UserRepositoryInterface $repository, Logger $logger = null) { $this->repository = $repository; $this->logger = $logger; } public function registerUser(string $name, string $email, string $password): int { // 检查邮箱是否已存在 if ($this->repository->findByEmail($email)) { throw new Exception("邮箱已被注册"); } // 哈希密码 $hashedPassword = password_hash($password, PASSWORD_DEFAULT); // 创建用户 $userId = $this->repository->create([ 'name' => $name, 'email' => $email, 'password' => $hashedPassword ]); // 记录日志 if ($this->logger) { $this->logger->log("用户注册成功: {$email}"); } return $userId; } public function authenticateUser(string $email, string $password): bool { $user = $this->repository->findByEmail($email); if (!$user) { return false; } $authenticated = password_verify($password, $user['password']); if ($authenticated && $this->logger) { $this->logger->log("用户登录成功: {$email}"); } return $authenticated; } public function updateUserProfile(int $id, array $data): bool { // 确保不能更新敏感字段 unset($data['password']); $success = $this->repository->update($id, $data); if ($success && $this->logger) { $this->logger->log("用户资料更新: ID {$id}"); } return $success; }}
/** * 简单日志类 */class Logger { private $logFile; public function __construct(string $logFile = 'application.log') { $this->logFile = $logFile; } public function log(string $message): void { $timestamp = date('Y-m-d H:i:s'); $logEntry = "[{$timestamp}] {$message}" . PHP_EOL; // 在实际应用中,这里会将日志写入文件 echo "写入日志: {$logEntry}"; }}
// 使用示例try { // 创建数据库连接 $db = new PDO('mysql:host=localhost;dbname=test', 'root', ''); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 创建仓库 $userRepository = new MySqlUserRepository($db); // 创建日志记录器 $logger = new Logger(); // 创建用户服务(注入依赖) $userService = new UserService($userRepository, $logger); // 注册用户 $userId = $userService->registerUser('John Doe', 'john@example.com', 'password123'); echo "用户注册成功,ID: {$userId}\n"; // 验证用户 $authenticated = $userService->authenticateUser('john@example.com', 'password123'); echo "用户认证: " . ($authenticated ? '成功' : '失败') . "\n"; // 更新用户资料 $userService->updateUserProfile($userId, [ 'name' => 'John Smith', 'address' => '123 Main St' ]); } catch (Exception $e) { echo "错误: " . $e->getMessage() . "\n";}
复制代码


依赖注入的优点:


  1. 解耦:减少类之间的依赖关系

  2. 可测试性:可以轻松模拟依赖进行单元测试

  3. 灵活性:可以轻松替换实现,如切换数据库或日志系统

  4. 可维护性:代码更清晰,职责更明确

反射和注解

PHP 的反射 API 允许在运行时检查类、接口、函数、方法和扩展。PHP 8 引入了原生注解(Attributes)支持,可以为代码元素添加元数据。

反射 API

<?php/** * 产品类 */class Product {    private $id;    private $name;    private $price;    private $description;        public function __construct(int $id, string $name, float $price, string $description = '') {        $this->id = $id;        $this->name = $name;        $this->price = $price;        $this->description = $description;    }        public function getId(): int {        return $this->id;    }        public function getName(): string {        return $this->name;    }        public function getPrice(): float {        return $this->price;    }        public function getDescription(): string {        return $this->description;    }        public function setPrice(float $price): void {        $this->price = $price;    }        public function setDescription(string $description): void {        $this->description = $description;    }        public function getFormattedPrice(): string {        return number_format($this->price, 2) . ' 元';    }}
/** * 使用反射API检查类 */function inspectClass(string $className): void { echo "检查类: {$className}\n"; // 获取类的反射 $reflectionClass = new ReflectionClass($className); // 获取类信息 echo "类名: " . $reflectionClass->getName() . "\n"; echo "命名空间: " . $reflectionClass->getNamespaceName() . "\n"; echo "是否为抽象类: " . ($reflectionClass->isAbstract() ? '是' : '否') . "\n"; echo "是否为接口: " . ($reflectionClass->isInterface() ? '是' : '否') . "\n"; echo "是否为特性: " . ($reflectionClass->isTrait() ? '是' : '否') . "\n"; // 获取属性 echo "\n属性:\n"; $properties = $reflectionClass->getProperties(); foreach ($properties as $property) { echo " - " . $property->getName(); echo " (" . ($property->isPublic() ? 'public' : ($property->isProtected() ? 'protected' : 'private')) . ")"; echo "\n"; } // 获取方法 echo "\n方法:\n"; $methods = $reflectionClass->getMethods(); foreach ($methods as $method) { echo " - " . $method->getName(); echo " (" . ($method->isPublic() ? 'public' : ($method->isProtected() ? 'protected' : 'private')) . ")"; // 获取方法参数 $parameters = $method->getParameters(); if (count($parameters) > 0) { echo " 参数: "; $paramStrings = []; foreach ($parameters as $param) { $paramStr = ''; if ($param->hasType()) { $paramStr .= $param->getType() . ' '; } $paramStr .= '
这个示例展示了静态属性和方法的三种常见用途:1. 配置管理:使用静态方法管理全局配置2. 计数器:使用静态属性跟踪状态3. 单例模式:确保类只有一个实例
静态成员的主要特点:- 不需要实例化类就可以访问- 在所有类实例间共享- 使用self::关键字访问- 适合管理全局状态和工具方法
### 后期静态绑定
后期静态绑定是PHP 5.3引入的一个特性,它解决了在继承情况下静态方法调用的问题。使用`static::`关键字代替`self::`可以引用运行时调用类的静态成员,而不是定义方法的类。
```php<?php/** * 基础模型类 * 演示后期静态绑定 */class BaseModel { /** * 表名 * @var string */ protected static $table; /** * 获取表名 * * @return string 表名 */ public static function getTable(): string { // 使用self::会引用BaseModel类中的$table // return self::$table; // 使用static::会引用运行时调用的类中的$table return static::$table ?? strtolower(get_called_class()); } /** * 查找所有记录 * * @return array 记录数组 */ public static function findAll(): array { $table = static::getTable(); echo "SELECT * FROM {$table}\n"; // 在实际应用中,这里会执行数据库查询 return []; } /** * 根据ID查找记录 * * @param int $id 记录ID * @return array|null 记录数组或null */ public static function findById(int $id): ?array { $table = static::getTable(); echo "SELECT * FROM {$table} WHERE id = {$id}\n"; // 在实际应用中,这里会执行数据库查询 return []; } /** * 创建新记录 * * @param array $data 记录数据 * @return int 新记录ID */ public static function create(array $data): int { $table = static::getTable(); $columns = implode(', ', array_keys($data)); $values = implode(', ', array_map(function($value) { return is_string($value) ? "'{$value}'" : $value; }, $data)); echo "INSERT INTO {$table} ({$columns}) VALUES ({$values})\n"; // 在实际应用中,这里会执行数据库插入 return 1; }}
/** * 用户模型类 * 继承自BaseModel */class UserModel extends BaseModel { /** * 表名 * @var string */ protected static $table = 'users'; /** * 查找用户名 * * @param string $username 用户名 * @return array|null 用户记录或null */ public static function findByUsername(string $username): ?array { $table = static::getTable(); echo "SELECT * FROM {$table} WHERE username = '{$username}'\n"; // 在实际应用中,这里会执行数据库查询 return []; }}
/** * 产品模型类 * 继承自BaseModel */class ProductModel extends BaseModel { /** * 表名 * @var string */ protected static $table = 'products'; /** * 查找活跃产品 * * @return array 产品记录数组 */ public static function findActive(): array { $table = static::getTable(); echo "SELECT * FROM {$table} WHERE status = 'active'\n"; // 在实际应用中,这里会执行数据库查询 return []; }}
// 使用示例echo "BaseModel表名: " . BaseModel::getTable() . "\n";echo "UserModel表名: " . UserModel::getTable() . "\n";echo "ProductModel表名: " . ProductModel::getTable() . "\n";
UserModel::findAll();UserModel::findById(1);UserModel::findByUsername('john');
ProductModel::findAll();ProductModel::findActive();
// 创建记录UserModel::create([ 'username' => 'newuser', 'email' => 'newuser@example.com', 'password' => 'hashed_password']);
复制代码

魔术方法

PHP 提供了一系列魔术方法,允许在特定事件发生时自动调用。这些方法以双下划线开头。


<?php/** * 产品类 * 演示魔术方法的使用 */class Product {    /**     * 产品属性     * @var array     */    private $data = [];        /**     * 只读属性     * @var array     */    private $readOnlyProps = ['id', 'created_at'];        /**     * 构造函数     *      * @param array $data 初始数据     */    public function __construct(array $data = []) {        $this->data = $data;        if (!isset($this->data['created_at'])) {            $this->data['created_at'] = date('Y-m-d H:i:s');        }        echo "__construct 被调用\n";    }        /**     * 析构函数     * 对象被销毁时调用     */    public function __destruct() {        echo "__destruct 被调用\n";    }        /**     * 获取不可访问属性的值     *      * @param string $name 属性名     * @return mixed 属性值     */    public function __get(string $name) {        echo "__get({$name}) 被调用\n";                if (array_key_exists($name, $this->data)) {            return $this->data[$name];        }                $trace = debug_backtrace();        trigger_error(            "未定义的属性: {$name} in {$trace[0]['file']} on line {$trace[0]['line']}",            E_USER_NOTICE        );        return null;    }        /**     * 设置不可访问属性的值     *      * @param string $name 属性名     * @param mixed $value 属性值     * @return void     */    public function __set(string $name, $value) {        echo "__set({$name}, " . json_encode($value) . ") 被调用\n";                if (in_array($name, $this->readOnlyProps)) {            throw new Exception("属性 {$name} 是只读的");        }                $this->data[$name] = $value;    }        /**     * 检查不可访问属性是否存在     *      * @param string $name 属性名     * @return bool 是否存在     */    public function __isset(string $name): bool {        echo "__isset({$name}) 被调用\n";        return isset($this->data[$name]);    }        /**     * 删除不可访问属性     *      * @param string $name 属性名     * @return void     */    public function __unset(string $name) {        echo "__unset({$name}) 被调用\n";                if (in_array($name, $this->readOnlyProps)) {            throw new Exception("属性 {$name} 是只读的");        }                unset($this->data[$name]);    }        /**     * 调用不可访问方法时被调用     *      * @param string $name 方法名     * @param array $arguments 参数     * @return mixed 返回值     */    public function __call(string $name, array $arguments) {        echo "__call({$name}, " . json_encode($arguments) . ") 被调用\n";                // 处理getter方法        if (strpos($name, 'get') === 0) {            $property = lcfirst(substr($name, 3));            if (array_key_exists($property, $this->data)) {                return $this->data[$property];            }        }                // 处理setter方法        if (strpos($name, 'set') === 0) {            $property = lcfirst(substr($name, 3));            if (!in_array($property, $this->readOnlyProps)) {                $this->data[$property] = $arguments[0];                return true;            }        }                throw new Exception("方法 {$name} 不存在");    }        /**     * 调用不可访问的静态方法时被调用     *      * @param string $name 方法名     * @param array $arguments 参数     * @return mixed 返回值     */    public static function __callStatic(string $name, array $arguments) {        echo "__callStatic({$name}, " . json_encode($arguments) . ") 被调用\n";                // 处理查找方法        if (strpos($name, 'findBy') === 0) {            $property = lcfirst(substr($name, 6));            $value = $arguments[0];            echo "模拟查询: SELECT * FROM products WHERE {$property} = '{$value}'\n";            return new self(['id' => 1, $property => $value]);        }                throw new Exception("静态方法 {$name} 不存在");    }        /**     * 对象被当作字符串使用时被调用     *      * @return string 字符串表示     */    public function __toString(): string {        echo "__toString() 被调用\n";        return json_encode($this->data, JSON_PRETTY_PRINT);    }        /**     * 对象被当作函数调用时被调用     *      * @return mixed 返回值     */    public function __invoke() {        echo "__invoke() 被调用\n";        return $this->data;    }        /**     * 序列化对象时被调用     *      * @return array 需要被序列化的属性     */    public function __sleep(): array {        echo "__sleep() 被调用\n";        return ['data'];    }        /**     * 反序列化对象时被调用     *      * @return void     */    public function __wakeup() {        echo "__wakeup() 被调用\n";        if (!isset($this->data['updated_at'])) {            $this->data['updated_at'] = date('Y-m-d H:i:s');        }    }        /**     * 调试信息     *      * @return array 调试信息     */    public function __debugInfo(): array {        echo "__debugInfo() 被调用\n";        return [            'id' => $this->data['id'] ?? null,            'properties' => count($this->data),            'readOnly' => $this->readOnlyProps        ];    }}
// 使用示例try { // 创建产品 $product = new Product([ 'id' => 1, 'name' => 'Laptop', 'price' => 999.99, 'stock' => 10 ]); // 使用__get echo "产品名称: {$product->name}\n"; echo "产品价格: {$product->price}\n"; // 使用__set $product->description = '高性能笔记本电脑'; $product->stock = 5; // 尝试设置只读属性 try { $product->id = 2; } catch (Exception $e) { echo "错误: " . $e->getMessage() . "\n"; } // 使用__isset和__unset if (isset($product->description)) { echo "描述存在\n"; unset($product->description); } if (!isset($product->description)) { echo "描述不存在\n"; } // 使用__call $product->setColor('Silver'); echo "颜色: " . $product->getColor() . "\n"; // 使用__callStatic $foundProduct = Product::findByName('Keyboard'); // 使用__toString echo "产品信息: {$product}\n"; // 使用__invoke $data = $product(); print_r($data); // 使用__sleep和__wakeup $serialized = serialize($product); echo "序列化: {$serialized}\n"; $unserialized = unserialize($serialized); // 使用__debugInfo var_dump($product); } catch (Exception $e) { echo "错误: " . $e->getMessage() . "\n";}
复制代码


魔术方法提供了强大的功能,可以实现:


  • 属性重载:通过__get__set__isset__unset

  • 方法重载:通过__call__callStatic

  • 对象序列化控制:通过__sleep__wakeup

  • 对象字符串表示:通过__toString

  • 对象调试信息:通过__debugInfo

  • 对象作为函数调用:通过__invoke

特性(Traits)

特性(Traits)是 PHP 5.4 引入的一种代码复用机制,它允许开发者在不同的类中重用方法集,从而解决 PHP 单继承的限制。

基本使用

<?php/** * 日志特性 * 提供日志记录功能 */trait Logger {    /**     * 日志文件路径     * @var string     */    protected $logFile = 'application.log';        /**     * 设置日志文件     *      * @param string $path 日志文件路径     * @return void     */    public function setLogFile(string $path): void {        $this->logFile = $path;    }        /**     * 记录日志     *      * @param string $message 日志消息     * @param string $level 日志级别     * @return bool 是否成功     */    public function log(string $message, string $level = 'INFO'): bool {        $timestamp = date('Y-m-d H:i:s');        $logEntry = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;                // 在实际应用中,这里会将日志写入文件        echo "写入日志: {$logEntry}";        return true;    }        /**     * 记录错误日志     *      * @param string $message 错误消息     * @return bool 是否成功     */    public function logError(string $message): bool {        return $this->log($message, 'ERROR');    }        /**     * 记录警告日志     *      * @param string $message 警告消息     * @return bool 是否成功     */    public function logWarning(string $message): bool {        return $this->log($message, 'WARNING');    }}
/** * 时间戳特性 * 提供时间戳管理功能 */trait Timestampable { /** * 创建时间 * @var string|null */ protected $createdAt = null; /** * 更新时间 * @var string|null */ protected $updatedAt = null; /** * 初始化时间戳 * * @return void */ public function initTimestamps(): void { $this->createdAt = date('Y-m-d H:i:s'); $this->updatedAt = $this->createdAt; } /** * 更新时间戳 * * @return void */ public function updateTimestamp(): void { $this->updatedAt = date('Y-m-d H:i:s'); } /** * 获取创建时间 * * @return string|null 创建时间 */ public function getCreatedAt(): ?string { return $this->createdAt; } /** * 获取更新时间 * * @return string|null 更新时间 */ public function getUpdatedAt(): ?string { return $this->updatedAt; }}
/** * 用户类 * 使用Logger和Timestampable特性 */class User { // 使用特性 use Logger, Timestampable; /** * 用户ID * @var int */ private $id; /** * 用户名 * @var string */ private $username; /** * 电子邮件 * @var string */ private $email; /** * 构造函数 * * @param int $id 用户ID * @param string $username 用户名 * @param string $email 电子邮件 */ public function __construct(int $id, string $username, string $email) { $this->id = $id; $this->username = $username; $this->email = $email; // 初始化时间戳 $this->initTimestamps(); // 记录日志 $this->log("用户 {$username} 已创建"); } /** * 更新用户信息 * * @param array $data 更新数据 * @return bool 是否成功 */ public function update(array $data): bool { foreach ($data as $key => $value) { if (property_exists($this, $key)) { $this->$key = $value; } } // 更新时间戳 $this->updateTimestamp(); // 记录日志 $this->log("用户 {$this->username} 已更新"); return true; } /** * 获取用户信息 * * @return array 用户信息 */ public function getInfo(): array { return [ 'id' => $this->id, 'username' => $this->username, 'email' => $this->email, 'created_at' => $this->createdAt, 'updated_at' => $this->updatedAt ]; }}
// 使用示例$user = new User(1, 'johndoe', 'john@example.com');print_r($user->getInfo());
// 更新用户$user->update(['email' => 'newemail@example.com']);print_r($user->getInfo());
// 记录警告$user->logWarning("用户尝试访问受限资源");
复制代码

解决多继承问题

特性可以帮助我们解决 PHP 中的多继承问题,通过组合多个特性来为类添加功能。


<?php/** * 序列化特性 * 提供对象序列化功能 */trait Serializer {    /**     * 序列化对象     *      * @return string 序列化后的字符串     */    public function serialize(): string {        $data = get_object_vars($this);        return json_encode($data);    }        /**     * 反序列化对象     *      * @param string $data 序列化的数据     * @return void     */    public function unserialize(string $data): void {        $properties = json_decode($data, true);        foreach ($properties as $key => $value) {            if (property_exists($this, $key)) {                $this->$key = $value;            }        }    }}
/** * 验证特性 * 提供数据验证功能 */trait Validator { /** * 验证错误 * @var array */ protected $errors = []; /** * 验证电子邮件 * * @param string $email 电子邮件 * @return bool 是否有效 */ public function validateEmail(string $email): bool { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $this->errors['email'] = "无效的电子邮件地址"; return false; } return true; } /** * 验证字符串长度 * * @param string $value 字符串值 * @param int $min 最小长度 * @param int $max 最大长度 * @param string $field 字段名 * @return bool 是否有效 */ public function validateLength(string $value, int $min, int $max, string $field): bool { $length = strlen($value); if ($length < $min || $length > $max) { $this->errors[$field] = "{$field}长度必须在{$min}到{$max}之间"; return false; } return true; } /** * 获取验证错误 * * @return array 错误数组 */ public function getErrors(): array { return $this->errors; } /** * 检查是否有错误 * * @return bool 是否有错误 */ public function hasErrors(): bool { return !empty($this->errors); }}
/** * 用户模型类 * 使用多个特性 */class UserModel { // 使用多个特性 use Logger, Timestampable, Serializer, Validator; /** * 用户ID * @var int */ private $id; /** * 用户名 * @var string */ private $username; /** * 电子邮件 * @var string */ private $email; /** * 构造函数 * * @param array $data 用户数据 */ public function __construct(array $data = []) { if (!empty($data)) { $this->fill($data); } $this->initTimestamps(); $this->setLogFile('users.log'); } /** * 填充用户数据 * * @param array $data 用户数据 * @return bool 是否成功 */ public function fill(array $data): bool { // 验证数据 if (isset($data['email'])) { $this->validateEmail($data['email']); } if (isset($data['username'])) { $this->validateLength($data['username'], 3, 20, 'username'); } // 如果有验证错误,返回false if ($this->hasErrors()) { $this->logError("用户数据验证失败: " . json_encode($this->getErrors())); return false; } // 填充数据 $this->id = $data['id'] ?? null; $this->username = $data['username'] ?? ''; $this->email = $data['email'] ?? ''; $this->log("用户数据已填充"); return true; } /** * 保存用户 * * @return bool 是否成功 */ public function save(): bool { // 在实际应用中,这里会将用户保存到数据库 $this->updateTimestamp(); $this->log("用户 {$this->username} 已保存"); return true; } /** * 获取用户信息 * * @return array 用户信息 */ public function toArray(): array { return [ 'id' => $this->id, 'username' => $this->username, 'email' => $this->email, 'created_at' => $this->createdAt, 'updated_at' => $this->updatedAt ]; }}
// 使用示例$userData = [ 'id' => 1, 'username' => 'johndoe', 'email' => 'john@example.com'];
$user = new UserModel($userData);print_r($user->toArray());
// 序列化用户$serialized = $user->serialize();echo "序列化的用户数据: {$serialized}\n";
// 创建新用户并反序列化$newUser = new UserModel();$newUser->unserialize($serialized);print_r($newUser->toArray());
// 使用验证$invalidUser = new UserModel([ 'username' => 'jo', // 太短 'email' => 'invalid-email' // 无效的邮箱]);
if ($invalidUser->hasErrors()) { echo "验证错误:\n"; print_r($invalidUser->getErrors());}
复制代码

特性中的冲突解决

当多个特性包含同名方法时,可能会发生冲突。PHP 提供了几种解决冲突的方法。


<?php/** * 特性A */trait TraitA {    public function hello() {        return "Hello from TraitA";    }        public function greet() {        return "Greet from TraitA";    }}
/** * 特性B */trait TraitB { public function hello() { return "Hello from TraitB"; } public function greet() { return "Greet from TraitB"; }}
/** * 特性C */trait TraitC { public function hello() { return "Hello from TraitC"; } // 使用其他特性 use TraitA, TraitB { // 解决冲突:使用TraitA的greet方法 TraitA::greet insteadof TraitB; // 解决冲突:使用TraitB的hello方法 TraitB::hello insteadof TraitA; // 为TraitA的hello方法创建别名 TraitA::hello as helloA; }}
/** * 示例类 * 演示特性冲突解决 */class ConflictExample { // 使用特性C use TraitC; // 使用特性A和特性B,并解决冲突 use TraitA, TraitB { // 解决冲突:使用TraitB的hello方法 TraitB::hello insteadof TraitA; // 为TraitA的hello方法创建别名 TraitA::hello as helloFromA; // 修改方法可见性 TraitB::greet as protected greetFromB; } /** * 测试方法 * * @return array 测试结果 */ public function test(): array { return [ 'hello' => $this->hello(), 'helloFromA' => $this->helloFromA(), 'helloA' => $this->helloA() ]; } /** * 访问受保护的方法 * * @return string 结果 */ public function accessProtected(): string { return $this->greetFromB(); }}
// 使用示例$example = new ConflictExample();print_r($example->test());echo "Protected method: " . $example->accessProtected() . "\n";
复制代码


特性的主要优点:


  1. 代码复用:可以在多个类中重用方法,而不需要继承

  2. 解决多继承问题:可以组合多个特性,实现类似多继承的功能

  3. 灵活性:可以在运行时解决方法冲突

  4. 组合优于继承:符合现代编程的"组合优于继承"原则


特性的使用场景:


  1. 横切关注点:如日志记录、时间戳管理、序列化等

  2. 多个类需要相同功能,但不适合放在基类中

  3. 需要在多个不相关的类中共享代码

  4. 需要组合多个功能,但单继承限制了类的设计

命名空间和自动加载

命名空间是 PHP 5.3 引入的一种组织代码的方式,它可以避免命名冲突,并使代码结构更加清晰。自动加载则是一种自动包含类文件的机制,避免了手动使用 require 或 include。

命名空间基础

<?php// 文件: App/Models/User.php
// 声明命名空间namespace App\Models;
/** * 用户模型类 */class User { /** * 用户ID * @var int */ private $id; /** * 用户名 * @var string */ private $username; /** * 构造函数 * * @param int $id 用户ID * @param string $username 用户名 */ public function __construct(int $id, string $username) { $this->id = $id; $this->username = $username; } /** * 获取用户信息 * * @return array 用户信息 */ public function getInfo(): array { return [ 'id' => $this->id, 'username' => $this->username ]; }}
// 文件: App/Services/UserService.php
namespace App\Services;
// 导入User类use App\Models\User;use App\Repositories\UserRepository;use App\Exceptions\UserNotFoundException;
/** * 用户服务类 */class UserService { /** * 用户仓库 * @var UserRepository */ private $repository; /** * 构造函数 * * @param UserRepository $repository 用户仓库 */ public function __construct(UserRepository $repository) { $this->repository = $repository; } /** * 获取用户 * * @param int $id 用户ID * @return User 用户对象 * @throws UserNotFoundException 用户不存在时抛出异常 */ public function getUser(int $id): User { $user = $this->repository->find($id); if (!$user) { throw new UserNotFoundException("用户ID {$id} 不存在"); } return $user; } /** * 创建用户 * * @param string $username 用户名 * @return User 新用户对象 */ public function createUser(string $username): User { $id = $this->repository->create(['username' => $username]); return new User($id, $username); }}
// 文件: App/Repositories/UserRepository.php
namespace App\Repositories;
use App\Models\User;
/** * 用户仓库类 */class UserRepository { /** * 查找用户 * * @param int $id 用户ID * @return User|null 用户对象或null */ public function find(int $id): ?User { // 模拟数据库查询 if ($id > 0 && $id < 100) { return new User($id, "user{$id}"); } return null; } /** * 创建用户 * * @param array $data 用户数据 * @return int 新用户ID */ public function create(array $data): int { // 模拟数据库插入 $id = rand(1, 1000); return $id; }}
// 文件: App/Exceptions/UserNotFoundException.php
namespace App\Exceptions;
use Exception;
/** * 用户未找到异常 */class UserNotFoundException extends Exception { // 继承Exception的所有功能}
// 文件: index.php
// 导入类use App\Services\UserService;use App\Repositories\UserRepository;use App\Exceptions\UserNotFoundException;
// 创建服务$repository = new UserRepository();$userService = new UserService($repository);
try { // 获取用户 $user = $userService->getUser(5); print_r($user->getInfo()); // 创建用户 $newUser = $userService->createUser('newuser'); print_r($newUser->getInfo()); // 尝试获取不存在的用户 $userService->getUser(101);} catch (UserNotFoundException $e) { echo "错误: " . $e->getMessage() . "\n";} catch (Exception $e) { echo "未知错误: " . $e->getMessage() . "\n";}
复制代码


命名空间的主要优点:


  1. 避免命名冲突:可以使用相同的类名,只要它们在不同的命名空间中

  2. 更好的组织代码:可以按功能或模块组织代码

  3. 更短的类名:可以使用更短的类名,而不必担心全局命名冲突

  4. 更好的可读性:代码结构更清晰,更易于理解

PSR-4 标准

PSR-4 是 PHP 标准推荐的自动加载规范,它定义了如何将命名空间映射到文件路径。


<?php/** * PSR-4自动加载器实现 */class Autoloader {    /**     * 命名空间前缀到目录的映射     * @var array     */    private $prefixes = [];        /**     * 注册自动加载器     *      * @return void     */    public function register(): void {        spl_autoload_register([$this, 'loadClass']);    }        /**     * 添加命名空间前缀     *      * @param string $prefix 命名空间前缀     * @param string $baseDir 基础目录     * @param bool $prepend 是否前置     * @return void     */    public function addNamespace(string $prefix, string $baseDir, bool $prepend = false): void {        // 规范化命名空间前缀        $prefix = trim($prefix, '\\') . '\\';                // 规范化基础目录        $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';                // 初始化命名空间前缀数组        if (isset($this->prefixes[$prefix]) === false) {            $this->prefixes[$prefix] = [];        }                // 保留基础目录        if ($prepend) {            array_unshift($this->prefixes[$prefix], $baseDir);        } else {            array_push($this->prefixes[$prefix], $baseDir);        }    }        /**     * 加载类文件     *      * @param string $class 完全限定类名     * @return bool 是否成功加载     */    public function loadClass(string $class): bool {        // 当前命名空间前缀        $prefix = $class;                // 遍历命名空间查找文件        while (false !== $pos = strrpos($prefix, '\\')) {            // 保留命名空间前缀和相对类名            $prefix = substr($class, 0, $pos + 1);            $relativeClass = substr($class, $pos + 1);                        // 尝试加载映射的文件            $mapped = $this->loadMappedFile($prefix, $relativeClass);            if ($mapped) {                return true;            }                        // 移除尾部命名空间分隔符            $prefix = rtrim($prefix, '\\');        }                return false;    }        /**     * 加载映射的文件     *      * @param string $prefix 命名空间前缀     * @param string $relativeClass 相对类名     * @return bool 是否成功加载     */    protected function loadMappedFile(string $prefix, string $relativeClass): bool {        // 检查命名空间前缀是否存在        if (isset($this->prefixes[$prefix]) === false) {            return false;        }                // 遍历基础目录        foreach ($this->prefixes[$prefix] as $baseDir) {            // 构建文件路径            $file = $baseDir                  . str_replace('\\', '/', $relativeClass)                  . '.php';                        // 如果文件存在,加载它            if ($this->requireFile($file)) {                return true;            }        }                return false;    }        /**     * 加载文件     *      * @param string $file 文件路径     * @return bool 文件是否存在     */    protected function requireFile(string $file): bool {        if (file_exists($file)) {            require $file;            return true;        }        return false;    }}
// 使用示例$autoloader = new Autoloader();$autoloader->register();
// 添加命名空间映射$autoloader->addNamespace('App\\Models', __DIR__ . '/App/Models');$autoloader->addNamespace('App\\Services', __DIR__ . '/App/Services');$autoloader->addNamespace('App\\Repositories', __DIR__ . '/App/Repositories');$autoloader->addNamespace('App\\Exceptions', __DIR__ . '/App/Exceptions');
// 现在可以直接使用类,无需手动require$user = new App\Models\User(1, 'johndoe');print_r($user->getInfo());
复制代码

Composer 自动加载

Composer 是 PHP 的依赖管理工具,它提供了强大的自动加载功能,基于 PSR-4 标准。


// composer.json{    "name": "myapp/example",    "description": "Example application",    "type": "project",    "require": {        "php": ">=7.4"    },    "autoload": {        "psr-4": {            "App\\": "src/"        }    }}
复制代码


使用 Composer 自动加载:


<?php// 引入Composer自动加载器require 'vendor/autoload.php';
// 现在可以直接使用命名空间中的类use App\Models\User;use App\Services\UserService;
$user = new User(1, 'johndoe');
复制代码


命名空间和自动加载的最佳实践:


  1. 遵循 PSR-4 标准:命名空间应与目录结构一致

  2. 每个类一个文件:每个 PHP 文件应只包含一个类

  3. 文件名应与类名匹配:例如 User 类应在 User.php 文件中

  4. 使用 Composer 管理自动加载:避免手动实现自动加载器

  5. 合理组织命名空间:按功能或模块组织代码

设计模式

设计模式是软件开发中常见问题的解决方案,它们是经过验证的、可重用的代码设计。PHP 作为一种面向对象的语言,可以实现多种设计模式。

单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点。


<?php/** * 数据库连接类 * 使用单例模式 */class Database {    /**     * 单例实例     * @var Database|null     */    private static $instance = null;        /**     * 数据库连接     * @var PDO|null     */    private $connection = null;        /**     * 配置     * @var array     */    private $config = [        'host' => 'localhost',        'database' => 'test',        'username' => 'root',        'password' => '',        'charset' => 'utf8mb4'    ];        /**     * 私有构造函数,防止外部实例化     */    private function __construct() {        try {            $dsn = "mysql:host={$this->config['host']};dbname={$this->config['database']};charset={$this->config['charset']}";            $this->connection = new PDO(                $dsn,                $this->config['username'],                $this->config['password'],                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]            );            echo "数据库连接成功\n";        } catch (PDOException $e) {            echo "数据库连接失败: " . $e->getMessage() . "\n";        }    }        /**     * 获取单例实例     *      * @return Database 数据库实例     */    public static function getInstance(): Database {        if (self::$instance === null) {            self::$instance = new self();        }        return self::$instance;    }        /**     * 执行查询     *      * @param string $sql SQL语句     * @param array $params 参数数组     * @return PDOStatement|false 查询结果     */    public function query(string $sql, array $params = []) {        try {            $stmt = $this->connection->prepare($sql);            $stmt->execute($params);            return $stmt;        } catch (PDOException $e) {            echo "查询执行失败: " . $e->getMessage() . "\n";            return false;        }    }        /**     * 私有克隆方法,防止克隆实例     */    private function __clone() {}        /**     * 私有反序列化方法,防止反序列化实例     */    private function __wakeup() {}}
// 使用示例$db1 = Database::getInstance();$db2 = Database::getInstance();
// $db1和$db2是同一个实例var_dump($db1 === $db2); // 输出: bool(true)
// 执行查询$db1->query("SELECT * FROM users WHERE id = ?", [1]);
复制代码

工厂模式

工厂模式提供了一种创建对象的接口,允许子类决定实例化的对象类型。


<?php/** * 支付接口 * 定义所有支付方式必须实现的方法 */interface PaymentGateway {    /**     * 处理支付     *      * @param float $amount 金额     * @param array $options 选项     * @return bool 是否成功     */    public function processPayment(float $amount, array $options = []): bool;        /**     * 验证支付     *      * @param array $data 验证数据     * @return bool 是否有效     */    public function validatePayment(array $data): bool;}
/** * PayPal支付网关 */class PayPalGateway implements PaymentGateway { private $credentials; public function __construct(array $credentials) { $this->credentials = $credentials; } public function processPayment(float $amount, array $options = []): bool { echo "使用PayPal处理 {$amount} 元的支付\n"; return true; } public function validatePayment(array $data): bool { return isset($data['transaction_id']); }}
/** * 支付宝网关 */class AlipayGateway implements PaymentGateway { private $credentials; public function __construct(array $credentials) { $this->credentials = $credentials; } public function processPayment(float $amount, array $options = []): bool { echo "使用支付宝处理 {$amount} 元的支付\n"; return true; } public function validatePayment(array $data): bool { return isset($data['trade_no']); }}
/** * 支付网关工厂 */class PaymentGatewayFactory { private $config; public function __construct(array $config) { $this->config = $config; } public function createGateway(string $type): PaymentGateway { switch ($type) { case 'paypal': return new PayPalGateway($this->config['paypal'] ?? []); case 'alipay': return new AlipayGateway($this->config['alipay'] ?? []); default: throw new Exception("不支持的支付网关类型: {$type}"); } }}
// 使用示例$config = [ 'paypal' => ['client_id' => 'xxx', 'secret' => 'yyy'], 'alipay' => ['app_id' => 'xxx', 'private_key' => 'yyy']];
$factory = new PaymentGatewayFactory($config);
try { $paypal = $factory->createGateway('paypal'); $alipay = $factory->createGateway('alipay'); $paypal->processPayment(100.00); $alipay->processPayment(100.00);} catch (Exception $e) { echo "错误: " . $e->getMessage() . "\n";}``` . $param->getName(); if ($param->isDefaultValueAvailable()) { $defaultValue = $param->getDefaultValue(); if (is_string($defaultValue)) { $defaultValue = "'{$defaultValue}'"; } elseif (is_null($defaultValue)) { $defaultValue = 'null'; } elseif (is_bool($defaultValue)) { $defaultValue = $defaultValue ? 'true' : 'false'; } $paramStr .= " = {$defaultValue}"; } $paramStrings[] = $paramStr; } echo implode(', ', $paramStrings); } // 获取返回类型 if ($method->hasReturnType()) { echo " 返回: " . $method->getReturnType(); } echo "\n"; }}
/** * 使用反射API创建对象 */function createObjectDynamically(string $className, array $constructorArgs = []): object { $reflectionClass = new ReflectionClass($className); return $reflectionClass->newInstanceArgs($constructorArgs);}
/** * 使用反射API调用方法 */function callMethodDynamically(object $object, string $methodName, array $args = []): mixed { $reflectionMethod = new ReflectionMethod(get_class($object), $methodName); return $reflectionMethod->invokeArgs($object, $args);}
/** * 使用反射API获取和设置属性 */function getPropertyValue(object $object, string $propertyName): mixed { $reflectionProperty = new ReflectionProperty(get_class($object), $propertyName); $reflectionProperty->setAccessible(true); return $reflectionProperty->getValue($object);}
function setPropertyValue(object $object, string $propertyName, $value): void { $reflectionProperty = new ReflectionProperty(get_class($object), $propertyName); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($object, $value);}
// 使用示例inspectClass(Product::class);
// 动态创建对象$product = createObjectDynamically(Product::class, [1, '笔记本电脑', 5999.99, '高性能笔记本']);echo "\n创建的产品: " . $product->getName() . ", 价格: " . $product->getPrice() . "\n";
// 动态调用方法$formattedPrice = callMethodDynamically($product, 'getFormattedPrice');echo "格式化价格: " . $formattedPrice . "\n";
// 获取私有属性$id = getPropertyValue($product, 'id');echo "产品ID: " . $id . "\n";
// 设置私有属性setPropertyValue($product, 'price', 4999.99);echo "新价格: " . $product->getPrice() . "\n";
复制代码

PHP 8 注解(Attributes)

PHP 8 引入了原生注解支持,可以为代码元素添加元数据。


<?php/** * 路由注解 */#[Attribute]class Route {    private $path;    private $methods;        public function __construct(string $path, array $methods = ['GET']) {        $this->path = $path;        $this->methods = $methods;    }        public function getPath(): string {        return $this->path;    }        public function getMethods(): array {        return $this->methods;    }}
/** * 验证注解 */#[Attribute(Attribute::TARGET_PROPERTY)]class Validate { private $rules; public function __construct(array $rules) { $this->rules = $rules; } public function getRules(): array { return $this->rules; }}
/** * 用户控制器 */class UserController { #[Route('/users', ['GET'])] public function index() { return "用户列表"; } #[Route('/users/{id}', ['GET'])] public function show(int $id) { return "显示用户 {$id}"; } #[Route('/users', ['POST'])] public function store() { return "创建用户"; } #[Route('/users/{id}', ['PUT'])] public function update(int $id) { return "更新用户 {$id}"; } #[Route('/users/{id}', ['DELETE'])] public function destroy(int $id) { return "删除用户 {$id}"; }}
/** * 用户模型 */class User { #[Validate(['required', 'string', 'max:255'])] private $name; #[Validate(['required', 'email', 'unique:users'])] private $email; #[Validate(['required', 'string', 'min:8'])] private $password; public function __construct(string $name, string $email, string $password) { $this->name = $name; $this->email = $email; $this->password = $password; } public function getName(): string { return $this->name; } public function getEmail(): string { return $this->email; }}
/** * 路由注册器 */class Router { private $routes = []; public function registerController(string $controllerClass): void { $reflectionClass = new ReflectionClass($controllerClass); $methods = $reflectionClass->getMethods(); foreach ($methods as $method) { $attributes = $method->getAttributes(Route::class); foreach ($attributes as $attribute) { $route = $attribute->newInstance(); $this->routes[] = [ 'path' => $route->getPath(), 'methods' => $route->getMethods(), 'controller' => $controllerClass, 'action' => $method->getName() ]; } } } public function getRoutes(): array { return $this->routes; } public function findRoute(string $path, string $method): ?array { foreach ($this->routes as $route) { // 简单的路由匹配,实际应用中会更复杂 $pattern = preg_replace('/{[^}]+}/', '([^/]+)', $route['path']); $pattern = str_replace('/', '\/', $pattern); $pattern = '/^' . $pattern . '$/'; if (preg_match($pattern, $path) && in_array($method, $route['methods'])) { return $route; } } return null; }}
/** * 验证器 */class Validator { public function validate(object $object): array { $errors = []; $reflectionClass = new ReflectionClass($object); $properties = $reflectionClass->getProperties(); foreach ($properties as $property) { $attributes = $property->getAttributes(Validate::class); foreach ($attributes as $attribute) { $validate = $attribute->newInstance(); $rules = $validate->getRules(); $property->setAccessible(true); $value = $property->getValue($object); foreach ($rules as $rule) { if ($rule === 'required' && empty($value)) { $errors[] = "{$property->getName()} 是必填的"; } elseif ($rule === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) { $errors[] = "{$property->getName()} 必须是有效的电子邮件地址"; } elseif (strpos($rule, 'min:') === 0) { $min = (int) substr($rule, 4); if (strlen($value) < $min) { $errors[] = "{$property->getName()} 长度不能小于 {$min}"; } } elseif (strpos($rule, 'max:') === 0) { $max = (int) substr($rule, 4); if (strlen($value) > $max) { $errors[] = "{$property->getName()} 长度不能大于 {$max}"; } } } } } return $errors; }}
// 使用示例// 注册路由$router = new Router();$router->registerController(UserController::class);
// 显示所有路由echo "注册的路由:\n";foreach ($router->getRoutes() as $route) { echo " - " . implode(', ', $route['methods']) . " {$route['path']} => {$route['controller']}::{$route['action']}\n";}
// 查找路由$route = $router->findRoute('/users/123', 'GET');if ($route) { echo "\n找到路由: " . implode(', ', $route['methods']) . " {$route['path']} => {$route['controller']}::{$route['action']}\n";}
// 验证对象$user = new User('John Doe', 'invalid-email', 'pass');$validator = new Validator();$errors = $validator->validate($user);
if (count($errors) > 0) { echo "\n验证错误:\n"; foreach ($errors as $error) { echo " - {$error}\n"; }}
复制代码


反射和注解的应用场景:


  1. 框架开发:如路由系统、依赖注入容器、ORM 等

  2. 代码生成:根据类的结构生成代码

  3. 测试工具:自动发现和运行测试

  4. 文档生成:从代码注释和注解生成文档

  5. 验证系统:基于注解的数据验证


这个示例展示了静态属性和方法的三种常见用途:


  1. 配置管理:使用静态方法管理全局配置

  2. 计数器:使用静态属性跟踪状态

  3. 单例模式:确保类只有一个实例


静态成员的主要特点:


  • 不需要实例化类就可以访问

  • 在所有类实例间共享

  • 使用 self::关键字访问

  • 适合管理全局状态和工具方法

后期静态绑定

后期静态绑定是 PHP 5.3 引入的一个特性,它解决了在继承情况下静态方法调用的问题。使用static::关键字代替self::可以引用运行时调用类的静态成员,而不是定义方法的类。


<?php/** * 基础模型类 * 演示后期静态绑定 */class BaseModel {    /**     * 表名     * @var string     */    protected static $table;        /**     * 获取表名     *      * @return string 表名     */    public static function getTable(): string {        // 使用self::会引用BaseModel类中的$table        // return self::$table;                // 使用static::会引用运行时调用的类中的$table        return static::$table ?? strtolower(get_called_class());    }        /**     * 查找所有记录     *      * @return array 记录数组     */    public static function findAll(): array {        $table = static::getTable();        echo "SELECT * FROM {$table}\n";                // 在实际应用中,这里会执行数据库查询        return [];    }        /**     * 根据ID查找记录     *      * @param int $id 记录ID     * @return array|null 记录数组或null     */    public static function findById(int $id): ?array {        $table = static::getTable();        echo "SELECT * FROM {$table} WHERE id = {$id}\n";                // 在实际应用中,这里会执行数据库查询        return [];    }        /**     * 创建新记录     *      * @param array $data 记录数据     * @return int 新记录ID     */    public static function create(array $data): int {        $table = static::getTable();        $columns = implode(', ', array_keys($data));        $values = implode(', ', array_map(function($value) {            return is_string($value) ? "'{$value}'" : $value;        }, $data));                echo "INSERT INTO {$table} ({$columns}) VALUES ({$values})\n";                // 在实际应用中,这里会执行数据库插入        return 1;    }}
/** * 用户模型类 * 继承自BaseModel */class UserModel extends BaseModel { /** * 表名 * @var string */ protected static $table = 'users'; /** * 查找用户名 * * @param string $username 用户名 * @return array|null 用户记录或null */ public static function findByUsername(string $username): ?array { $table = static::getTable(); echo "SELECT * FROM {$table} WHERE username = '{$username}'\n"; // 在实际应用中,这里会执行数据库查询 return []; }}
/** * 产品模型类 * 继承自BaseModel */class ProductModel extends BaseModel { /** * 表名 * @var string */ protected static $table = 'products'; /** * 查找活跃产品 * * @return array 产品记录数组 */ public static function findActive(): array { $table = static::getTable(); echo "SELECT * FROM {$table} WHERE status = 'active'\n"; // 在实际应用中,这里会执行数据库查询 return []; }}
// 使用示例echo "BaseModel表名: " . BaseModel::getTable() . "\n";echo "UserModel表名: " . UserModel::getTable() . "\n";echo "ProductModel表名: " . ProductModel::getTable() . "\n";
UserModel::findAll();UserModel::findById(1);UserModel::findByUsername('john');
ProductModel::findAll();ProductModel::findActive();
// 创建记录UserModel::create([ 'username' => 'newuser', 'email' => 'newuser@example.com', 'password' => 'hashed_password']);
复制代码

魔术方法

PHP 提供了一系列魔术方法,允许在特定事件发生时自动调用。这些方法以双下划线开头。


<?php/** * 产品类 * 演示魔术方法的使用 */class Product {    /**     * 产品属性     * @var array     */    private $data = [];        /**     * 只读属性     * @var array     */    private $readOnlyProps = ['id', 'created_at'];        /**     * 构造函数     *      * @param array $data 初始数据     */    public function __construct(array $data = []) {        $this->data = $data;        if (!isset($this->data['created_at'])) {            $this->data['created_at'] = date('Y-m-d H:i:s');        }        echo "__construct 被调用\n";    }        /**     * 析构函数     * 对象被销毁时调用     */    public function __destruct() {        echo "__destruct 被调用\n";    }        /**     * 获取不可访问属性的值     *      * @param string $name 属性名     * @return mixed 属性值     */    public function __get(string $name) {        echo "__get({$name}) 被调用\n";                if (array_key_exists($name, $this->data)) {            return $this->data[$name];        }                $trace = debug_backtrace();        trigger_error(            "未定义的属性: {$name} in {$trace[0]['file']} on line {$trace[0]['line']}",            E_USER_NOTICE        );        return null;    }        /**     * 设置不可访问属性的值     *      * @param string $name 属性名     * @param mixed $value 属性值     * @return void     */    public function __set(string $name, $value) {        echo "__set({$name}, " . json_encode($value) . ") 被调用\n";                if (in_array($name, $this->readOnlyProps)) {            throw new Exception("属性 {$name} 是只读的");        }                $this->data[$name] = $value;    }        /**     * 检查不可访问属性是否存在     *      * @param string $name 属性名     * @return bool 是否存在     */    public function __isset(string $name): bool {        echo "__isset({$name}) 被调用\n";        return isset($this->data[$name]);    }        /**     * 删除不可访问属性     *      * @param string $name 属性名     * @return void     */    public function __unset(string $name) {        echo "__unset({$name}) 被调用\n";                if (in_array($name, $this->readOnlyProps)) {            throw new Exception("属性 {$name} 是只读的");        }                unset($this->data[$name]);    }        /**     * 调用不可访问方法时被调用     *      * @param string $name 方法名     * @param array $arguments 参数     * @return mixed 返回值     */    public function __call(string $name, array $arguments) {        echo "__call({$name}, " . json_encode($arguments) . ") 被调用\n";                // 处理getter方法        if (strpos($name, 'get') === 0) {            $property = lcfirst(substr($name, 3));            if (array_key_exists($property, $this->data)) {                return $this->data[$property];            }        }                // 处理setter方法        if (strpos($name, 'set') === 0) {            $property = lcfirst(substr($name, 3));            if (!in_array($property, $this->readOnlyProps)) {                $this->data[$property] = $arguments[0];                return true;            }        }                throw new Exception("方法 {$name} 不存在");    }        /**     * 调用不可访问的静态方法时被调用     *      * @param string $name 方法名     * @param array $arguments 参数     * @return mixed 返回值     */    public static function __callStatic(string $name, array $arguments) {        echo "__callStatic({$name}, " . json_encode($arguments) . ") 被调用\n";                // 处理查找方法        if (strpos($name, 'findBy') === 0) {            $property = lcfirst(substr($name, 6));            $value = $arguments[0];            echo "模拟查询: SELECT * FROM products WHERE {$property} = '{$value}'\n";            return new self(['id' => 1, $property => $value]);        }                throw new Exception("静态方法 {$name} 不存在");    }        /**     * 对象被当作字符串使用时被调用     *      * @return string 字符串表示     */    public function __toString(): string {        echo "__toString() 被调用\n";        return json_encode($this->data, JSON_PRETTY_PRINT);    }        /**     * 对象被当作函数调用时被调用     *      * @return mixed 返回值     */    public function __invoke() {        echo "__invoke() 被调用\n";        return $this->data;    }        /**     * 序列化对象时被调用     *      * @return array 需要被序列化的属性     */    public function __sleep(): array {        echo "__sleep() 被调用\n";        return ['data'];    }        /**     * 反序列化对象时被调用     *      * @return void     */    public function __wakeup() {        echo "__wakeup() 被调用\n";        if (!isset($this->data['updated_at'])) {            $this->data['updated_at'] = date('Y-m-d H:i:s');        }    }        /**     * 调试信息     *      * @return array 调试信息     */    public function __debugInfo(): array {        echo "__debugInfo() 被调用\n";        return [            'id' => $this->data['id'] ?? null,            'properties' => count($this->data),            'readOnly' => $this->readOnlyProps        ];    }}
// 使用示例try { // 创建产品 $product = new Product([ 'id' => 1, 'name' => 'Laptop', 'price' => 999.99, 'stock' => 10 ]); // 使用__get echo "产品名称: {$product->name}\n"; echo "产品价格: {$product->price}\n"; // 使用__set $product->description = '高性能笔记本电脑'; $product->stock = 5; // 尝试设置只读属性 try { $product->id = 2; } catch (Exception $e) { echo "错误: " . $e->getMessage() . "\n"; } // 使用__isset和__unset if (isset($product->description)) { echo "描述存在\n"; unset($product->description); } if (!isset($product->description)) { echo "描述不存在\n"; } // 使用__call $product->setColor('Silver'); echo "颜色: " . $product->getColor() . "\n"; // 使用__callStatic $foundProduct = Product::findByName('Keyboard'); // 使用__toString echo "产品信息: {$product}\n"; // 使用__invoke $data = $product(); print_r($data); // 使用__sleep和__wakeup $serialized = serialize($product); echo "序列化: {$serialized}\n"; $unserialized = unserialize($serialized); // 使用__debugInfo var_dump($product); } catch (Exception $e) { echo "错误: " . $e->getMessage() . "\n";}
复制代码


魔术方法提供了强大的功能,可以实现:


  • 属性重载:通过__get__set__isset__unset

  • 方法重载:通过__call__callStatic

  • 对象序列化控制:通过__sleep__wakeup

  • 对象字符串表示:通过__toString

  • 对象调试信息:通过__debugInfo

  • 对象作为函数调用:通过__invoke

特性(Traits)

特性(Traits)是 PHP 5.4 引入的一种代码复用机制,它允许开发者在不同的类中重用方法集,从而解决 PHP 单继承的限制。

基本使用

<?php/** * 日志特性 * 提供日志记录功能 */trait Logger {    /**     * 日志文件路径     * @var string     */    protected $logFile = 'application.log';        /**     * 设置日志文件     *      * @param string $path 日志文件路径     * @return void     */    public function setLogFile(string $path): void {        $this->logFile = $path;    }        /**     * 记录日志     *      * @param string $message 日志消息     * @param string $level 日志级别     * @return bool 是否成功     */    public function log(string $message, string $level = 'INFO'): bool {        $timestamp = date('Y-m-d H:i:s');        $logEntry = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;                // 在实际应用中,这里会将日志写入文件        echo "写入日志: {$logEntry}";        return true;    }        /**     * 记录错误日志     *      * @param string $message 错误消息     * @return bool 是否成功     */    public function logError(string $message): bool {        return $this->log($message, 'ERROR');    }        /**     * 记录警告日志     *      * @param string $message 警告消息     * @return bool 是否成功     */    public function logWarning(string $message): bool {        return $this->log($message, 'WARNING');    }}
/** * 时间戳特性 * 提供时间戳管理功能 */trait Timestampable { /** * 创建时间 * @var string|null */ protected $createdAt = null; /** * 更新时间 * @var string|null */ protected $updatedAt = null; /** * 初始化时间戳 * * @return void */ public function initTimestamps(): void { $this->createdAt = date('Y-m-d H:i:s'); $this->updatedAt = $this->createdAt; } /** * 更新时间戳 * * @return void */ public function updateTimestamp(): void { $this->updatedAt = date('Y-m-d H:i:s'); } /** * 获取创建时间 * * @return string|null 创建时间 */ public function getCreatedAt(): ?string { return $this->createdAt; } /** * 获取更新时间 * * @return string|null 更新时间 */ public function getUpdatedAt(): ?string { return $this->updatedAt; }}
/** * 用户类 * 使用Logger和Timestampable特性 */class User { // 使用特性 use Logger, Timestampable; /** * 用户ID * @var int */ private $id; /** * 用户名 * @var string */ private $username; /** * 电子邮件 * @var string */ private $email; /** * 构造函数 * * @param int $id 用户ID * @param string $username 用户名 * @param string $email 电子邮件 */ public function __construct(int $id, string $username, string $email) { $this->id = $id; $this->username = $username; $this->email = $email; // 初始化时间戳 $this->initTimestamps(); // 记录日志 $this->log("用户 {$username} 已创建"); } /** * 更新用户信息 * * @param array $data 更新数据 * @return bool 是否成功 */ public function update(array $data): bool { foreach ($data as $key => $value) { if (property_exists($this, $key)) { $this->$key = $value; } } // 更新时间戳 $this->updateTimestamp(); // 记录日志 $this->log("用户 {$this->username} 已更新"); return true; } /** * 获取用户信息 * * @return array 用户信息 */ public function getInfo(): array { return [ 'id' => $this->id, 'username' => $this->username, 'email' => $this->email, 'created_at' => $this->createdAt, 'updated_at' => $this->updatedAt ]; }}
// 使用示例$user = new User(1, 'johndoe', 'john@example.com');print_r($user->getInfo());
// 更新用户$user->update(['email' => 'newemail@example.com']);print_r($user->getInfo());
// 记录警告$user->logWarning("用户尝试访问受限资源");
复制代码

解决多继承问题

特性可以帮助我们解决 PHP 中的多继承问题,通过组合多个特性来为类添加功能。


<?php/** * 序列化特性 * 提供对象序列化功能 */trait Serializer {    /**     * 序列化对象     *      * @return string 序列化后的字符串     */    public function serialize(): string {        $data = get_object_vars($this);        return json_encode($data);    }        /**     * 反序列化对象     *      * @param string $data 序列化的数据     * @return void     */    public function unserialize(string $data): void {        $properties = json_decode($data, true);        foreach ($properties as $key => $value) {            if (property_exists($this, $key)) {                $this->$key = $value;            }        }    }}
/** * 验证特性 * 提供数据验证功能 */trait Validator { /** * 验证错误 * @var array */ protected $errors = []; /** * 验证电子邮件 * * @param string $email 电子邮件 * @return bool 是否有效 */ public function validateEmail(string $email): bool { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $this->errors['email'] = "无效的电子邮件地址"; return false; } return true; } /** * 验证字符串长度 * * @param string $value 字符串值 * @param int $min 最小长度 * @param int $max 最大长度 * @param string $field 字段名 * @return bool 是否有效 */ public function validateLength(string $value, int $min, int $max, string $field): bool { $length = strlen($value); if ($length < $min || $length > $max) { $this->errors[$field] = "{$field}长度必须在{$min}到{$max}之间"; return false; } return true; } /** * 获取验证错误 * * @return array 错误数组 */ public function getErrors(): array { return $this->errors; } /** * 检查是否有错误 * * @return bool 是否有错误 */ public function hasErrors(): bool { return !empty($this->errors); }}
/** * 用户模型类 * 使用多个特性 */class UserModel { // 使用多个特性 use Logger, Timestampable, Serializer, Validator; /** * 用户ID * @var int */ private $id; /** * 用户名 * @var string */ private $username; /** * 电子邮件 * @var string */ private $email; /** * 构造函数 * * @param array $data 用户数据 */ public function __construct(array $data = []) { if (!empty($data)) { $this->fill($data); } $this->initTimestamps(); $this->setLogFile('users.log'); } /** * 填充用户数据 * * @param array $data 用户数据 * @return bool 是否成功 */ public function fill(array $data): bool { // 验证数据 if (isset($data['email'])) { $this->validateEmail($data['email']); } if (isset($data['username'])) { $this->validateLength($data['username'], 3, 20, 'username'); } // 如果有验证错误,返回false if ($this->hasErrors()) { $this->logError("用户数据验证失败: " . json_encode($this->getErrors())); return false; } // 填充数据 $this->id = $data['id'] ?? null; $this->username = $data['username'] ?? ''; $this->email = $data['email'] ?? ''; $this->log("用户数据已填充"); return true; } /** * 保存用户 * * @return bool 是否成功 */ public function save(): bool { // 在实际应用中,这里会将用户保存到数据库 $this->updateTimestamp(); $this->log("用户 {$this->username} 已保存"); return true; } /** * 获取用户信息 * * @return array 用户信息 */ public function toArray(): array { return [ 'id' => $this->id, 'username' => $this->username, 'email' => $this->email, 'created_at' => $this->createdAt, 'updated_at' => $this->updatedAt ]; }}
// 使用示例$userData = [ 'id' => 1, 'username' => 'johndoe', 'email' => 'john@example.com'];
$user = new UserModel($userData);print_r($user->toArray());
// 序列化用户$serialized = $user->serialize();echo "序列化的用户数据: {$serialized}\n";
// 创建新用户并反序列化$newUser = new UserModel();$newUser->unserialize($serialized);print_r($newUser->toArray());
// 使用验证$invalidUser = new UserModel([ 'username' => 'jo', // 太短 'email' => 'invalid-email' // 无效的邮箱]);
if ($invalidUser->hasErrors()) { echo "验证错误:\n"; print_r($invalidUser->getErrors());}
复制代码

特性中的冲突解决

当多个特性包含同名方法时,可能会发生冲突。PHP 提供了几种解决冲突的方法。


<?php/** * 特性A */trait TraitA {    public function hello() {        return "Hello from TraitA";    }        public function greet() {        return "Greet from TraitA";    }}
/** * 特性B */trait TraitB { public function hello() { return "Hello from TraitB"; } public function greet() { return "Greet from TraitB"; }}
/** * 特性C */trait TraitC { public function hello() { return "Hello from TraitC"; } // 使用其他特性 use TraitA, TraitB { // 解决冲突:使用TraitA的greet方法 TraitA::greet insteadof TraitB; // 解决冲突:使用TraitB的hello方法 TraitB::hello insteadof TraitA; // 为TraitA的hello方法创建别名 TraitA::hello as helloA; }}
/** * 示例类 * 演示特性冲突解决 */class ConflictExample { // 使用特性C use TraitC; // 使用特性A和特性B,并解决冲突 use TraitA, TraitB { // 解决冲突:使用TraitB的hello方法 TraitB::hello insteadof TraitA; // 为TraitA的hello方法创建别名 TraitA::hello as helloFromA; // 修改方法可见性 TraitB::greet as protected greetFromB; } /** * 测试方法 * * @return array 测试结果 */ public function test(): array { return [ 'hello' => $this->hello(), 'helloFromA' => $this->helloFromA(), 'helloA' => $this->helloA() ]; } /** * 访问受保护的方法 * * @return string 结果 */ public function accessProtected(): string { return $this->greetFromB(); }}
// 使用示例$example = new ConflictExample();print_r($example->test());echo "Protected method: " . $example->accessProtected() . "\n";
复制代码


特性的主要优点:


  1. 代码复用:可以在多个类中重用方法,而不需要继承

  2. 解决多继承问题:可以组合多个特性,实现类似多继承的功能

  3. 灵活性:可以在运行时解决方法冲突

  4. 组合优于继承:符合现代编程的"组合优于继承"原则


特性的使用场景:


  1. 横切关注点:如日志记录、时间戳管理、序列化等

  2. 多个类需要相同功能,但不适合放在基类中

  3. 需要在多个不相关的类中共享代码

  4. 需要组合多个功能,但单继承限制了类的设计

命名空间和自动加载

命名空间是 PHP 5.3 引入的一种组织代码的方式,它可以避免命名冲突,并使代码结构更加清晰。自动加载则是一种自动包含类文件的机制,避免了手动使用 require 或 include。

命名空间基础

<?php// 文件: App/Models/User.php
// 声明命名空间namespace App\Models;
/** * 用户模型类 */class User { /** * 用户ID * @var int */ private $id; /** * 用户名 * @var string */ private $username; /** * 构造函数 * * @param int $id 用户ID * @param string $username 用户名 */ public function __construct(int $id, string $username) { $this->id = $id; $this->username = $username; } /** * 获取用户信息 * * @return array 用户信息 */ public function getInfo(): array { return [ 'id' => $this->id, 'username' => $this->username ]; }}
// 文件: App/Services/UserService.php
namespace App\Services;
// 导入User类use App\Models\User;use App\Repositories\UserRepository;use App\Exceptions\UserNotFoundException;
/** * 用户服务类 */class UserService { /** * 用户仓库 * @var UserRepository */ private $repository; /** * 构造函数 * * @param UserRepository $repository 用户仓库 */ public function __construct(UserRepository $repository) { $this->repository = $repository; } /** * 获取用户 * * @param int $id 用户ID * @return User 用户对象 * @throws UserNotFoundException 用户不存在时抛出异常 */ public function getUser(int $id): User { $user = $this->repository->find($id); if (!$user) { throw new UserNotFoundException("用户ID {$id} 不存在"); } return $user; } /** * 创建用户 * * @param string $username 用户名 * @return User 新用户对象 */ public function createUser(string $username): User { $id = $this->repository->create(['username' => $username]); return new User($id, $username); }}
// 文件: App/Repositories/UserRepository.php
namespace App\Repositories;
use App\Models\User;
/** * 用户仓库类 */class UserRepository { /** * 查找用户 * * @param int $id 用户ID * @return User|null 用户对象或null */ public function find(int $id): ?User { // 模拟数据库查询 if ($id > 0 && $id < 100) { return new User($id, "user{$id}"); } return null; } /** * 创建用户 * * @param array $data 用户数据 * @return int 新用户ID */ public function create(array $data): int { // 模拟数据库插入 $id = rand(1, 1000); return $id; }}
// 文件: App/Exceptions/UserNotFoundException.php
namespace App\Exceptions;
use Exception;
/** * 用户未找到异常 */class UserNotFoundException extends Exception { // 继承Exception的所有功能}
// 文件: index.php
// 导入类use App\Services\UserService;use App\Repositories\UserRepository;use App\Exceptions\UserNotFoundException;
// 创建服务$repository = new UserRepository();$userService = new UserService($repository);
try { // 获取用户 $user = $userService->getUser(5); print_r($user->getInfo()); // 创建用户 $newUser = $userService->createUser('newuser'); print_r($newUser->getInfo()); // 尝试获取不存在的用户 $userService->getUser(101);} catch (UserNotFoundException $e) { echo "错误: " . $e->getMessage() . "\n";} catch (Exception $e) { echo "未知错误: " . $e->getMessage() . "\n";}
复制代码


命名空间的主要优点:


  1. 避免命名冲突:可以使用相同的类名,只要它们在不同的命名空间中

  2. 更好的组织代码:可以按功能或模块组织代码

  3. 更短的类名:可以使用更短的类名,而不必担心全局命名冲突

  4. 更好的可读性:代码结构更清晰,更易于理解

PSR-4 标准

PSR-4 是 PHP 标准推荐的自动加载规范,它定义了如何将命名空间映射到文件路径。


<?php/** * PSR-4自动加载器实现 */class Autoloader {    /**     * 命名空间前缀到目录的映射     * @var array     */    private $prefixes = [];        /**     * 注册自动加载器     *      * @return void     */    public function register(): void {        spl_autoload_register([$this, 'loadClass']);    }        /**     * 添加命名空间前缀     *      * @param string $prefix 命名空间前缀     * @param string $baseDir 基础目录     * @param bool $prepend 是否前置     * @return void     */    public function addNamespace(string $prefix, string $baseDir, bool $prepend = false): void {        // 规范化命名空间前缀        $prefix = trim($prefix, '\\') . '\\';                // 规范化基础目录        $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';                // 初始化命名空间前缀数组        if (isset($this->prefixes[$prefix]) === false) {            $this->prefixes[$prefix] = [];        }                // 保留基础目录        if ($prepend) {            array_unshift($this->prefixes[$prefix], $baseDir);        } else {            array_push($this->prefixes[$prefix], $baseDir);        }    }        /**     * 加载类文件     *      * @param string $class 完全限定类名     * @return bool 是否成功加载     */    public function loadClass(string $class): bool {        // 当前命名空间前缀        $prefix = $class;                // 遍历命名空间查找文件        while (false !== $pos = strrpos($prefix, '\\')) {            // 保留命名空间前缀和相对类名            $prefix = substr($class, 0, $pos + 1);            $relativeClass = substr($class, $pos + 1);                        // 尝试加载映射的文件            $mapped = $this->loadMappedFile($prefix, $relativeClass);            if ($mapped) {                return true;            }                        // 移除尾部命名空间分隔符            $prefix = rtrim($prefix, '\\');        }                return false;    }        /**     * 加载映射的文件     *      * @param string $prefix 命名空间前缀     * @param string $relativeClass 相对类名     * @return bool 是否成功加载     */    protected function loadMappedFile(string $prefix, string $relativeClass): bool {        // 检查命名空间前缀是否存在        if (isset($this->prefixes[$prefix]) === false) {            return false;        }                // 遍历基础目录        foreach ($this->prefixes[$prefix] as $baseDir) {            // 构建文件路径            $file = $baseDir                  . str_replace('\\', '/', $relativeClass)                  . '.php';                        // 如果文件存在,加载它            if ($this->requireFile($file)) {                return true;            }        }                return false;    }        /**     * 加载文件     *      * @param string $file 文件路径     * @return bool 文件是否存在     */    protected function requireFile(string $file): bool {        if (file_exists($file)) {            require $file;            return true;        }        return false;    }}
// 使用示例$autoloader = new Autoloader();$autoloader->register();
// 添加命名空间映射$autoloader->addNamespace('App\\Models', __DIR__ . '/App/Models');$autoloader->addNamespace('App\\Services', __DIR__ . '/App/Services');$autoloader->addNamespace('App\\Repositories', __DIR__ . '/App/Repositories');$autoloader->addNamespace('App\\Exceptions', __DIR__ . '/App/Exceptions');
// 现在可以直接使用类,无需手动require$user = new App\Models\User(1, 'johndoe');print_r($user->getInfo());
复制代码

Composer 自动加载

Composer 是 PHP 的依赖管理工具,它提供了强大的自动加载功能,基于 PSR-4 标准。


// composer.json{    "name": "myapp/example",    "description": "Example application",    "type": "project",    "require": {        "php": ">=7.4"    },    "autoload": {        "psr-4": {            "App\\": "src/"        }    }}
复制代码


使用 Composer 自动加载:


<?php// 引入Composer自动加载器require 'vendor/autoload.php';
// 现在可以直接使用命名空间中的类use App\Models\User;use App\Services\UserService;
$user = new User(1, 'johndoe');
复制代码


命名空间和自动加载的最佳实践:


  1. 遵循 PSR-4 标准:命名空间应与目录结构一致

  2. 每个类一个文件:每个 PHP 文件应只包含一个类

  3. 文件名应与类名匹配:例如 User 类应在 User.php 文件中

  4. 使用 Composer 管理自动加载:避免手动实现自动加载器

  5. 合理组织命名空间:按功能或模块组织代码

设计模式

设计模式是软件开发中常见问题的解决方案,它们是经过验证的、可重用的代码设计。PHP 作为一种面向对象的语言,可以实现多种设计模式。

单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点。


<?php/** * 数据库连接类 * 使用单例模式 */class Database {    /**     * 单例实例     * @var Database|null     */    private static $instance = null;        /**     * 数据库连接     * @var PDO|null     */    private $connection = null;        /**     * 配置     * @var array     */    private $config = [        'host' => 'localhost',        'database' => 'test',        'username' => 'root',        'password' => '',        'charset' => 'utf8mb4'    ];        /**     * 私有构造函数,防止外部实例化     */    private function __construct() {        try {            $dsn = "mysql:host={$this->config['host']};dbname={$this->config['database']};charset={$this->config['charset']}";            $this->connection = new PDO(                $dsn,                $this->config['username'],                $this->config['password'],                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]            );            echo "数据库连接成功\n";        } catch (PDOException $e) {            echo "数据库连接失败: " . $e->getMessage() . "\n";        }    }        /**     * 获取单例实例     *      * @return Database 数据库实例     */    public static function getInstance(): Database {        if (self::$instance === null) {            self::$instance = new self();        }        return self::$instance;    }        /**     * 执行查询     *      * @param string $sql SQL语句     * @param array $params 参数数组     * @return PDOStatement|false 查询结果     */    public function query(string $sql, array $params = []) {        try {            $stmt = $this->connection->prepare($sql);            $stmt->execute($params);            return $stmt;        } catch (PDOException $e) {            echo "查询执行失败: " . $e->getMessage() . "\n";            return false;        }    }        /**     * 私有克隆方法,防止克隆实例     */    private function __clone() {}        /**     * 私有反序列化方法,防止反序列化实例     */    private function __wakeup() {}}
// 使用示例$db1 = Database::getInstance();$db2 = Database::getInstance();
// $db1和$db2是同一个实例var_dump($db1 === $db2); // 输出: bool(true)
// 执行查询$db1->query("SELECT * FROM users WHERE id = ?", [1]);
复制代码


发布于: 2025-06-18阅读数: 6
用户头像

白月书生

关注

还未添加个人签名 2022-08-31 加入

还未添加个人简介

评论

发布
暂无评论
PHP面向对象编程高级教程_php_白月书生_InfoQ写作社区