1. PHP基础:从零开始的动态网页开发之旅
2003年那个闷热的夏天,当我第一次在本地服务器上成功运行出一个简单的PHP页面时,那种兴奋感至今难忘。页面上只是简单地显示着"Hello, World!"和当前服务器时间,但我知道自己打开了一扇通往动态网页开发的大门。PHP作为一门已经存在28年的服务器端脚本语言(最初由Rasmus Lerdorf于1994年创建),至今仍然驱动着全球78%的网站,包括WordPress、Facebook早期版本等知名项目。它的持久生命力源于其低门槛的学习曲线、强大的社区支持和与HTML的无缝集成特性。
对于刚接触PHP的开发者来说,最令人惊喜的莫过于它的"即时反馈"特性——只需保存.php文件并刷新浏览器,就能立即看到代码变更的效果,这种快速迭代的开发体验在Java或C#等编译型语言中是无法想象的。PHP最初是"Personal Home Page"的缩写,后来演变为"PHP: Hypertext Preprocessor"的递归缩写,这恰好反映了它从个人工具到专业开发语言的进化历程。
在2023年的技术环境下,PHP依然保持着旺盛的生命力。根据W3Techs的最新统计,PHP在服务器端编程语言中的市场份额高达77.4%,远超ASP.NET(7.9%)和Ruby(5.3%)。这种持久流行不仅因为其历史悠久积累的庞大代码库,更因为PHP8系列版本带来的性能飞跃(比PHP7快3倍)和现代语言特性(如JIT编译器、属性注解等)的加入,让这门"老"语言焕发出新的活力。
2. 开发环境搭建:从零配置到第一个PHP页面
2.1 环境方案选型对比
搭建PHP开发环境就像为厨师准备厨房,选择什么样的"炉灶"直接影响后续的"烹饪"体验。主流的PHP环境方案主要有三种:
集成环境包(适合新手快速上手):
- XAMPP(跨平台,包含Apache+MySQL+PHP+Perl)
- WAMP(Windows专用)
- MAMP(Mac专用)
- 宝塔面板(国产,带可视化界面)
原生安装(适合需要精细控制的中高级开发者):
- 单独安装Apache/Nginx
- 编译安装PHP核心
- 配置MySQL/MariaDB
容器化方案(适合现代云原生开发):
- Docker + PHP官方镜像
- 配合docker-compose编排多服务
对于绝对初学者,我强烈推荐从XAMPP开始。它的Windows版本安装就像安装普通软件一样简单——下载约150MB的安装包,双击运行,只需在安装向导中保持默认选项,不到5分钟就能获得一个功能完整的PHP开发环境。安装完成后,启动XAMPP控制面板,点击Apache和MySQL模块的"Start"按钮,当状态指示灯变为绿色时,你的本地服务器就已经在运行了。
注意:如果电脑的80端口被占用(常见于已安装IIS或Skype的情况),需要在XAMPP的Apache配置中修改监听端口。编辑
xampp\apache\conf\httpd.conf,找到"Listen 80"改为"Listen 8080",同时修改"ServerName localhost:80"为对应端口。
2.2 验证安装与第一个脚本
环境就绪后,在XAMPP的安装目录(通常是C:\xampp)中找到htdocs文件夹,这是Apache的默认文档根目录。新建一个名为test.php的文件,用记事本或任何文本编辑器输入以下代码:
<?php echo "<h1>我的第一个PHP页面</h1>"; echo "当前服务器时间:".date("Y-m-d H:i:s"); phpinfo(); ?>保存后,在浏览器访问http://localhost/test.php(如果改了端口需加上端口号,如http://localhost:8080/test.php),你应该能看到一个包含PHP信息的大页面。这个phpinfo()函数调用会输出当前PHP环境的详细配置信息,是排查环境问题的利器。
2.3 开发工具的选择
虽然PHP可以用最简单的记事本编写,但合适的IDE能极大提升开发效率。以下是2023年主流的PHP开发工具对比:
| 工具名称 | 类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| PHPStorm | 商业IDE | 智能补全、重构强大 | 收费 | 专业PHP开发 |
| VS Code | 免费编辑器 | 轻量、插件丰富 | 需要配置插件 | 全栈开发 |
| NetBeans | 免费IDE | 全功能、PHP支持好 | 较耗资源 | 教育/入门 |
| Sublime Text | 付费编辑器 | 速度快、界面简洁 | 功能需插件扩展 | 快速编辑 |
对于初学者,我推荐从VS Code开始,安装PHP Intelephense插件后就能获得接近PHPStorm的代码智能感知体验。配置步骤如下:
- 安装VS Code后,打开扩展市场(Ctrl+Shift+X)
- 搜索并安装"PHP Intelephense"
- 新建或打开PHP项目文件夹
- 按F5创建调试配置,选择"PHP"环境
3. PHP语法基础:从变量到控制结构
3.1 变量与数据类型
PHP的变量就像贴有标签的储物柜——每个柜子(内存位置)通过一个独特的标签(变量名)来标识,而且柜子里可以存放不同类型的物品(数据)。PHP是弱类型语言,这意味着变量不需要预先声明类型,同一个变量可以在不同时刻存储不同类型的数据。
PHP支持八种原始数据类型:
标量类型:
- 整型(int):
$age = 25; - 浮点型(float):
$price = 19.99; - 字符串(string):
$name = "张三";(可用单引号或双引号) - 布尔型(bool):
$is_active = true;
- 整型(int):
复合类型:
- 数组(array):
$colors = ["红","绿","蓝"]; - 对象(object):
$user = new User();
- 数组(array):
特殊类型:
- 资源(resource):如数据库连接
- NULL:表示变量没有值
变量命名必须遵循以下规则:
- 以美元符号$开头
- 首字符必须是字母或下划线
- 后续字符可以是字母、数字或下划线
- 区分大小写($Name和$name是不同的变量)
最佳实践:虽然PHP允许各种变量命名方式,但建议采用小驼峰式命名(如$userName)或下划线分隔(如$user_name)以提高可读性。避免使用$a、$b等无意义的单字母变量名。
3.2 字符串操作实战
字符串处理是Web开发中最常见的任务之一。PHP提供了丰富的字符串函数:
$text = " PHP编程入门 "; // 去除首尾空格 $trimmed = trim($text); // "PHP编程入门" // 转换大小写 $lower = strtolower($text); // " php编程入门 " $upper = strtoupper($text); // " PHP编程入门 " // 获取长度 $length = mb_strlen($trimmed, "UTF-8"); // 7(中文字符需用mb_系列函数) // 子字符串 $sub = mb_substr($trimmed, 0, 3, "UTF-8"); // "PHP" // 替换 $replaced = str_replace("入门", "精通", $trimmed); // "PHP编程精通" // 分割为数组 $parts = explode("编", $trimmed); // ["PHP", "程入门"]处理中文字符时,务必使用mb_开头的多字节字符串函数(需确保php.ini中启用了mbstring扩展),否则可能出现乱码或计算错误。
3.3 控制结构:条件与循环
控制结构是程序的决策者和重复劳动的执行者。PHP提供了与其他C风格语言相似的控制结构:
条件语句:
// if-elseif-else结构 $score = 85; if ($score >= 90) { echo "优秀"; } elseif ($score >= 80) { echo "良好"; // 这里会输出"良好" } else { echo "继续努力"; } // switch语句 $day = "周一"; switch ($day) { case "周一": echo "每周的开始"; // 这里会输出 break; case "周五": echo "周末快乐"; break; default: echo "普通工作日"; }循环结构:
// for循环 for ($i = 0; $i < 5; $i++) { echo $i . " "; // 输出:0 1 2 3 4 } // while循环 $j = 0; while ($j < 3) { echo $j * 2 . " "; // 输出:0 2 4 $j++; } // foreach循环(特别适合数组) $colors = ["红"=>"red", "绿"=>"green", "蓝"=>"blue"]; foreach ($colors as $cn => $en) { echo "$cn 在英语中是 $en<br>"; } // 输出: // 红 在英语中是 red // 绿 在英语中是 green // 蓝 在英语中是 blue4. 函数与数组:构建可重用代码块
4.1 自定义函数开发
函数是代码复用的基本单元,好的函数应该像黑盒子——使用者只需要知道输入什么、输出什么,而不需要了解内部实现细节。PHP函数定义的基本语法:
function 函数名(参数1, 参数2, ...) { // 函数体 return 返回值; // 可选 }一个计算商品折扣的实际例子:
function calculateDiscount($price, $discountRate = 0.9) { if (!is_numeric($price) || $price <= 0) { trigger_error("价格必须是正数", E_USER_WARNING); return false; } $discounted = $price * $discountRate; return round($discounted, 2); // 保留两位小数 } // 使用 $originalPrice = 188.88; $finalPrice = calculateDiscount($originalPrice); // 使用默认9折 echo "折后价:¥$finalPrice"; // 输出:折后价:¥169.99 // 指定7折优惠 $vipPrice = calculateDiscount($originalPrice, 0.7); echo "VIP价:¥$vipPrice"; // 输出:VIP价:¥132.22经验之谈:函数参数设置默认值可以增加灵活性,但要注意将带默认值的参数放在参数列表最后。函数内部应该对参数进行验证,避免错误数据导致意外行为。
4.2 数组的深度应用
PHP的数组实际上是有序映射(既能当数字索引数组用,也能当字典用),这是它最强大也最容易让初学者困惑的特性之一。数组分为三种类型:
索引数组:传统的数字索引数组
$colors = ["红", "绿", "蓝"]; // 等价于 $colors = [0 => "红", 1 => "绿", 2 => "蓝"];关联数组:字符串键名的键值对集合
$user = [ "name" => "张三", "age" => 25, "email" => "zhangsan@example.com" ];多维数组:数组嵌套数组
$products = [ [ "id" => 1, "name" => "笔记本电脑", "price" => 5999 ], [ "id" => 2, "name" => "智能手机", "price" => 3999 ] ];
PHP提供了超过80个数组处理函数,以下是最常用的几个:
$nums = [10, 20, 30, 40, 50]; // 添加元素 array_push($nums, 60); // 末尾添加 $nums[] = 70; // 更简洁的写法 // 删除元素 $last = array_pop($nums); // 移除并返回最后一个元素(70) $first = array_shift($nums); // 移除并返回第一个元素(10) // 数组合并 $moreNums = [80, 90]; $allNums = array_merge($nums, $moreNums); // [20,30,40,50,60,80,90] // 数组过滤 $filtered = array_filter($allNums, function($n) { return $n > 30; }); // [40,50,60,80,90] // 数组映射 $doubled = array_map(function($n) { return $n * 2; }, $filtered); // [80,100,120,160,180] // 数组排序 rsort($doubled); // 降序排序 [180,160,120,100,80]4.3 包含文件与代码组织
当项目规模增长时,把代码拆分到多个文件中是必要的。PHP提供了四种包含文件的方式:
include:包含文件,失败时只发警告include_once:确保文件只被包含一次require:包含文件,失败时致命错误require_once:确保文件只被包含一次,失败时致命错误
实际项目中的典型文件结构:
/project /includes config.php # 数据库配置等 functions.php # 公共函数库 /templates header.php # 页面头部 footer.php # 页面底部 index.php # 入口文件在index.php中:
<?php // 加载配置和函数库 require_once 'includes/config.php'; require_once 'includes/functions.php'; // 开始页面输出 include 'templates/header.php'; // 页面主要内容 echo "<main>欢迎来到我的网站</main>"; // 页脚 include 'templates/footer.php'; ?>避坑指南:使用require包含关键文件(如数据库配置),因为如果这些文件缺失,程序不应该继续运行。对于模板文件等非关键内容,可以使用include。_once版本可以防止重复包含导致的函数重定义等问题,但会有轻微性能开销。
5. 表单处理与数据库交互
5.1 表单数据接收与验证
Web应用的核心功能之一就是处理用户通过表单提交的数据。PHP通过超全局变量$_GET、$_POST和$_REQUEST来接收这些数据。
一个完整的用户注册表单处理示例:
register.html:
<form action="process_register.php" method="post"> <div> <label for="username">用户名:</label> <input type="text" id="username" name="username" required minlength="4"> </div> <div> <label for="email">邮箱:</label> <input type="email" id="email" name="email" required> </div> <div> <label for="password">密码:</label> <input type="password" id="password" name="password" required minlength="8"> </div> <button type="submit">注册</button> </form>process_register.php:
<?php // 1. 接收数据并基础过滤 $username = trim($_POST['username'] ?? ''); $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL); $password = $_POST['password'] ?? ''; // 2. 验证数据 $errors = []; // 用户名验证 if (empty($username)) { $errors[] = "用户名不能为空"; } elseif (strlen($username) < 4) { $errors[] = "用户名至少4个字符"; } elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) { $errors[] = "用户名只能包含字母、数字和下划线"; } // 邮箱验证 if (empty($email)) { $errors[] = "邮箱不能为空"; } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $errors[] = "邮箱格式不正确"; } // 密码验证 if (empty($password)) { $errors[] = "密码不能为空"; } elseif (strlen($password) < 8) { $errors[] = "密码至少8个字符"; } // 3. 处理验证结果 if (!empty($errors)) { // 显示错误信息 echo "<h2>注册失败</h2>"; echo "<ul>"; foreach ($errors as $error) { echo "<li>$error</li>"; } echo "</ul>"; echo '<a href="register.html">返回注册页面</a>'; exit; } // 4. 数据有效,继续处理(如存入数据库) // 密码哈希处理(永远不要明文存储密码!) $hashedPassword = password_hash($password, PASSWORD_DEFAULT); // 这里应该有数据库存储操作(下一节介绍) // saveUserToDatabase($username, $email, $hashedPassword); // 5. 注册成功反馈 echo "<h2>注册成功!</h2>"; echo "<p>欢迎,$username!</p>"; ?>5.2 MySQL数据库基础操作
PHP与MySQL是天作之合,它们就像咖啡和奶精的组合——各自都很强大,但结合在一起才最完美。PHP通过MySQLi(MySQL Improved)和PDO(PHP Data Objects)两种扩展与MySQL交互。
使用MySQLi的完整示例:
<?php // 1. 数据库配置 define('DB_HOST', 'localhost'); define('DB_USER', 'root'); define('DB_PASS', 'password'); // 生产环境要用更安全的密码 define('DB_NAME', 'my_app'); // 2. 创建连接 $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME); // 检查连接 if ($conn->connect_error) { die("连接失败: " . $conn->connect_error); } // 3. 创建表(实际项目中通常在外部执行) $sql = "CREATE TABLE IF NOT EXISTS users ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30) NOT NULL UNIQUE, email VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, reg_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP )"; if (!$conn->query($sql)) { echo "创建表错误: " . $conn->error; } // 4. 插入数据(使用预处理语句防止SQL注入) $stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $username, $email, $hashedPassword); // 设置参数并执行 $username = "testuser"; $email = "test@example.com"; $hashedPassword = password_hash("secure123", PASSWORD_DEFAULT); if ($stmt->execute()) { echo "新记录创建成功,ID: " . $stmt->insert_id; } else { echo "错误: " . $stmt->error; } // 5. 查询数据 $sql = "SELECT id, username, email, reg_date FROM users"; $result = $conn->query($sql); if ($result->num_rows > 0) { echo "<h2>用户列表</h2>"; echo "<table border='1'>"; echo "<tr><th>ID</th><th>用户名</th><th>邮箱</th><th>注册时间</th></tr>"; while($row = $result->fetch_assoc()) { echo "<tr>"; echo "<td>".$row["id"]."</td>"; echo "<td>".$row["username"]."</td>"; echo "<td>".$row["email"]."</td>"; echo "<td>".$row["reg_date"]."</td>"; echo "</tr>"; } echo "</table>"; } else { echo "0 结果"; } // 6. 关闭连接 $stmt->close(); $conn->close(); ?>使用PDO的相同操作:
<?php try { // 1. 创建PDO连接 $pdo = new PDO( "mysql:host=localhost;dbname=my_app;charset=utf8mb4", "root", "password", [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ] ); // 2. 插入数据 $stmt = $pdo->prepare("INSERT INTO users (username, email, password) VALUES (:username, :email, :password)"); $stmt->execute([ ':username' => 'pdo_user', ':email' => 'pdo@example.com', ':password' => password_hash('pdo123', PASSWORD_DEFAULT) ]); echo "PDO插入成功,ID: " . $pdo->lastInsertId(); // 3. 查询数据 $stmt = $pdo->query("SELECT * FROM users"); $users = $stmt->fetchAll(); echo "<pre>"; print_r($users); echo "</pre>"; } catch (PDOException $e) { die("数据库错误: " . $e->getMessage()); } ?>安全警示:永远不要直接将用户输入拼接到SQL语句中!上面的例子使用了预处理语句(MySQLi的bind_param和PDO的prepare+execute),这是防止SQL注入攻击的唯一可靠方法。即使参数来自下拉菜单等"可信"来源,也应该使用预处理语句。
6. 错误处理与调试技巧
6.1 PHP错误处理机制
PHP的错误处理就像汽车的仪表盘警告灯——它能在问题变得严重之前给你提示。PHP错误主要分为以下几类:
- E_ERROR:致命运行时错误(脚本终止)
- E_WARNING:运行时警告(非致命,脚本继续)
- E_NOTICE:运行时通知(可能是错误的前兆)
- E_PARSE:编译时语法错误
- E_USER_:用户触发的错误(通过trigger_error())
在开发阶段,建议显示所有错误以便调试。在php.ini中设置:
error_reporting = E_ALL display_errors = On或者在脚本开头使用:
error_reporting(E_ALL); ini_set('display_errors', 1);在生产环境中,应该关闭错误显示但记录到日志:
error_reporting(E_ALL); ini_set('display_errors', 0); ini_set('log_errors', 1); ini_set('error_log', '/path/to/php-errors.log');6.2 自定义错误处理
PHP允许你设置自定义错误处理器,就像给系统安装了一个更智能的警报系统:
set_error_handler(function($errno, $errstr, $errfile, $errline) { $errorTypes = [ E_ERROR => 'ERROR', E_WARNING => 'WARNING', E_PARSE => 'PARSE', E_NOTICE => 'NOTICE', E_CORE_ERROR => 'CORE_ERROR', E_CORE_WARNING => 'CORE_WARNING', E_COMPILE_ERROR => 'COMPILE_ERROR', E_COMPILE_WARNING => 'COMPILE_WARNING', E_USER_ERROR => 'USER_ERROR', E_USER_WARNING => 'USER_WARNING', E_USER_NOTICE => 'USER_NOTICE', E_STRICT => 'STRICT', E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR', E_DEPRECATED => 'DEPRECATED', E_USER_DEPRECATED => 'USER_DEPRECATED' ]; $type = $errorTypes[$errno] ?? 'UNKNOWN'; $message = "[$type] $errstr in $errfile on line $errline"; // 记录到文件 error_log($message); // 如果是开发环境,显示友好错误 if (ini_get('display_errors')) { echo "<div style='color:red;padding:10px;border:1px solid #f00;'>$message</div>"; } // 如果错误是致命的,应该终止脚本 if ($errno === E_ERROR || $errno === E_USER_ERROR) { die("致命错误,脚本终止"); } // 返回false将允许PHP继续执行标准错误处理 return false; });6.3 异常处理
异常是PHP5引入的面向对象错误处理机制,它像是一种更结构化的"紧急预案"。与错误不同,异常必须被捕获处理,否则会导致致命错误。
class DatabaseException extends Exception {} class Database { private $conn; public function __construct($host, $user, $pass, $db) { $this->conn = new mysqli($host, $user, $pass, $db); if ($this->conn->connect_error) { throw new DatabaseException("连接失败: " . $this->conn->connect_error); } } public function query($sql) { $result = $this->conn->query($sql); if (!$result) { throw new DatabaseException("查询错误: " . $this->conn->error); } return $result; } } // 使用try-catch块处理异常 try { $db = new Database('localhost', 'wronguser', 'wrongpass', 'my_app'); $result = $db->query("SELECT * FROM non_existent_table"); } catch (DatabaseException $e) { echo "数据库操作出错: " . $e->getMessage(); // 可以记录日志、发送警报等 } catch (Exception $e) { echo "一般错误: " . $e->getMessage(); } finally { echo "<br>清理工作..."; }6.4 调试技巧与工具
调试PHP代码就像侦探破案——需要收集线索、分析证据。以下是我多年实践中总结的高效调试方法:
var_dump和print_r:
$data = ['name' => '张三', 'age' => 25]; echo '<pre>'; var_dump($data); // 显示类型和值 print_r($data); // 更简洁的输出 echo '</pre>';Xdebug(PHP调试神器):
- 安装Xdebug扩展
- 配置php.ini:
zend_extension=xdebug.so xdebug.mode=debug xdebug.start_with_request=yes - 在IDE(如PHPStorm)中设置断点调试
日志记录:
// 简单日志函数 function log_message($message, $context = []) { $entry = date('[Y-m-d H:i:s]') . ' ' . $message; if (!empty($context)) { $entry .= ' ' . json_encode($context); } error_log($entry); } // 使用 log_message("用户登录失败", ['username' => 'test', 'ip' => $_SERVER['REMOTE_ADDR']]);网络请求调试:
- 使用Postman测试API
- 检查HTTP头信息:
echo '<pre>'; print_r(getallheaders()); print_r($_SERVER); echo '</pre>';
数据库调试:
- 在SQL查询前输出最终SQL:
$sql = "SELECT * FROM users WHERE id = ?"; $stmt = $conn->prepare($sql); $stmt->bind_param("i", $id); $id = 1; // 调试用 - 显示实际执行的SQL echo "调试SQL: " . $sql . " 参数: [$id]<br>"; $stmt->execute();
- 在SQL查询前输出最终SQL:
调试心得:当遇到"诡异"的问题时,首先检查:1) 错误报告是否开启;2) 变量是否意外被修改;3) 缓存是否导致旧代码运行;4) 文件权限问题。使用
phpinfo()确认当前运行的php.ini位置和加载的扩展。
7. 面向对象编程与常用设计模式
7.1 PHP面向对象基础
PHP从版本4开始支持基本的面向对象特性,但直到PHP5才真正成熟。面向对象编程(OOP)就像用乐高积木构建系统——每个类是一个独立的积木块,可以组合出复杂结构。
类与对象的基本概念:
class User { // 属性(特征) private $name; protected $email; public $age; // 构造方法(创建对象时自动调用) public function __construct($name, $email, $age) { $this->name = $name; $this->email = $email; $this->age = $age; } // 方法(行为) public function greet() { return "你好,我是{$this->name},我的邮箱是{$this->email}"; } // Getter方法 public function getName() { return $this->name; } // Setter方法 public function setEmail($newEmail) { if (filter_var($newEmail, FILTER_VALIDATE_EMAIL)) { $this->email = $newEmail; return true; } return false; } } // 创建对象(实例化) $user1 = new User("张三", "zhangsan@example.com", 25); // 访问属性和方法 echo $user1->greet(); // 你好,我是张三,我的邮箱是zhangsan@example.com echo $user1->age; // 25(公共属性可以直接访问) // echo $user1->name; // 错误!私有属性不能外部访问 echo $user1->getName(); // 正确方式访问私有属性 // 修改属性 $user1->setEmail("new@example.com"); // 成功 $user1->setEmail("invalid-email"); // 失败7.2 继承与多态
继承就像基因遗传——子类会继承父类的特征,但可以有自己的特点:
class Animal { protected $name; public function __construct($name) { $this->name = $name; } public function speak() { return "{$this->name} 发出声音"; } } class Dog extends Animal { public function speak() { return "{$this->name} 汪汪叫"; } public function fetch() { return "{$this->name} 去捡球"; } } class Cat extends Animal { public function speak() { return "{$this->name} 喵喵叫"; } public function climb() { return "{$this->name} 爬上了树"; } } // 多态演示 $animals = [ new Dog("阿黄"), new Cat("小花"), new Animal("普通动物") ]; foreach ($animals as $animal) { echo $animal->speak() . "<br>"; } // 输出: // 阿黄 汪汪叫 // 小花 喵喵叫 // 普通动物 发出声音7.3 常用设计模式实践
设计模式是解决特定问题的经验总结,就像建筑师的蓝图。以下是PHP中常用的几种设计模式:
1. 单例模式(确保一个类只有一个实例):
class DatabaseConnection { private static $instance = null; private $connection; // 私有构造方法防止外部实例化 private function __construct() { $this->connection = new PDO("mysql:host=localhost;dbname=test", "root", ""); } // 获取唯一实例 public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } // 获取数据库连接 public function getConnection() { return $this->connection; } // 防止克隆 private function __clone() {} // 防止反序列化 private function __wakeup() {} } // 使用 $db1 = DatabaseConnection::getInstance(); $db2 = DatabaseConnection::getInstance(); var_dump($db1 === $db2); // true,是同一个实例2. 工厂模式(创建对象的接口,让子类决定实例化哪个类):
interface Logger { public function log($message); } class FileLogger implements Logger { private $filePath; public function __construct($filePath) { $this->filePath = $filePath; } public function log($message) { file_put_contents($this->filePath, date('[Y-m-d H:i:s]') . ' ' . $message . PHP_EOL, FILE_APPEND); } } class DatabaseLogger implements Logger { private $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function log($message) { $stmt = $this->pdo->prepare("INSERT INTO logs (message, created_at) VALUES (?, NOW())"); $stmt->execute([$message]); } } class LoggerFactory { public static function createLogger($type, $param) { switch ($type) { case 'file': return new FileLogger($param); case 'database': return new DatabaseLogger($param); default: throw new InvalidArgumentException("未知的日志类型: $type"); } } } // 使用 $fileLogger = LoggerFactory::createLogger('file', 'app.log'); $fileLogger->log("测试文件日志"); $pdo = new PDO("mysql:host=localhost;dbname=test", "root", ""); $dbLogger = LoggerFactory::createLogger('database', $pdo); $dbLogger->log("测试数据库日志");3. 观察者模式(对象状态变化时通知依赖对象):
interface Observer { public function update($data); } interface Subject { public function attach(Observer $observer); public function detach(Observer $observer); public function notify(); } class Order implements Subject { private $observers = []; private $state; public function attach(Observer $observer) { $this->observers[] = $observer; } public function detach(Observer $observer) { $index = array_search($observer, $this->observers); if ($index !== false) { unset($this->observers[$index]); } } public function notify() { foreach ($this->observers as $observer) { $observer->update($this->state); } } public function setState($state) { $this->state = $state; $this->notify(); } } class EmailNotifier implements Observer { public function update($data) { echo "发送邮件通知: 订单状态变更为 $data<br>"; } } class SmsNotifier implements Observer { public function update($data) { echo "发送短信通知