PHP 面向对象编程高级教程
- 2025-06-18 浙江
本文字数:56325 字
阅读完需:约 185 分钟
前言
本教程是 PHP 面向对象编程入门教程的进阶版,适合已经掌握基本面向对象概念的开发者。我们将深入探讨 PHP 中更高级的面向对象编程特性,并通过实际案例展示如何在项目中应用这些概念。
目录
高级 OOP 概念
抽象类和抽象方法
接口和多重实现
静态属性和方法
后期静态绑定
魔术方法
特性(Traits)
基本使用
解决多继承问题
特性中的冲突解决
命名空间和自动加载
命名空间基础
PSR-4 标准
Composer 自动加载
设计模式
单例模式
工厂模式
观察者模式
依赖注入
反射 API 和元编程
反射类和方法
动态创建对象
注解的使用
实际项目案例
简单 MVC 框架实现
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";}
依赖注入的优点:
解耦:减少类之间的依赖关系
可测试性:可以轻松模拟依赖进行单元测试
灵活性:可以轻松替换实现,如切换数据库或日志系统
可维护性:代码更清晰,职责更明确
反射和注解
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";
特性的主要优点:
代码复用:可以在多个类中重用方法,而不需要继承
解决多继承问题:可以组合多个特性,实现类似多继承的功能
灵活性:可以在运行时解决方法冲突
组合优于继承:符合现代编程的"组合优于继承"原则
特性的使用场景:
横切关注点:如日志记录、时间戳管理、序列化等
多个类需要相同功能,但不适合放在基类中
需要在多个不相关的类中共享代码
需要组合多个功能,但单继承限制了类的设计
命名空间和自动加载
命名空间是 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";}
命名空间的主要优点:
避免命名冲突:可以使用相同的类名,只要它们在不同的命名空间中
更好的组织代码:可以按功能或模块组织代码
更短的类名:可以使用更短的类名,而不必担心全局命名冲突
更好的可读性:代码结构更清晰,更易于理解
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');
命名空间和自动加载的最佳实践:
遵循 PSR-4 标准:命名空间应与目录结构一致
每个类一个文件:每个 PHP 文件应只包含一个类
文件名应与类名匹配:例如 User 类应在 User.php 文件中
使用 Composer 管理自动加载:避免手动实现自动加载器
合理组织命名空间:按功能或模块组织代码
设计模式
设计模式是软件开发中常见问题的解决方案,它们是经过验证的、可重用的代码设计。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"; }}
反射和注解的应用场景:
框架开发:如路由系统、依赖注入容器、ORM 等
代码生成:根据类的结构生成代码
测试工具:自动发现和运行测试
文档生成:从代码注释和注解生成文档
验证系统:基于注解的数据验证
这个示例展示了静态属性和方法的三种常见用途:
配置管理:使用静态方法管理全局配置
计数器:使用静态属性跟踪状态
单例模式:确保类只有一个实例
静态成员的主要特点:
不需要实例化类就可以访问
在所有类实例间共享
使用 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";
特性的主要优点:
代码复用:可以在多个类中重用方法,而不需要继承
解决多继承问题:可以组合多个特性,实现类似多继承的功能
灵活性:可以在运行时解决方法冲突
组合优于继承:符合现代编程的"组合优于继承"原则
特性的使用场景:
横切关注点:如日志记录、时间戳管理、序列化等
多个类需要相同功能,但不适合放在基类中
需要在多个不相关的类中共享代码
需要组合多个功能,但单继承限制了类的设计
命名空间和自动加载
命名空间是 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";}
命名空间的主要优点:
避免命名冲突:可以使用相同的类名,只要它们在不同的命名空间中
更好的组织代码:可以按功能或模块组织代码
更短的类名:可以使用更短的类名,而不必担心全局命名冲突
更好的可读性:代码结构更清晰,更易于理解
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');
命名空间和自动加载的最佳实践:
遵循 PSR-4 标准:命名空间应与目录结构一致
每个类一个文件:每个 PHP 文件应只包含一个类
文件名应与类名匹配:例如 User 类应在 User.php 文件中
使用 Composer 管理自动加载:避免手动实现自动加载器
合理组织命名空间:按功能或模块组织代码
设计模式
设计模式是软件开发中常见问题的解决方案,它们是经过验证的、可重用的代码设计。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]);
版权声明: 本文为 InfoQ 作者【白月书生】的原创文章。
原文链接:【http://xie.infoq.cn/article/7c78829d750ac506f47d430b1】。未经作者许可,禁止转载。
白月书生
还未添加个人签名 2022-08-31 加入
还未添加个人简介









评论